diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index da79fab..5100f5a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,6 +4,10 @@ - **Implementation mirrors the official Python reference**: The C++ code structure, type names, and semantics directly correspond to the Python implementation in `ref/component-model/design/mvp/canonical-abi/definitions.py` and the specification in `ref/component-model/design/mvp/CanonicalABI.md`. When making changes, cross-reference these files to ensure alignment. - Supports async runtime primitives (`Store`, `Thread`, `Task`), resource management (`own`, `borrow`), streams/futures, waitables, and error contexts. +## Working style +- **Do not create summary documents**: Avoid creating markdown files in `docs/` to summarize work sessions, changes, or fixes unless explicitly requested. The git commit history and code comments are sufficient documentation. +- Focus on making changes directly to code, updating existing documentation only when functionality changes, and providing concise summaries in responses rather than generating files. + ## Core headers - `include/cmcpp/traits.hpp` defines `ValTrait`, type aliases (e.g., `bool_t`, `string_t`), and concept shortcuts that every new type must implement. - `include/cmcpp/context.hpp` encapsulates `LiftLowerContext`, `ComponentInstance`, `Task`, resource tables (`HandleTable`, `InstanceTable`), and canonical operations like `canon_waitable_*`, `canon_stream_*`, `canon_future_*`. @@ -37,5 +41,4 @@ ## Tooling notes - Dependencies are managed through `vcpkg.json` with overlays in `vcpkg_overlays/` (notably for WAMR); stick with preset builds so CMake wires in the correct toolchain file automatically. - Cargo manifest (`Cargo.toml`) is only for fetching `wasm-tools` and `wit-bindgen-cli`; if you touch wasm generation logic, update both the manifest and any scripts referencing those versions. -- Release management uses Release Please with conventional commits (`feat:`, `fix:`, etc.) to automate changelog and version bumps. - Keep documentation alongside code: update `README.md` when introducing new host types or workflows so downstream integrators stay aligned with the canonical ABI behavior. diff --git a/.github/instructions/general.instructions.md b/.github/instructions/general.instructions.md index 41d6a2e..0d90e47 100644 --- a/.github/instructions/general.instructions.md +++ b/.github/instructions/general.instructions.md @@ -45,5 +45,4 @@ This repository is a modern C++20 header-only library implementing WebAssembly C - **Function Templates**: Use function templates for type-generic operations - **SFINAE/Concepts**: Use concepts or SFINAE for template constraints - **Const Correctness**: Maintain const correctness throughout -- **Naming**: Use snake_case for variables, PascalCase for types -- **Commit Messages**: Use conventional commits (`feat:`, `fix:`, etc.) for Release Please automation \ No newline at end of file +- **Naming**: Use snake_case for variables, PascalCase for types \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b4af7d..234ec07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: C++ Release CI +name: Release on: push: @@ -11,15 +11,59 @@ env: jobs: build: strategy: + fail-fast: false matrix: include: - - os: "ubuntu-24.04" - fail-fast: false + - os: ubuntu-24.04 + preset: linux-ninja-Release + build-preset: linux-ninja-Release + cpack-generators: "TGZ;DEB;RPM" + package-patterns: | + build/cmcpp-*.tar.gz + build/cmcpp_*.deb + build/cmcpp-*.rpm + extra-deps: | + sudo apt-get update + sudo apt-get install -y \ + autoconf \ + autoconf-archive \ + automake \ + build-essential \ + ninja-build \ + rpm - runs-on: ${{ matrix.os }} + - os: windows-2022 + preset: vcpkg-VS-17 + build-preset: VS-17-Release + cpack-generators: "ZIP;NSIS" + cpack-config: "-C Release" + package-patterns: | + build/cmcpp-*.zip + build/cmcpp-*.exe + extra-deps: "" + - os: macos-14 + preset: linux-ninja-Release + build-preset: linux-ninja-Release + cpack-generators: "TGZ;ZIP" + package-patterns: | + build/cmcpp-*.tar.gz + build/cmcpp-*.zip + extra-deps: | + brew install \ + pkg-config \ + autoconf \ + autoconf-archive \ + automake \ + coreutils \ + libtool \ + cmake \ + ninja + + runs-on: ${{ matrix.os }} permissions: contents: write + steps: - uses: actions/checkout@v4 with: @@ -47,47 +91,45 @@ jobs: restore-keys: | ${{ runner.os }}-vcpkg- - - name: install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - autoconf \ - autoconf-archive \ - automake \ - build-essential \ - ninja-build - - - name: Install rust dependencies + - name: Install dependencies + if: matrix.extra-deps != '' + run: ${{ matrix.extra-deps }} + + - name: Install Rust dependencies run: | cargo install wasm-tools wit-bindgen-cli || true - - name: configure + - name: Configure CMake run: | - cmake --preset linux-ninja-Release + cmake --preset ${{ matrix.preset }} - - name: build + - name: Build run: | - cmake --build --preset linux-ninja-Release + cmake --build --preset ${{ matrix.build-preset }} - - name: test + - name: Run Tests working-directory: build run: | - ctest -VV + ctest ${{ matrix.cpack-config || '' }} -VV - - name: Package + - name: Create Packages + working-directory: build run: | - tar -czvf cmcpp-${GITHUB_REF_NAME}.tar.gz include LICENSE LICENSE-BOOST-PFR + IFS=';' read -ra GENS <<< "${{ matrix.cpack-generators }}" + for gen in "${GENS[@]}"; do + cpack ${{ matrix.cpack-config || '' }} -G "$gen" + done + shell: bash - - name: Create Release - uses: ncipollo/release-action@v1 + - name: Upload Packages + uses: softprops/action-gh-release@v2 with: - generateReleaseNotes: true - tag: ${{ github.ref_name }} - artifacts: cmcpp-${{ github.ref_name }}.tar.gz + files: ${{ matrix.package-patterns }} + fail_on_unmatched_files: false - name: Upload error logs if: ${{ failure() || cancelled() }} uses: actions/upload-artifact@v4 with: - name: ${{ matrix.os }}-logs + name: ${{ runner.os }}-logs path: ./**/*.log diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ab7f46e..f0abfb1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -52,6 +52,12 @@ jobs: restore-keys: | ${{ runner.os }}-vcpkg- + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: "17" + distribution: "temurin" + - name: Install rust dependencies run: | cargo install wasm-tools wit-bindgen-cli || true diff --git a/.gitignore b/.gitignore index 72b4b8c..a446064 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -/build* +/build /.vscode/settings.json /cpm_modules /vcpkg_installed /test/wasm/build* .DS_Store +# ANTLR jar files +antlr-*.jar +*.jar # Added by cargo diff --git a/.gitmodules b/.gitmodules index 3ab3449..be3efb0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,7 @@ url = https://github.com/WebAssembly/component-model.git [submodule "ref/wasm-micro-runtime"] path = ref/wasm-micro-runtime - url = git@github.com:bytecodealliance/wasm-micro-runtime.git + url = https://github.com/bytecodealliance/wasm-micro-runtime.git +[submodule "ref/wit-bindgen"] + path = ref/wit-bindgen + url = https://github.com/bytecodealliance/wit-bindgen.git diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f231d9..ef391ab 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -70,6 +70,56 @@ "environment": [], "externalConsole": false, "preLaunchTask": "CMake: build" + }, + { + "name": "(gdb) test-wit-grammar", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/test-wit-grammar", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "preLaunchTask": "CMake: build" + }, + { + "name": "(gdb) test-wit-grammar (verbose)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/test-wit-grammar", + "args": [ + "--verbose" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "preLaunchTask": "CMake: build" + }, + { + "name": "(win) test-wit-grammar", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/Debug/test-wit-grammar.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "preLaunchTask": "CMake: build" + }, + { + "name": "(win) test-wit-grammar (verbose)", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/Debug/test-wit-grammar.exe", + "args": [ + "--verbose" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "preLaunchTask": "CMake: build" } ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1370e61..484bb64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.10) -project(cmcpp LANGUAGES CXX) +project(cmcpp + VERSION 0.1.0 + DESCRIPTION "C++20 header-only implementation of the WebAssembly Component Model canonical ABI" + HOMEPAGE_URL "https://github.com/GordonSmith/component-model-cpp" + LANGUAGES CXX +) # Determine default for BUILD_SAMPLES: OFF on Windows, ON elsewhere set(_BUILD_SAMPLES_DEFAULT ON) @@ -9,6 +14,7 @@ if (WIN32) endif() option(BUILD_SAMPLES "Build samples" ${_BUILD_SAMPLES_DEFAULT}) option(BUILD_TESTING "Build tests" ON) +option(BUILD_GRAMMAR "Generate code from ANTLR grammar" ON) add_library(cmcpp INTERFACE) @@ -40,24 +46,155 @@ if (BUILD_SAMPLES AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/samples/CMakeLists.txt add_subdirectory(samples) endif() +# Grammar must be added before tools/test so the generate-grammar target is available +if (BUILD_GRAMMAR AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/grammar/CMakeLists.txt") + add_subdirectory(grammar) + # Add tools that use the grammar + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/wit-codegen/CMakeLists.txt") + add_subdirectory(tools/wit-codegen) + endif() +endif() + if (BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") enable_testing() add_subdirectory(test) endif() include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +# Install header files install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) +# Install library target install(TARGETS cmcpp EXPORT cmcppTargets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) +# Install CMake export file install(EXPORT cmcppTargets FILE cmcppTargets.cmake NAMESPACE cmcpp:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cmcpp ) + +# Generate and install CMake config files +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmcppConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmcppConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cmcpp + PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_BINDIR +) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/cmcppConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmcppConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmcppConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cmcpp +) + +# Install wit-codegen tool if it was built +if(BUILD_GRAMMAR AND TARGET wit-codegen) + install(TARGETS wit-codegen + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT tools + ) + + # Install ANTLR4 runtime DLL on Windows + if(WIN32) + # Find and install the ANTLR4 runtime DLL + set(ANTLR4_DLL_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/bin/antlr4-runtime.dll") + if(EXISTS "${ANTLR4_DLL_PATH}") + install(FILES "${ANTLR4_DLL_PATH}" + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT tools + ) + message(STATUS "Will install ANTLR4 runtime DLL: ${ANTLR4_DLL_PATH}") + else() + message(WARNING "ANTLR4 runtime DLL not found at: ${ANTLR4_DLL_PATH}") + endif() + elseif(UNIX AND TARGET antlr4_shared) + # On Linux/macOS, try to find and install the shared library + if(APPLE) + set(ANTLR4_LIB_PATTERN "libantlr4-runtime*.dylib") + else() + set(ANTLR4_LIB_PATTERN "libantlr4-runtime.so*") + endif() + + file(GLOB ANTLR4_LIBS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib/${ANTLR4_LIB_PATTERN}") + if(ANTLR4_LIBS) + install(FILES ${ANTLR4_LIBS} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT tools + ) + endif() + endif() +endif() + +# CPack configuration for package generation +set(CPACK_PACKAGE_NAME "cmcpp") +set(CPACK_PACKAGE_VENDOR "GordonSmith") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "C++20 WebAssembly Component Model implementation") +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/GordonSmith/component-model-cpp") +set(CPACK_PACKAGE_CONTACT "GordonSmith") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +# Package components +set(CPACK_COMPONENTS_ALL libraries headers tools) +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "CMake Library Targets") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C++ Header Files") +set(CPACK_COMPONENT_TOOLS_DISPLAY_NAME "WIT Code Generator Tool") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION "Header-only C++20 Component Model implementation") +set(CPACK_COMPONENT_TOOLS_DESCRIPTION "wit-codegen executable for generating C++ bindings from WIT files") + +# Platform-specific package generators +if(WIN32) + set(CPACK_GENERATOR "ZIP;NSIS") + set(CPACK_NSIS_DISPLAY_NAME "cmcpp ${PROJECT_VERSION}") + set(CPACK_NSIS_PACKAGE_NAME "cmcpp") + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) +elseif(APPLE) + set(CPACK_GENERATOR "TGZ;ZIP") +elseif(UNIX) + set(CPACK_GENERATOR "TGZ;DEB;RPM") + + # Debian/Ubuntu package settings + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "GordonSmith") + set(CPACK_DEBIAN_PACKAGE_SECTION "devel") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "") + set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + + # RPM package settings + set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0") + set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") + set(CPACK_RPM_FILE_NAME RPM-DEFAULT) +endif() + +# Source package +set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") +set(CPACK_SOURCE_IGNORE_FILES + /.git + /.github + /build + /\\\\.vscode + /\\\\.vs + /\\\\.cache + \\\\.gitignore + \\\\.gitmodules +) + +include(CPack) + diff --git a/README.md b/README.md index 2371986..4eab30c 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,6 @@ This repository contains a C++ ABI implementation of the WebAssembly Component Model. -## Release management - -Automated releases are handled by [Release Please](https://github.com/googleapis/release-please) via GitHub Actions. Conventional commit messages (`feat:`, `fix:`, etc.) keep the changelog accurate and drive version bumps; when enough changes accumulate, the workflow opens a release PR that can be merged to publish a GitHub release. - ## Features ### OS @@ -78,6 +74,9 @@ When expanding the canonical ABI surface, cross-check the Python reference tests **Ubuntu/Linux:** ```bash sudo apt-get install -y autoconf autoconf-archive automake build-essential ninja-build + +# Optional: for creating RPM packages +sudo apt-get install -y rpm ``` **macOS:** @@ -166,6 +165,35 @@ ctest -VV # Run tests ### Build Options +The following CMake options control what gets built: + +- `BUILD_TESTING` (default: ON) - Build unit tests +- `BUILD_SAMPLES` (default: ON) - Build sample applications demonstrating runtime integration +- `BUILD_GRAMMAR` (default: ON) - Generate C++ code from ANTLR grammar for WIT parsing + +Example: +```bash +cmake -DBUILD_TESTING=ON -DBUILD_SAMPLES=ON -DBUILD_GRAMMAR=ON .. +``` + +#### Grammar Code Generation + +The project includes an ANTLR grammar for parsing WebAssembly Interface Types (WIT). To generate C++ parser code: + +```bash +# Enable grammar generation during configuration +cmake -DBUILD_GRAMMAR=ON .. + +# Generate the code +cmake --build . --target generate-grammar +``` + +**Requirements:** +- Java runtime (for ANTLR) +- The ANTLR jar is automatically downloaded during CMake configuration + +Generated C++ files are compiled into a static library `wit-grammar` in the build tree that can be linked by tools. See [grammar/README.md](grammar/README.md) for details. + ### Coverage @@ -195,11 +223,69 @@ genhtml coverage.filtered.info --output-directory coverage-html # optional Generated artifacts live in the `build/` directory (`coverage.info`, `coverage.filtered.info`, and optionally `coverage-html/`). The same commands work on other platforms once the equivalent of `lcov` (or LLVM's `llvm-cov`) is installed. +## Installation and Packaging + +### Installing Locally + +Install cmcpp to a local directory (default: `build/stage`): + +```bash +cmake --preset linux-ninja-Debug +cmake --build build +cmake --build build --target install +``` + +### Using in Other CMake Projects + +Once installed, use `find_package()` to integrate cmcpp: + +```cmake +find_package(cmcpp REQUIRED) +target_link_libraries(my_app PRIVATE cmcpp::cmcpp) +``` + +Build your project: + +```bash +cmake . -DCMAKE_PREFIX_PATH=/path/to/cmcpp/install +cmake --build . +``` + +### Creating Distribution Packages + +Generate packages with CPack: + +```bash +cd build + +# All default packages for your platform +cpack + +# Specific formats +cpack -G TGZ # Tar.gz archive (cross-platform) +cpack -G DEB # Debian package (.deb) +cpack -G RPM # RPM package (.rpm) - requires 'rpm' package installed +cpack -G ZIP # ZIP archive (Windows) +``` + +**Note:** To create RPM packages on Ubuntu/Debian, install the `rpm` package first: +```bash +sudo apt-get install -y rpm +``` + +Packages include: +- Complete header-only library +- CMake config files for `find_package()` +- `wit-codegen` tool for generating C++ bindings from WIT files (if `BUILD_GRAMMAR=ON`) + +See [docs/PACKAGING.md](docs/PACKAGING.md) for complete packaging documentation. + ## Usage This library is a header only library. To use it in your project, you can: - [x] Copy the contents of the `include` directory to your project. -- [ ] Use `vcpkg` to install the library and its dependencies. +- [x] Install via `cmake --build build --target install` and use `find_package(cmcpp)`. +- [ ] Use `vcpkg` to install the library and its dependencies (planned). ### Configuring `InstanceContext` and canonical options diff --git a/cmcppConfig.cmake.in b/cmcppConfig.cmake.in new file mode 100644 index 0000000..0dbf6d3 --- /dev/null +++ b/cmcppConfig.cmake.in @@ -0,0 +1,33 @@ +@PACKAGE_INIT@ + +# cmcppConfig.cmake - CMake package configuration file for cmcpp +# This file allows other projects to find and use cmcpp via find_package() + +include(CMakeFindDependencyMacro) + +# cmcpp is a header-only library, but users may need these dependencies +# for the complete Component Model functionality +# Note: These are optional - users can choose to provide their own implementations +# find_dependency(ICU COMPONENTS uc dt in io) + +# Include the targets file which defines cmcpp::cmcpp +include("${CMAKE_CURRENT_LIST_DIR}/cmcppTargets.cmake") + +# Define convenient variables for users +set(CMCPP_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set(CMCPP_LIBRARIES cmcpp::cmcpp) + +# Check that the library target was imported successfully +if(NOT TARGET cmcpp::cmcpp) + message(FATAL_ERROR "cmcpp::cmcpp target not found!") +endif() + +check_required_components(cmcpp) + +# Provide information about optional tools +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../../@CMAKE_INSTALL_BINDIR@/wit-codegen@CMAKE_EXECUTABLE_SUFFIX@") + set(CMCPP_WIT_CODEGEN_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/../../../@CMAKE_INSTALL_BINDIR@/wit-codegen@CMAKE_EXECUTABLE_SUFFIX@") + message(STATUS "Found wit-codegen: ${CMCPP_WIT_CODEGEN_EXECUTABLE}") +endif() + +message(STATUS "Found cmcpp: ${CMAKE_CURRENT_LIST_DIR}") diff --git a/cmcppConfigVersion.cmake.in b/cmcppConfigVersion.cmake.in new file mode 100644 index 0000000..df8e7b6 --- /dev/null +++ b/cmcppConfigVersion.cmake.in @@ -0,0 +1,26 @@ +set(PACKAGE_VERSION "@PROJECT_VERSION@") + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() + +# Check for version compatibility based on semantic versioning +# Major version must match for compatibility +if(PACKAGE_FIND_VERSION_MAJOR STREQUAL "0") + # For 0.x.y versions, minor version must also match (pre-1.0 API instability) + if(NOT "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}" VERSION_EQUAL + "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@") + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() +else() + # For 1.x.y and above, only major version must match + if(NOT PACKAGE_FIND_VERSION_MAJOR STREQUAL "@PROJECT_VERSION_MAJOR@") + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() +endif() diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md new file mode 100644 index 0000000..e29db12 --- /dev/null +++ b/docs/PACKAGING.md @@ -0,0 +1,315 @@ +# Packaging Guide for cmcpp + +This document describes how to build, install, and distribute cmcpp packages. + +## Overview + +cmcpp uses CMake's built-in packaging capabilities: +- **CMake Config Files**: For integration with other CMake projects +- **CPack**: For creating distribution packages (tar.gz, DEB, RPM, ZIP, NSIS) + +## Building and Installing + +### Local Installation + +Install cmcpp to a local prefix (default: `build/stage/Debug` for Debug preset): + +```bash +cmake --preset linux-ninja-Debug +cmake --build --preset linux-ninja-Debug +cmake --install build --prefix build/stage/Debug +``` + +Or use the default install directory configured by the preset: + +```bash +cmake --preset linux-ninja-Debug +cmake --build --preset linux-ninja-Debug +cmake --build build --target install +``` + +The installation includes: +- **Headers**: All C++20 header files in `include/` +- **CMake Config**: Package config files for `find_package(cmcpp)` +- **Tools**: `wit-codegen` executable (if `BUILD_GRAMMAR=ON`) + +### Custom Installation Prefix + +```bash +cmake --preset linux-ninja-Debug -DCMAKE_INSTALL_PREFIX=/usr/local +cmake --build --preset linux-ninja-Debug +cmake --build build --target install +``` + +On Windows with Visual Studio preset: + +```bash +cmake --preset vcpkg-VS-17 -DCMAKE_INSTALL_PREFIX=C:/Program\ Files/cmcpp +cmake --build --preset VS-17-Debug +cmake --build build --config Debug --target install +``` + +## Using Installed cmcpp in Other Projects + +Once installed, other CMake projects can find and use cmcpp: + +```cmake +cmake_minimum_required(VERSION 3.10) +project(MyProject) + +# Find cmcpp +find_package(cmcpp REQUIRED) + +# Create your executable +add_executable(my_app main.cpp) + +# Link against cmcpp +target_link_libraries(my_app PRIVATE cmcpp::cmcpp) + +# Optional: Use wit-codegen tool if available +if(DEFINED CMCPP_WIT_CODEGEN_EXECUTABLE) + message(STATUS "wit-codegen found: ${CMCPP_WIT_CODEGEN_EXECUTABLE}") +endif() +``` + +Build your project with: + +```bash +cmake . -DCMAKE_PREFIX_PATH=/path/to/cmcpp/install +cmake --build . +``` + +## Creating Distribution Packages + +### Supported Package Formats + +cmcpp supports multiple package formats via CPack: + +- **Linux**: TGZ, DEB, RPM +- **Windows**: ZIP, NSIS +- **macOS**: TGZ, ZIP + +### Generate All Default Packages + +```bash +cd build +cpack +``` + +This generates packages for all default generators on your platform: +- **Windows**: ZIP and NSIS installer +- **Linux**: TGZ, DEB, and RPM +- **macOS**: TGZ and ZIP + +### Generate Specific Package Types + +#### Tar.gz Archive (Cross-platform) + +```bash +cd build +cpack -G TGZ +``` + +Output: `cmcpp--.tar.gz` + +#### Debian Package (.deb) + +```bash +cd build +cpack -G DEB +``` + +Output: `cmcpp__.deb` + +Install on Ubuntu/Debian: +```bash +sudo dpkg -i cmcpp_0.1.0_amd64.deb +``` + +#### RPM Package (.rpm) + +```bash +cd build +cpack -G RPM +``` + +Output: `cmcpp--..rpm` + +Install on Red Hat/Fedora: +```bash +sudo rpm -i cmcpp-0.1.0-1.x86_64.rpm +``` + +#### Windows ZIP Archive + +```bash +cd build +cpack -G ZIP +``` + +#### Windows NSIS Installer + +```bash +cd build +cpack -G NSIS +``` + +Requires NSIS to be installed on Windows. + +### Package Contents + +All packages include: + +1. **Headers**: Complete C++20 header-only library + - Location: `include/cmcpp/` + - Main header: `include/cmcpp.hpp` + - Runtime integration: `include/wamr.hpp` + - Boost.PFR headers: `include/boost/pfr/` + +2. **CMake Integration Files**: + - `lib/cmake/cmcpp/cmcppConfig.cmake` - Package configuration + - `lib/cmake/cmcpp/cmcppConfigVersion.cmake` - Version compatibility + - `lib/cmake/cmcpp/cmcppTargets.cmake` - Target exports + +3. **Tools** (if `BUILD_GRAMMAR=ON`): + - `bin/wit-codegen` - WIT code generator (or `wit-codegen.exe` on Windows) + - Runtime dependencies (automatically included): + - Windows: `antlr4-runtime.dll` + - Linux: `libantlr4-runtime.so*` + - macOS: `libantlr4-runtime*.dylib` + +### Package Metadata + +Packages are configured with the following metadata: + +- **Name**: cmcpp +- **Version**: From `PROJECT_VERSION` in CMakeLists.txt (e.g., 0.1.0) +- **Description**: C++20 WebAssembly Component Model implementation +- **License**: Apache-2.0 (from LICENSE file) +- **Homepage**: https://github.com/GordonSmith/component-model-cpp +- **Components**: + - `headers` - C++ header files + - `libraries` - CMake library targets + - `tools` - wit-codegen command-line tool + +## Version Compatibility + +cmcpp uses semantic versioning with the following compatibility rules: + +- **Pre-1.0 versions (0.x.y)**: Minor version must match exactly + - `find_package(cmcpp 0.1)` only accepts 0.1.x versions +- **Post-1.0 versions (1.x.y+)**: Major version must match + - `find_package(cmcpp 1.0)` accepts any 1.x.y version + +This ensures API compatibility according to Component Model stability guarantees. + +**Note**: The version compatibility is implemented in `cmcppConfigVersion.cmake.in` with custom logic that overrides CMake's default `SameMajorVersion` compatibility to enforce stricter rules for pre-1.0 versions. + +## Distribution + +### GitHub Releases + +Packages are automatically generated via CI/CD and attached to GitHub releases. + +### vcpkg (Future) + +A vcpkg port is planned for easier installation: + +```bash +vcpkg install cmcpp +``` + +### Manual Distribution + +To distribute cmcpp manually: + +1. Build a release package: + ```bash + cmake --preset linux-ninja-Release + cmake --build --preset linux-ninja-Release + cd build && cpack -G TGZ + ``` + +2. Distribute the generated `cmcpp--.tar.gz` + +3. Users extract and set `CMAKE_PREFIX_PATH`: + ```bash + tar xzf cmcpp-0.1.0-Linux.tar.gz + export CMAKE_PREFIX_PATH=$PWD/cmcpp-0.1.0-Linux + ``` + + Or install system-wide: + ```bash + sudo tar xzf cmcpp-0.1.0-Linux.tar.gz -C /usr/local + # CMAKE_PREFIX_PATH=/usr/local is typically searched by default + ``` + +## Component Installation + +CPack supports installing specific components: + +```bash +# Install only headers (no tools) +cpack -G TGZ -D CPACK_COMPONENTS_ALL="headers;libraries" + +# Install only tools +cpack -G TGZ -D CPACK_COMPONENTS_ALL="tools" +``` + +**Current Limitation**: The headers and library target installations in `CMakeLists.txt` do not explicitly specify `COMPONENT` tags (lines 67-76), so they are included in all packages by default. Only the `wit-codegen` tool has an explicit `COMPONENT tools` tag. To properly separate components, the install commands would need to be updated to include: +```cmake +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT headers +) + +install(TARGETS cmcpp + EXPORT cmcppTargets + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT libraries +) +``` + +## Troubleshooting + +### find_package(cmcpp) Not Found + +Ensure `CMAKE_PREFIX_PATH` includes the installation directory: + +```bash +cmake . -DCMAKE_PREFIX_PATH=/path/to/cmcpp/install +``` + +Or set the `cmcpp_DIR` variable directly: + +```bash +cmake . -Dcmcpp_DIR=/path/to/cmcpp/install/lib/cmake/cmcpp +``` + +### Tools Not Included in Package + +Make sure `BUILD_GRAMMAR=ON` when configuring: + +```bash +cmake --preset linux-ninja-Debug -DBUILD_GRAMMAR=ON +``` + +### Package Generation Fails + +Check CPack generators are available: + +```bash +cpack --help | grep "Generators:" +``` + +Install required tools: +- **DEB**: `dpkg-deb` (usually pre-installed on Debian/Ubuntu) +- **RPM**: `rpmbuild` (`sudo apt-get install rpm` on Ubuntu) +- **NSIS**: Download from https://nsis.sourceforge.io/ (Windows only) + +## Reference + +- CMake Config File: `cmcppConfig.cmake.in` +- Version File: `cmcppConfigVersion.cmake.in` +- CPack Configuration: Bottom of root `CMakeLists.txt` +- Installation Rules: Root `CMakeLists.txt` (lines 60+) diff --git a/docs/generated-files-structure.md b/docs/generated-files-structure.md new file mode 100644 index 0000000..d454ee7 --- /dev/null +++ b/docs/generated-files-structure.md @@ -0,0 +1,342 @@ +# Generated Files Structure + +The WIT code generator (`wit-codegen`) produces three files to support WAMR host applications: + +## Generated Files + +### 1. `.hpp` - Component Model Interface Declarations + +**Purpose:** Declares all Component Model interfaces (imports and exports) with C++ type mappings. + +**Contents:** +- Namespace `host` - Functions the host must implement (imported by guest) +- Namespace `guest` - Type aliases for all guest function signatures (exported by guest) +- Comments documenting package names and function purposes + +**Example:** +```cpp +namespace host { + // Standalone function: dbglog + // Package: component-model-cpp:test-wasm + void dbglog(cmcpp::string_t msg); +} + +namespace guest { + // Standalone function: bool-and + // Package: component-model-cpp:test-wasm + // Guest function signature for use with guest_function() + using bool_and_t = cmcpp::bool_t(cmcpp::bool_t, cmcpp::bool_t); +} +``` + +**Key Changes from Earlier Versions:** +- Guest functions are now **type aliases only** (not function declarations) +- Used with `guest_function()` template from `` +- Comprehensive documentation comments included + +**Usage:** +```cpp +#include "sample.hpp" +``` + +--- + +### 2. `_wamr.hpp` - WAMR Helper Function Declarations + +**Purpose:** Declares utility functions for registering WAMR native functions. + +**Contents:** +- `NativeRegistration` struct for organizing import interfaces +- `get_import_registrations()` - Get array of all import interfaces +- `register_all_imports()` - Register all imports at once +- `unregister_all_imports()` - Unregister all imports at cleanup +- `wasm_utils` namespace with constants: + - `DEFAULT_STACK_SIZE` + - `DEFAULT_HEAP_SIZE` + +**Header Guard:** +```cpp +#ifndef GENERATED_WAMR_BINDINGS_HPP +#define GENERATED_WAMR_BINDINGS_HPP +``` + +**Includes:** +- `` - WAMR runtime API +- `` - Component Model C++ library +- `".hpp"` - Your interface declarations +- Standard library headers (``, ``, ``) + +**Important Note:** +Helper functions like `create_guest_realloc()` and `create_lift_lower_context()` +are **no longer generated**. They are now available directly from `` +in the `cmcpp` namespace. + +**Usage:** +```cpp +#include "sample_wamr.hpp" // Automatically includes sample.hpp +``` + +--- + +### 3. `_wamr.cpp` - WAMR Symbol Arrays and Implementations + +**Purpose:** Implements the WAMR bindings and helper functions. + +**Contents:** +- NativeSymbol arrays for each import (standalone functions or interfaces) + - Example: `dbglog_symbols[]` for standalone imports + - Example: `interface_name_symbols[]` for interface imports +- Implementation of all functions declared in `_wamr.hpp`: + - `get_import_registrations()` + - `register_all_imports()` + - `unregister_all_imports()` +- Constants in `wasm_utils` namespace + +**Example:** +```cpp +#include "sample_wamr.hpp" + +// Import interface: dbglog (standalone function) +// Register with: wasm_runtime_register_natives_raw("$root", dbglog_symbols, 1) +NativeSymbol dbglog_symbols[] = { + host_function("dbglog", host::dbglog), +}; + +std::vector get_import_registrations() { + return { + {"$root", dbglog_symbols, 1}, + }; +} + +int register_all_imports() { + int count = 0; + for (const auto& reg : get_import_registrations()) { + if (!wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count)) { + return -1; // Registration failed + } + count += reg.count; + } + return count; +} + +void unregister_all_imports() { + for (const auto& reg : get_import_registrations()) { + wasm_runtime_unregister_natives(reg.module_name, reg.symbols); + } +} + +namespace wasm_utils { + const uint32_t DEFAULT_STACK_SIZE = 8192; + const uint32_t DEFAULT_HEAP_SIZE = 8192; +} +``` + +**Build Integration:** +This file must be compiled and linked with your host application: +```cmake +add_executable(my_host + main.cpp + host_impl.cpp # Your implementation of host::* + ${GENERATED_DIR}/sample_wamr.cpp # Generated bindings +) +``` + +--- + +## File Dependencies + +``` +_wamr.cpp + ↓ includes +_wamr.hpp + ↓ includes +.hpp +``` + +Your host code only needs to include `_wamr.hpp` to get access to everything. + +--- + +## Typical Project Structure + +``` +my-project/ +├── CMakeLists.txt +├── main.cpp # Your main application +├── host_impl.cpp # Your implementations of host::* +└── generated/ # Generated by WIT codegen + ├── sample.hpp # Interface declarations + ├── sample_wamr.hpp # Helper function declarations + └── sample_wamr.cpp # Symbol arrays & implementations +``` + +--- + +## What You Need to Implement + +The generator creates the scaffolding, but you must provide: + +1. **Host Function Implementations** (`host_impl.cpp`) + - Implement all functions declared in the `host` namespace + - Example: `host::dbglog()` + +2. **Main Application Logic** (`main.cpp`) + - Initialize WAMR runtime + - Call `register_all_imports()` during initialization + - Load and instantiate your WASM module + - Use `cmcpp::create_lift_lower_context()` from `` to create context + - Call guest functions using `guest_function()` template + - Call `unregister_all_imports()` during cleanup + +--- + +## Example: Complete Host Application + +**host_impl.cpp** - Implement host functions: +```cpp +#include "sample.hpp" +#include + +namespace host { + void dbglog(cmcpp::string_t msg) { + std::cout << "[GUEST LOG] " << msg << std::endl; + } +} +``` + +**main.cpp** - Minimal WAMR integration: +```cpp +#include "sample_wamr.hpp" +#include +#include + +int main(int argc, char** argv) { + // Initialize WAMR + wasm_runtime_init(); + + // Load WASM file + std::ifstream file("sample.wasm", std::ios::binary | std::ios::ate); + if (!file) { + std::cerr << "Failed to open WASM file" << std::endl; + return 1; + } + + size_t size = file.tellg(); + file.seekg(0); + std::vector buffer(size); + file.read(reinterpret_cast(buffer.data()), size); + + // Register all host functions (imports) + int count = register_all_imports(); + if (count < 0) { + std::cerr << "Failed to register imports" << std::endl; + return 1; + } + std::cout << "Registered " << count << " host functions" << std::endl; + + // Load and instantiate module + char error_buf[128]; + auto module = wasm_runtime_load( + buffer.data(), buffer.size(), + error_buf, sizeof(error_buf) + ); + if (!module) { + std::cerr << "Failed to load module: " << error_buf << std::endl; + return 1; + } + + auto module_inst = wasm_runtime_instantiate( + module, + wasm_utils::DEFAULT_STACK_SIZE, + wasm_utils::DEFAULT_HEAP_SIZE, + error_buf, sizeof(error_buf) + ); + if (!module_inst) { + std::cerr << "Failed to instantiate: " << error_buf << std::endl; + wasm_runtime_unload(module); + return 1; + } + + // Create execution environment and context + auto exec_env = wasm_runtime_create_exec_env( + module_inst, + wasm_utils::DEFAULT_STACK_SIZE + ); + + // Create lift/lower context using helper from + auto ctx = cmcpp::create_lift_lower_context(module_inst, exec_env); + + // Call guest functions (exports) + auto bool_and = guest_function( + module_inst, exec_env, ctx, + "bool-and" // Function name from WIT + ); + + std::cout << "Guest bool-and(true, false) = " + << std::boolalpha << bool_and(true, false) << std::endl; + + // Cleanup + wasm_runtime_destroy_exec_env(exec_env); + unregister_all_imports(); + wasm_runtime_deinstantiate(module_inst); + wasm_runtime_unload(module); + wasm_runtime_destroy(); + + return 0; +} +``` + +**CMakeLists.txt**: +```cmake +add_executable(my_host + main.cpp + host_impl.cpp + ${CMAKE_CURRENT_BINARY_DIR}/generated/sample_wamr.cpp +) + +target_include_directories(my_host PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/generated +) + +target_link_libraries(my_host PRIVATE + cmcpp + vmlib # WAMR library +) +``` + +--- + +## Benefits of This Structure + +1. **Clean Separation**: Interface declarations separate from implementation +2. **Header-only for Types**: Type definitions can be used without linking +3. **Standard C++ Practice**: Follows conventional `.hpp`/`.cpp` pattern +4. **No ODR Violations**: Functions defined in `.cpp` only, declarations in `.hpp` +5. **Easy Integration**: Single `#include "sample_wamr.hpp"` gets everything +6. **Documentation**: Function declarations in header serve as API documentation +7. **Compile-time Efficiency**: No inline bloat in header files + +--- + +## Advanced: Using Only Parts of the Generated Code + +If you want finer control, you can use the files individually: + +**Option 1: Just the interfaces (no WAMR)** +```cpp +#include "sample.hpp" // Get type definitions only +``` + +**Option 2: Manual WAMR setup** +```cpp +#include "sample.hpp" +#include + +// Don't include sample_wamr.hpp +// Manually register each interface as needed +``` + +**Option 3: Full generated helpers (recommended)** +```cpp +#include "sample_wamr.hpp" // Everything you need +``` diff --git a/docs/generated-wamr-helpers.md b/docs/generated-wamr-helpers.md new file mode 100644 index 0000000..95a990c --- /dev/null +++ b/docs/generated-wamr-helpers.md @@ -0,0 +1,281 @@ +# Generated WAMR Helper Functions + +The WIT code generator produces three files to simplify WAMR host application development: + +1. **`.hpp`** - Component Model interface declarations (host and guest functions) +2. **`_wamr.hpp`** - WAMR helper function declarations +3. **`_wamr.cpp`** - WAMR symbol arrays and helper function implementations + +Where `` is the output prefix you provide to `wit-codegen`. + +## Usage + +Simply include the WAMR header to access all helper functions: + +```cpp +#include "_wamr.hpp" +``` + +This automatically includes: +- `` - WAMR runtime API +- `` - Component Model C++ types +- `".hpp"` - Your generated interface declarations + +## Generated Helper Functions + +### 1. Registration Helpers + +#### `register_all_imports()` +Registers all imported interfaces at once. Returns the number of functions registered, or -1 on failure. + +**Before:** +```cpp +for (const auto& reg : get_import_registrations()) { + bool success = wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count); + if (!success) { + std::cerr << "Failed to register natives for: " << reg.module_name << std::endl; + return 1; + } +} +``` + +**After:** +```cpp +int count = register_all_imports(); +if (count < 0) { + std::cerr << "Failed to register native imports" << std::endl; + return 1; +} +std::cout << "Registered " << count << " host functions" << std::endl; +``` + +#### `unregister_all_imports()` +Unregisters all imported interfaces at cleanup time. + +**Before:** +```cpp +for (const auto& reg : get_import_registrations()) { + wasm_runtime_unregister_natives(reg.module_name, reg.symbols); +} +``` + +**After:** +```cpp +unregister_all_imports(); +``` + +### 2. WASM Runtime Constants + +The generated WAMR bindings provide default configuration constants: + +```cpp +wasm_utils::DEFAULT_STACK_SIZE // 8192 +wasm_utils::DEFAULT_HEAP_SIZE // 8192 +``` + +### 3. Context Creation Functions (from ``) + +The following helper functions are available in the `cmcpp` namespace from `` (not generated, but part of the cmcpp library): + +#### `cmcpp::create_guest_realloc()` +Creates a `GuestRealloc` function from WAMR execution environment. + +**Usage:** +```cpp +wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc"); +cmcpp::GuestRealloc realloc = cmcpp::create_guest_realloc(exec_env, cabi_realloc); +``` + +#### `cmcpp::create_lift_lower_context()` +Creates a complete `LiftLowerContext` ready for use with `guest_function<>()`. + +**Before:** +```cpp +wasm_memory_inst_t memory = wasm_runtime_lookup_memory(module_inst, "memory"); +if (!memory) { + std::cerr << "Failed to lookup memory instance" << std::endl; + return 1; +} + +uint8_t* mem_start_addr = (uint8_t*)wasm_memory_get_base_address(memory); +uint8_t* mem_end_addr = NULL; +wasm_runtime_get_native_addr_range(module_inst, mem_start_addr, NULL, &mem_end_addr); + +GuestRealloc realloc = [exec_env, cabi_realloc](int original_ptr, int original_size, int alignment, int new_size) -> int { + uint32_t argv[4]; + argv[0] = original_ptr; + argv[1] = original_size; + argv[2] = alignment; + argv[3] = new_size; + wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv); + return argv[0]; +}; + +LiftLowerOptions opts(Encoding::Utf8, std::span(mem_start_addr, mem_end_addr - mem_start_addr), realloc); +LiftLowerContext ctx(trap, convert, opts); +``` + +**After:** +```cpp +wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc"); +LiftLowerContext ctx = cmcpp::create_lift_lower_context(module_inst, exec_env, cabi_realloc); +``` + +**Parameters:** +- `module_inst`: WAMR module instance +- `exec_env`: WAMR execution environment +- `cabi_realloc`: The `cabi_realloc` function from the WASM module +- `encoding`: String encoding (default: `Encoding::Utf8`) + +**Throws:** `std::runtime_error` if memory lookup fails + +## Example: Simplified main() Function + +Here's how a typical `main()` function looks using the generated helpers: + +```cpp +#include +#include "test_wamr.hpp" // Includes test.hpp automatically +#include +#include +#include + +int main(int argc, char** argv) { + // Initialize WAMR + wasm_runtime_init(); + + // Read WASM file (you need to implement this based on your application) + std::ifstream file("path/to/your.wasm", std::ios::binary | std::ios::ate); + if (!file) { + std::cerr << "Failed to open WASM file" << std::endl; + return 1; + } + + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + if (!file.read(buffer.data(), size)) { + std::cerr << "Failed to read WASM file" << std::endl; + return 1; + } + + // Register all host functions + int count = register_all_imports(); + if (count < 0) { + std::cerr << "Failed to register imports" << std::endl; + return 1; + } + std::cout << "Registered " << count << " host functions" << std::endl; + + // Load and instantiate module + char error_buf[128]; + wasm_module_t module = wasm_runtime_load( + (uint8_t*)buffer.data(), size, error_buf, sizeof(error_buf)); + if (!module) { + std::cerr << "Failed to load module: " << error_buf << std::endl; + return 1; + } + + wasm_module_inst_t module_inst = wasm_runtime_instantiate( + module, wasm_utils::DEFAULT_STACK_SIZE, wasm_utils::DEFAULT_HEAP_SIZE, + error_buf, sizeof(error_buf)); + if (!module_inst) { + std::cerr << "Failed to instantiate: " << error_buf << std::endl; + wasm_runtime_unload(module); + return 1; + } + + // Create execution environment and context + wasm_exec_env_t exec_env = wasm_runtime_create_exec_env( + module_inst, wasm_utils::DEFAULT_STACK_SIZE); + + wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function( + module_inst, "cabi_realloc"); + cmcpp::LiftLowerContext ctx = cmcpp::create_lift_lower_context( + module_inst, exec_env, cabi_realloc); + + // Call guest functions using the generated typedefs + auto my_func = guest_function( + module_inst, exec_env, ctx, "example:sample/namespace#my-func"); + auto result = my_func(/* args */); + + // Cleanup + wasm_runtime_destroy_exec_env(exec_env); + unregister_all_imports(); + wasm_runtime_deinstantiate(module_inst); + wasm_runtime_unload(module); + wasm_runtime_destroy(); + + return 0; +} +``` + +## Benefits + +1. **Reduced Boilerplate**: Registration and cleanup happen in two function calls rather than manually iterating arrays +2. **Automatic Constants**: Standard stack and heap sizes are provided as named constants +3. **Less Error-Prone**: No manual copying of function names or managing registration state +4. **Type Safety**: Generated function typedefs in the `guest::` namespace provide compile-time type checking +5. **Separation of Concerns**: Host interface definitions in `host::` namespace, guest signatures in `guest::` namespace +6. **Easier Maintenance**: Changes to WIT automatically regenerate all registration code + +## Migration Guide + +If you have existing code without generated helpers: + +1. **Replace manual registration loops** with a single call to `register_all_imports()` +2. **Replace manual unregistration** with `unregister_all_imports()` +3. **Use generated constants** `wasm_utils::DEFAULT_STACK_SIZE` and `wasm_utils::DEFAULT_HEAP_SIZE` for consistency +4. **Update namespace references**: Host functions are in `host::` namespace, guest type signatures are in `guest::` namespace +5. **Use helper functions from wamr.hpp**: `cmcpp::create_guest_realloc()` and `cmcpp::create_lift_lower_context()` for context setup + +### Example Transformation + +**Before (manual registration):** +```cpp +// Manual array iteration +for (int i = 0; test_wamr_imports[i].symbol; i++) { + if (!wasm_runtime_register_natives( + "example:test/namespace", + &test_wamr_imports[i], 1)) { + // handle error + } +} +``` + +**After (generated helpers):** +```cpp +// Single call +int count = register_all_imports(); +if (count < 0) { + // handle error +} +``` + +## Note on Root-Level Functions + +Functions defined at the world level (not inside an interface) are included in the generated registration code if they have import statements. They appear in the `host::` namespace just like interface functions: + +```cpp +// In your WIT file: +// package example:test; +// +// world test-world { +// import root-func: func(x: u32) -> u32; +// +// interface my-interface { +// import interface-func: func(s: string); +// } +// } + +// Generated in test.hpp: +namespace host { + // Root-level function + cmcpp::u32_t root_func(cmcpp::u32_t x); + + // Interface function + void interface_func(cmcpp::string_t s); +} +``` + +Both types of imports are automatically registered by `register_all_imports()`. The namespace structure in the generated code is `host::` for all imports and `guest::` for all exports, regardless of whether they are at root level or in an interface. diff --git a/docs/wit-codegen.md b/docs/wit-codegen.md new file mode 100644 index 0000000..753e392 --- /dev/null +++ b/docs/wit-codegen.md @@ -0,0 +1,239 @@ +# WIT Code Generator - ANTLR Grammar Integration + +## Overview +The `wit-codegen` tool now uses the ANTLR4 grammar parser to accurately parse WIT (WebAssembly Interface Types) files and generate C++ host function bindings using the cmcpp library. + +## Architecture + +### Components +1. **ANTLR Grammar Parser** (`grammar/Wit.g4`) + - Full WIT specification grammar + - Generates C++ lexer/parser code via `generate-grammar` CMake target + +2. **WitInterfaceVisitor** (custom visitor) + - Extends `WitBaseVisitor` from ANTLR + - Walks the parse tree to extract interfaces and functions + - Handles package declarations, interface items, and function items + +3. **TypeMapper** + - Maps WIT types to cmcpp types + - Supports: primitives, lists, options, results, tuples + +4. **CodeGenerator** + - Generates three files: + - `.hpp` - Function declarations with cmcpp types + - `.cpp` - Implementation stubs + - `_bindings.cpp` - WAMR NativeSymbol registration + +## Type Mappings + +| WIT Type | cmcpp Type | +|----------|------------| +| `bool` | `cmcpp::bool_t` | +| `u8`, `u16`, `u32`, `u64` | `cmcpp::u8_t`, `cmcpp::u16_t`, etc. | +| `s8`, `s16`, `s32`, `s64` | `cmcpp::s8_t`, `cmcpp::s16_t`, etc. | +| `f32`, `f64` | `cmcpp::f32_t`, `cmcpp::f64_t` | +| `string` | `cmcpp::string_t` | +| `list` | `cmcpp::list_t` | +| `option` | `cmcpp::option_t` | +| `result` | `cmcpp::result_t` | +| `tuple` | `cmcpp::tuple_t` | + +## Usage + +```bash +# Build the tool +cmake --build build --target wit-codegen + +# Generate C++ bindings from WIT file +./build/tools/wit-parser/wit-codegen + +# Example +./build/tools/wit-parser/wit-codegen test-example.wit host_functions +``` + +This generates: +- `host_functions.hpp` - Header with function declarations +- `host_functions.cpp` - Implementation stubs +- `host_functions_bindings.cpp` - WAMR runtime bindings + +## Example + +### Input WIT file (`test-example.wit`): +```wit +package example:hello@1.0.0; + +interface logging { + log: func(message: string); + get-level: func() -> u32; + process-items: func(items: list) -> list; +} +``` + +### Generated Header (`host_functions.hpp`): +```cpp +#pragma once +#include + +// Generated host function declarations from WIT +// Functions in 'host' namespace are imported by the guest (host implements them) + +namespace host { + +namespace logging { + // Host function declaration + void log(cmcpp::string_t message); + + // Guest function signature typedef for use with guest_function() + using log_t = void(cmcpp::string_t); + + // Host function declaration + uint32_t get_level(); + + // Guest function signature typedef for use with guest_function() + using get_level_t = uint32_t(); + + // Host function declaration + cmcpp::list_t process_items(cmcpp::list_t items); + + // Guest function signature typedef for use with guest_function() + using process_items_t = cmcpp::list_t(cmcpp::list_t); +} + +} // namespace host +``` + +The generated header includes: +1. **Namespace organization** - All imported interfaces are wrapped in the `host` namespace to prevent name clashes with guest exports +2. **Host function declarations** - Functions to be implemented on the host side +3. **Guest function signature typedefs** - Type aliases ending in `_t` that can be used with `guest_function()` template to call guest-exported functions + +Example usage of guest function typedef: +```cpp +#include "host_functions.hpp" +#include + +// In your host code, call a guest function: +auto guest_log = guest_function( + module_inst, exec_env, liftLowerContext, + "example:hello/logging#log" +); +guest_log("Hello from guest!"); +``` + +### Generated Implementation (`host_functions.cpp`): +```cpp +#include "host_functions.hpp" + +namespace imports { + +namespace logging { + void log(cmcpp::string_t message) { + // TODO: Implement log + } +} + +} // namespace imports +``` + +### Generated Bindings (`host_functions_bindings.cpp`): +```cpp +#include +#include + +extern "C" { + extern void logging::log_wrapper(); +} + +std::vector get_native_symbols() { + return { + {"logging#log", (void*)logging::log_wrapper, ""}, + }; +} +``` + +## Grammar Visitor Implementation + +The visitor follows the ANTLR parse tree structure: + +```cpp +class WitInterfaceVisitor : public WitBaseVisitor { + // visitPackageDecl - Extract package information + // visitInterfaceItem - Start new interface context + // visitFuncItem - Extract function name, parameters, and results +}; +``` + +Key grammar rules used: +- `funcItem: id ':' funcType ';'` +- `funcType: 'func' paramList resultList` +- `paramList: '(' namedTypeList ')'` +- `resultList: /* epsilon */ | '->' ty` +- `namedType: id ':' ty` + +## Integration with Component Model C++ + +The generated code uses the cmcpp header-only library which implements the WebAssembly Component Model canonical ABI. This ensures type-safe marshaling between C++ and WebAssembly components. + +## Using Generated Guest Function Typedefs + +The generated header file includes typedef aliases for each function that can be used with the `guest_function()` template from `wamr.hpp`. This provides type-safe calling of guest-exported functions. + +### Example: Calling Guest Functions + +```cpp +#include "host_functions.hpp" +#include +#include + +int main() { + // ... WAMR initialization code ... + + // Create a lift/lower context + cmcpp::LiftLowerContext liftLowerContext(/* ... */); + + // Call a guest function using the generated typedef + auto guest_log = guest_function( + module_inst, exec_env, liftLowerContext, + "example:hello/logging#log" + ); + guest_log("Hello from host!"); + + // Call a guest function that returns a value + auto guest_get_level = guest_function( + module_inst, exec_env, liftLowerContext, + "example:hello/logging#get-level" + ); + uint32_t level = guest_get_level(); + std::cout << "Guest log level: " << level << std::endl; + + // Call a guest function with complex types + auto guest_process = guest_function( + module_inst, exec_env, liftLowerContext, + "example:hello/logging#process-items" + ); + cmcpp::list_t input = {"foo", "bar", "baz"}; + auto result = guest_process(input); + for (auto val : result) { + std::cout << "Result: " << val << std::endl; + } + + return 0; +} +``` + +### Benefits of Generated Typedefs + +1. **Type Safety** - The compiler ensures you're using the correct parameter and return types +2. **Code Clarity** - The typedef name clearly indicates which guest function it corresponds to +3. **Autocomplete** - IDEs can provide better autocomplete suggestions +4. **Maintainability** - Changes to the WIT file automatically update the typedefs + +## Build Dependencies + +- ANTLR4 runtime (from vcpkg) - Linked directly by wit-codegen +- ANTLR4 code generator (antlr-4.13.2-complete.jar) - Used by generate-grammar CMake target +- cmcpp headers (Component Model C++ ABI) +- wamr.hpp (for WAMR runtime integration) + +The `wit-codegen` tool includes the ANTLR-generated source files directly and links to the ANTLR4 runtime library. The grammar code is generated by the `generate-grammar` CMake target. diff --git a/grammar/CMakeLists.txt b/grammar/CMakeLists.txt new file mode 100644 index 0000000..3d7421d --- /dev/null +++ b/grammar/CMakeLists.txt @@ -0,0 +1,110 @@ +# ANTLR Grammar Code Generation +cmake_minimum_required(VERSION 3.10) + +# Find ANTLR4 package from vcpkg (for C++ runtime, optional) +find_package(antlr4-runtime CONFIG QUIET) + +# Find Java (ANTLR4 generator requires Java runtime) +find_package(Java COMPONENTS Runtime) +if(NOT Java_FOUND) + message(WARNING "Java runtime not found. ANTLR4 requires Java to generate code.") + return() +endif() + +# Grammar and output directories +set(ANTLR_GRAMMAR_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE PATH "Directory containing grammar files") +set(ANTLR_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/grammar" CACHE PATH "Output directory for generated code") +set(ANTLR_VERSION "4.13.2" CACHE STRING "ANTLR version to use") +set(ANTLR_JAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../antlr-${ANTLR_VERSION}-complete.jar" CACHE FILEPATH "Path to ANTLR jar file") + +# Find all .g4 grammar files +file(GLOB GRAMMAR_FILES "${ANTLR_GRAMMAR_DIR}/*.g4") + +if(NOT GRAMMAR_FILES) + message(WARNING "No .g4 grammar files found in ${ANTLR_GRAMMAR_DIR}") + return() +endif() + +# Create output directory +file(MAKE_DIRECTORY "${ANTLR_OUTPUT_DIR}") + +# Download ANTLR jar if it doesn't exist +if(NOT EXISTS "${ANTLR_JAR_PATH}") + set(ANTLR_URL "https://www.antlr.org/download/antlr-${ANTLR_VERSION}-complete.jar") + message(STATUS "Downloading ANTLR ${ANTLR_VERSION} from ${ANTLR_URL}...") + + file(DOWNLOAD "${ANTLR_URL}" "${ANTLR_JAR_PATH}" + STATUS download_status + TIMEOUT 60 + SHOW_PROGRESS + ) + + list(GET download_status 0 status_code) + list(GET download_status 1 status_string) + + if(NOT status_code EQUAL 0) + message(WARNING "Failed to download ANTLR jar: ${status_string}") + message(WARNING "Please manually download from ${ANTLR_URL} to ${ANTLR_JAR_PATH}") + return() + endif() + + message(STATUS "Successfully downloaded ANTLR jar to ${ANTLR_JAR_PATH}") +endif() + +# Verify the jar file exists +if(NOT EXISTS "${ANTLR_JAR_PATH}") + message(WARNING "ANTLR jar not found at ${ANTLR_JAR_PATH}") + message(WARNING "Please download from https://www.antlr.org/download/antlr-${ANTLR_VERSION}-complete.jar") + return() +endif() + +# List of generated files (for dependency tracking) +set(GENERATED_FILES) +foreach(grammar_file ${GRAMMAR_FILES}) + get_filename_component(grammar_name ${grammar_file} NAME_WE) + list(APPEND GENERATED_FILES + "${ANTLR_OUTPUT_DIR}/${grammar_name}Lexer.h" + "${ANTLR_OUTPUT_DIR}/${grammar_name}Lexer.cpp" + "${ANTLR_OUTPUT_DIR}/${grammar_name}Parser.h" + "${ANTLR_OUTPUT_DIR}/${grammar_name}Parser.cpp" + "${ANTLR_OUTPUT_DIR}/${grammar_name}Visitor.h" + "${ANTLR_OUTPUT_DIR}/${grammar_name}Visitor.cpp" + "${ANTLR_OUTPUT_DIR}/${grammar_name}BaseVisitor.h" + "${ANTLR_OUTPUT_DIR}/${grammar_name}BaseVisitor.cpp" + ) +endforeach() + +# Create a custom command for grammar generation +add_custom_command( + OUTPUT ${GENERATED_FILES} + COMMAND ${Java_JAVA_EXECUTABLE} + -jar "${ANTLR_JAR_PATH}" + -Dlanguage=Cpp + -o "${ANTLR_OUTPUT_DIR}" + -visitor + -no-listener + -Xexact-output-dir + ${GRAMMAR_FILES} + WORKING_DIRECTORY "${ANTLR_GRAMMAR_DIR}" + DEPENDS ${GRAMMAR_FILES} + COMMENT "Generating ANTLR C++ grammar code from ${GRAMMAR_FILES}" + VERBATIM +) + +# Create a custom target that depends on the generated files +add_custom_target(generate-grammar + DEPENDS ${GENERATED_FILES} +) + + +# Print information +message(STATUS "ANTLR Grammar Configuration:") +message(STATUS " Java executable: ${Java_JAVA_EXECUTABLE}") +message(STATUS " ANTLR version: ${ANTLR_VERSION}") +message(STATUS " ANTLR jar: ${ANTLR_JAR_PATH}") +message(STATUS " Grammar files: ${GRAMMAR_FILES}") +message(STATUS " Output directory: ${ANTLR_OUTPUT_DIR}") +message(STATUS " Generated files: ${GENERATED_FILES}") +message(STATUS " Target: generate-grammar (run with 'cmake --build . --target generate-grammar')") +message(STATUS " Note: Consumers must link to antlr4_shared and add ${ANTLR_OUTPUT_DIR} to includes") + diff --git a/grammar/README.md b/grammar/README.md new file mode 100644 index 0000000..abfb0f0 --- /dev/null +++ b/grammar/README.md @@ -0,0 +1,360 @@ +# ANTLR Grammar Code Generation + +This directory contains the ANTLR grammar files for parsing WebAssembly Interface Types (WIT) format. + +## Overview + +The grammar is based on the official [WebAssembly Component Model WIT specification](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md). + +## Prerequisites + +- **Java Runtime**: ANTLR requires Java to run the code generator +- **CMake**: Used to automate the build process +- **ANTLR4**: Automatically downloaded via CMake (version 4.13.2) + +## Building + +### Enable Grammar Generation + +The grammar generation is controlled by the `BUILD_GRAMMAR` CMake option (enabled by default): + +```bash +cd build +cmake -DBUILD_GRAMMAR=ON .. +``` + +This will: +1. Check if Java is available +2. Automatically download the ANTLR jar file if not present +3. Configure the `generate-grammar` target + +### Generate Code + +To generate TypeScript code from the grammar files: + +```bash +cmake --build . --target generate-grammar +``` + +Or using make: + +```bash +make generate-grammar +``` + +### Output Location + +The generated C++ files are placed in the build directory (not shipped with the library): +``` +build/grammar/grammar/ +├── WitLexer.h +├── WitLexer.cpp +├── WitParser.h +├── WitParser.cpp +├── WitVisitor.h +├── WitVisitor.cpp +├── WitBaseVisitor.h +└── WitBaseVisitor.cpp +``` + +## Testing + +### Grammar Test Suite + +A comprehensive test suite validates the grammar against all official WIT test files from the [wit-bindgen repository](https://github.com/bytecodealliance/wit-bindgen). + +The test suite is located in the `test/` directory and provides: +- Validation against 79 official WIT files from wit-bindgen +- Complete coverage of all WIT features +- Automated testing via CTest + +For detailed testing documentation, see [test/README.md](../test/README.md#2-wit-grammar-tests-test_grammarcpp). + +#### Quick Test + +```bash +# Build and run tests +cmake -B build -DBUILD_GRAMMAR=ON +cmake --build build --target test-wit-grammar +ctest -R wit-grammar-test --verbose + +# Or run directly +./build/test/test-wit-grammar --verbose + +# Or use VS Code launch configurations (see .vscode/launch.json) +``` + +#### Test Coverage + +The test suite validates the grammar against 79 WIT files including: +- Basic types (integers, floats, strings, chars) +- Complex types (records, variants, enums, flags) +- Resources (with constructors, methods, static functions) +- Functions (sync and async) +- Futures and streams +- Interfaces and worlds +- Package declarations with versions +- Use statements and imports/exports +- Feature gates (@unstable, @since, @deprecated) +- Error contexts +- Real-world WASI specifications (wasi-clocks, wasi-filesystem, wasi-http, wasi-io) + +#### Test Output + +The test executable reports: +- Total number of WIT files found +- Number of successfully parsed files +- Number of failed files with detailed error messages +- Exit code 0 for success, 1 for failures + +Example output: +``` +WIT Grammar Test Suite +====================== +Test directory: ../ref/wit-bindgen/tests/codegen + +Found 79 WIT files + +✓ Successfully parsed: allow-unused.wit +✓ Successfully parsed: async-trait-function.wit +✓ Successfully parsed: char.wit +... + +====================== +Test Results: + Total files: 79 + Successful: 79 + Failed: 0 + +✓ All tests passed! +``` + +### Command Line Options + +The test executable supports several options: + +```bash +# Show help +./build/test/test-wit-grammar --help + +# Use verbose output (shows each file as it's parsed) +./build/test/test-wit-grammar --verbose + +# Specify a different test directory +./build/test/test-wit-grammar --directory /path/to/wit/files +``` + +## Manual Generation + +You can also generate the code manually using the downloaded jar: + +```bash +cd grammar +java -jar ../antlr-4.13.2-complete.jar \ + -Dlanguage=Cpp \ + -o ../build/grammar/grammar \ + -visitor \ + -no-listener \ + -Xexact-output-dir \ + ./*.g4 +``` + +Note: The double `grammar` in the path is intentional - first is the CMake subdirectory, second is the output folder. + +## Using the Generated Files + +The generated C++ source files can be used by including them directly in your tool's build. Consumers must: +1. Link to the `antlr4_shared` (or `antlr4_static`) library from vcpkg +2. Add the ANTLR4 runtime include directory and grammar output directory to their include paths +3. Compile the generated `.cpp` files as part of their target + +See `tools/wit-codegen/CMakeLists.txt` and `test/CMakeLists.txt` for complete examples. + +## Grammar Files + +- `Wit.g4`: Main grammar file for WebAssembly Interface Types + +## Configuration + +The following CMake variables can be customized: + +- `ANTLR_VERSION`: ANTLR version to use (default: 4.13.2) +- `ANTLR_JAR_PATH`: Path to ANTLR jar file (default: `../antlr-${ANTLR_VERSION}-complete.jar`) +- `ANTLR_GRAMMAR_DIR`: Directory containing .g4 files (default: current directory) +- `ANTLR_OUTPUT_DIR`: Output directory for generated code (default: `${CMAKE_CURRENT_BINARY_DIR}/grammar`) + +Example: + +```bash +cmake -DBUILD_GRAMMAR=ON \ + -DANTLR_VERSION=4.13.2 \ + -DANTLR_OUTPUT_DIR=/custom/path \ + .. +``` + +## Example: Using the Grammar in Your Tool + +### CMakeLists.txt Setup + +Tools that need to parse WIT files should link directly to the ANTLR4 runtime and include the generated source files: + +```cmake +# Find ANTLR4 runtime from vcpkg +find_package(antlr4-runtime CONFIG REQUIRED) + +# Determine which library to use (shared or static) +if(TARGET antlr4_shared) + set(ANTLR4_LIBRARY antlr4_shared) +elseif(TARGET antlr4_static) + set(ANTLR4_LIBRARY antlr4_static) +endif() + +# Get the grammar output directory from the grammar target +get_target_property(ANTLR_OUTPUT_DIR generate-grammar BINARY_DIR) +set(ANTLR_OUTPUT_DIR "${ANTLR_OUTPUT_DIR}/grammar") + +# List generated source files explicitly +set(ANTLR_GENERATED_SOURCES + ${ANTLR_OUTPUT_DIR}/WitLexer.cpp + ${ANTLR_OUTPUT_DIR}/WitParser.cpp + ${ANTLR_OUTPUT_DIR}/WitVisitor.cpp + ${ANTLR_OUTPUT_DIR}/WitBaseVisitor.cpp +) + +# Create your tool executable +add_executable(my_wit_tool + main.cpp + ${ANTLR_GENERATED_SOURCES} +) + +# Link to ANTLR4 runtime +target_link_libraries(my_wit_tool PRIVATE + ${ANTLR4_LIBRARY} +) + +# Add include directories +target_include_directories(my_wit_tool PRIVATE + ${ANTLR_OUTPUT_DIR} # For grammar headers + ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/antlr4-runtime +) + +# Ensure grammar is generated before building +add_dependencies(my_wit_tool generate-grammar) +``` + +### Example C++ Code + +```cpp +#include +#include "grammar/WitLexer.h" +#include "grammar/WitParser.h" +#include "grammar/WitBaseVisitor.h" +#include +#include + +using namespace antlr4; + +// Custom visitor to process the parse tree +class MyWitVisitor : public WitBaseVisitor { +public: + std::any visitPackageDecl(WitParser::PackageDeclContext *ctx) override { + // Process package declaration + std::cout << "Package: " << ctx->getText() << std::endl; + return visitChildren(ctx); + } +}; + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + // Read WIT file + std::ifstream stream(argv[1]); + if (!stream) { + std::cerr << "Failed to open file: " << argv[1] << std::endl; + return 1; + } + + ANTLRInputStream input(stream); + + // Create lexer and parser + WitLexer lexer(&input); + CommonTokenStream tokens(&lexer); + WitParser parser(&tokens); + + // Parse and visit + tree::ParseTree* tree = parser.witFile(); + + // Check for errors + if (parser.getNumberOfSyntaxErrors() > 0) { + std::cerr << "Parse errors encountered" << std::endl; + return 1; + } + + // Visit the parse tree + MyWitVisitor visitor; + visitor.visit(tree); + + return 0; +} +``` + +For a complete working example, see `tools/wit-codegen/` which uses this exact pattern. + +## vcpkg Integration + +The project uses vcpkg to manage the ANTLR4 C++ runtime library. The ANTLR jar file for code generation is automatically downloaded during CMake configuration. + +To manually install ANTLR4 runtime via vcpkg: + +```bash +# Already included in vcpkg.json +./vcpkg/vcpkg install antlr4 +``` + +Note: The vcpkg ANTLR4 package provides the C++ runtime library, not the Java-based code generator. The jar file is downloaded separately by CMake. + +## Troubleshooting + +### Java Not Found + +If CMake reports that Java is not found: + +```bash +# Install Java (Ubuntu/Debian) +sudo apt-get install default-jre + +# Or using SDKMAN (recommended for managing Java versions) +curl -s "https://get.sdkman.io" | bash +sdk install java +``` + +### ANTLR Download Failed + +If the automatic download fails, manually download the jar: + +```bash +curl -L -o antlr-4.13.2-complete.jar \ + https://www.antlr.org/download/antlr-4.13.2-complete.jar +``` + +Place it in the project root directory. + +### Grammar Changes Not Reflected + +The build system tracks dependencies on .g4 files. If changes aren't being picked up: + +```bash +# Clean and rebuild +cmake --build . --target clean +cmake --build . --target generate-grammar +``` + +## References + +- [ANTLR 4 Documentation](https://www.antlr.org/) +- [ANTLR 4 Download](https://www.antlr.org/download.html) +- [WebAssembly Component Model](https://github.com/WebAssembly/component-model) +- [WIT Specification](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) diff --git a/grammar/Wit.g4 b/grammar/Wit.g4 new file mode 100644 index 0000000..fb36e10 --- /dev/null +++ b/grammar/Wit.g4 @@ -0,0 +1,429 @@ +// https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md + +grammar Wit; + +witFile: (packageDecl ';')? witFileItems* EOF + ; + +witFileItems: packageItems | nestedPackageDefinition + ; + +nestedPackageDefinition + : packageDecl '{' packageItems* '}' + ; + +packageDecl + : 'package' (id ':')+ id ('/' id)* ('@' validSemver)? + ; + +packageItems: toplevelUseItem | interfaceItem | worldItem + ; + +// Item: toplevel-use --- +toplevelUseItem: 'use' usePath ('as' id)? ';' + ; + +usePath + : id + | id ':' id '/' id ('@' validSemver)? + | ( id ':')+ id ( '/' id)+ ('@' validSemver)? + ; + +// Item: World --- +worldItem: gate 'world' id '{' worldItems* '}' + ; + +worldItems: gate worldDefinition + ; + +worldDefinition + : exportItem + | importItem + | useItem + | typedefItem + | includeItem + ; + +exportItem + : 'export' id ':' externType + | 'export' usePath ';' + ; + +importItem + : 'import' id ':' externType + | 'import' usePath ';' + ; + +externType + : funcType ';' + | 'interface' '{' interfaceItems* '}' + ; + +// Item: include --- +includeItem + : 'include' usePath ';' + | 'include' usePath 'with' '{' includeNamesList '}' + ; + +includeNamesList + : includeNamesItem + | includeNamesList ',' includeNamesItem + ; + +includeNamesItem: id 'as' id + ; + +// Item: interface --- +interfaceItem + : gate 'interface' id '{' interfaceItems* '}' + ; + +interfaceItems: gate interfaceDefinition + ; + +interfaceDefinition: typedefItem | useItem | funcItem + ; + +typedefItem + : resourceItem + | variantItems + | recordItem + | flagsItems + | enumItems + | typeItem + ; + +funcItem: id ':' funcType ';' + ; + +funcType: 'async'? 'func' paramList resultList + ; + +paramList: '(' namedTypeList ')' + ; + +resultList: /* epsilon */ | '->' ty + ; + +namedTypeList + : /* epsilon */ + | namedType ( ',' namedType)* ','? + ; + +namedType: id ':' ty + ; + +// Item: use +useItem: 'use' usePath '.' '{' useNamesList '}' ';' + ; + +useNamesList: useNamesItem (',' useNamesItem)* + ; + +useNamesItem: id | id 'as' id + ; + +// Item: type (alias) + +typeItem: 'type' id '=' ty ';' + ; +// Item: record (bag of named fields) +recordItem: 'record' id '{' recordFields '}' + ; + +recordFields: recordField | recordField ',' recordFields? + ; + +recordField: id ':' ty + ; +// Item: flags (bag-of-bools) + +flagsItems: 'flags' id '{' flagsFields '}' + ; + +flagsFields: id | id ',' flagsFields? + ; + +// Item: variant (one of a set of types) + +variantItems: 'variant' id '{' variantCases '}' + ; + +variantCases: variantCase | variantCase ',' variantCases? + ; + +variantCase: id | id '(' ty ')' + ; +// Item: enum (variant but with no payload) + +enumItems: 'enum' id '{' enumCases '}' + ; + +enumCases: id | id ',' enumCases? + ; + +// Item: resource --- +resourceItem + : 'resource' id ';' + | 'resource' id '{' resourceMethod* '}' + ; + +resourceMethod + : funcItem + | id ':' 'static' funcType ';' + | 'constructor' paramList resultList ';' + ; + +// Types --- +ty + : 'u8' + | 'u16' + | 'u32' + | 'u64' + | 's8' + | 's16' + | 's32' + | 's64' + | 'f32' + | 'f64' + | 'char' + | 'bool' + | 'string' + | tuple + | list + | option + | result + | future + | stream + | handle + | id + ; + +tuple: 'tuple' '<' tupleList '>' + ; + +tupleList + : ty (',' ty)* ','? // Allow trailing comma + ; + +list: 'list' '<' ty '>' | 'list' '<' ty ',' NUM_ID '>' + ; + +option: 'option' '<' ty '>' + ; + +result + : 'result' '<' ty ',' ty '>' + | 'result' '<' '_' ',' ty '>' + | 'result' '<' ty '>' + | 'result' + ; + +future + : 'future' '<' ty '>' + | 'future' + ; + +stream + : 'stream' '<' ty '>' + | 'stream' + ; + +// Handles --- + +handle + : id + | 'borrow' '<' id '>' + | 'own' '<' id '>' + ; + +// Item: Gate --- +gate: gateItem* + ; + +gateItem: unstableGate | sinceGate | deprecatedGate + ; + +unstableGate: '@unstable' '(' featureField ')' + ; + +sinceGate: '@since' '(' versionField ')' + ; + +deprecatedGate: '@deprecated' '(' versionField ')' + ; + +featureField: 'feature' '=' id + ; + +versionField: 'version' '=' validSemver + ; + +validSemver: VALID_SEMVER + ; + +token: operator | keyword | NUM_ID | Identifier + ; + +operator + : '=' + | ',' + | ':' + | ';' + | '(' + | ')' + | '{' + | '}' + | '<' + | '>' + | '*' + | '->' + | '/' + | '.' + | '@' + ; + +id: '%' keyword | '%' Identifier | Identifier + ; + +keyword + : 'as' + | 'bool' + | 'borrow' + | 'char' + | 'constructor' + | 'enum' + | 'export' + | 'f32' + | 'f64' + | 'flags' + | 'from' + | 'func' + | 'future' + | 'import' + | 'include' + | 'interface' + | 'list' + | 'option' + | 'own' + | 'package' + | 'record' + | 'resource' + | 'result' + | 's16' + | 's32' + | 's64' + | 's8' + | 'static' + | 'stream' + | 'string' + | 'tuple' + | 'type' + | 'u16' + | 'u32' + | 'u64' + | 'u8' + | 'use' + | 'variant' + | 'with' + | 'world' + ; + +// ---- +Identifier: LETTER (LETTER | DIGIT | '-')* + ; + +// Semver: https://semver.org/ +VALID_SEMVER + : VERSION_CORE + | VERSION_CORE DASH PRERELEASE + | VERSION_CORE PLUS BUILD + | VERSION_CORE DASH PRERELEASE PLUS BUILD + ; + +VERSION_CORE: MAJOR DOT MINOR DOT PATCH + ; + +MAJOR: NUM_ID + ; + +MINOR: NUM_ID + ; + +PATCH: NUM_ID + ; + +PRERELEASE: DOTSEPERATED_PRERELEASE_ID + ; + +DOTSEPERATED_PRERELEASE_ID + : PRERELEASE_ID + | PRERELEASE_ID DOT DOTSEPERATED_PRERELEASE_ID + ; + +BUILD: DOTSEPERATED_BUILD_ID + ; + +DOTSEPERATED_BUILD_ID + : BUILD_ID + | BUILD_ID DOT DOTSEPERATED_BUILD_ID + ; + +PRERELEASE_ID: ALPHANUM_ID | DIGITS + ; + +BUILD_ID: ALPHANUM_ID | DIGITS + ; + +ALPHANUM_ID + : NON_DIGIT + | NON_DIGIT ID_CHARS + | ID_CHARS NON_DIGIT + | ID_CHARS NON_DIGIT ID_CHARS + ; + +NUM_ID: '0' | POSITIVE_DIGIT | POSITIVE_DIGIT DIGITS + ; + +ID_CHARS: ID_CHAR | ID_CHAR ID_CHARS + ; + +ID_CHAR: DIGIT | NON_DIGIT + ; + +NON_DIGIT: LETTER | DASH + ; + +DIGITS: DIGIT | DIGIT DIGITS + ; + +fragment DIGIT: [0-9] + ; + +fragment POSITIVE_DIGIT: [1-9] + ; + +fragment LETTER: [a-zA-Z] + ; + +fragment HexadecimalDigit: [0-9a-fA-F] + ; + +DASH: '-' + ; + +PLUS: '+' + ; + +DOT: '.' + ; + +// Other --- +COMMENT: '/*' .*? '*/' -> skip + ; + +LINE_COMMENT: '//' ~[\r\n]* -> skip + ; + +WS: [ \t\r\n]+ -> skip + ; \ No newline at end of file diff --git a/include/cmcpp/context.hpp b/include/cmcpp/context.hpp index 14dad52..3deb334 100644 --- a/include/cmcpp/context.hpp +++ b/include/cmcpp/context.hpp @@ -1683,6 +1683,11 @@ namespace cmcpp HostUnicodeConversion convert; GuestRealloc realloc; + InstanceContext() = default; + + InstanceContext(const HostTrap &trap_fn, HostUnicodeConversion convert_fn, const GuestRealloc &realloc_fn) + : trap(trap_fn), convert(convert_fn), realloc(realloc_fn) {} + std::unique_ptr createLiftLowerContext(const GuestMemory &memory, const Encoding &string_encoding = Encoding::Utf8, const std::optional &post_return = std::nullopt, diff --git a/include/cmcpp/traits.hpp b/include/cmcpp/traits.hpp index 30dd27f..c20a8c3 100644 --- a/include/cmcpp/traits.hpp +++ b/include/cmcpp/traits.hpp @@ -613,6 +613,53 @@ namespace cmcpp return (ptr + static_cast(alignment) - 1) & ~(static_cast(alignment) - 1); } + // Helper to compute tuple alignment at compile time + template + constexpr uint32_t compute_tuple_alignment() + { + uint32_t a = 1; + ((a = std::max(a, ValTrait::alignment)), ...); + return a; + } + + // Helper to compute tuple size at compile time + template + constexpr uint32_t compute_tuple_size() + { + constexpr uint32_t alignment = compute_tuple_alignment(); + uint32_t s = 0; + ((s = align_to(s, ValTrait::alignment), (s += ValTrait::size)), ...); + return align_to(s, alignment); + } + + // Helper to compute tuple flat_types length at compile time + template + constexpr size_t compute_tuple_flat_types_len() + { + size_t i = 0; + ((i += ValTrait::flat_types.size()), ...); + return i; + } + + // Helper to compute tuple flat_types at compile time + template + constexpr std::array compute_tuple_flat_types() + { + std::array v{}; + size_t idx = 0; + auto append = [&v, &idx](auto ft) constexpr + { + v[idx++] = ft; + }; + (([&append]() constexpr + { + for (auto ft : ValTrait::flat_types) { + append(ft); + } }()), + ...); + return v; + } + template using tuple_t = std::tuple; template @@ -620,36 +667,10 @@ namespace cmcpp { static constexpr ValType type = ValType::Tuple; using inner_type = typename std::tuple; - static constexpr uint32_t alignment = []() constexpr - { - uint32_t a = 1; - ((a = std::max(a, ValTrait::alignment)), ...); - return a; - }(); - static constexpr uint32_t size = []() constexpr - { - uint32_t s = 0; - ((s = align_to(s, ValTrait::alignment), (s += ValTrait::size)), ...); - return align_to(s, alignment); - }(); - static constexpr size_t flat_types_len = []() constexpr - { - size_t i = 0; - ((i += ValTrait::flat_types.size()), ...); - return i; - }(); - static constexpr std::array flat_types = []() constexpr - { - std::array v; - size_t i = 0; - (([&v, &i]() - { - for (auto &ft : ValTrait::flat_types) { - v[i++] = ft; - } }()), - ...); - return v; - }(); + static constexpr uint32_t alignment = compute_tuple_alignment(); + static constexpr uint32_t size = compute_tuple_size(); + static constexpr size_t flat_types_len = compute_tuple_flat_types_len(); + static constexpr std::array flat_types = compute_tuple_flat_types(); }; template concept Tuple = ValTrait::type == ValType::Tuple; @@ -703,6 +724,43 @@ namespace cmcpp return WasmValType::i64; } + // Helper to compute variant max_case_alignment at compile time + template + constexpr uint32_t compute_variant_max_case_alignment() + { + uint32_t a = 1; + ((a = std::max(a, ValTrait::alignment)), ...); + return a; + } + + // Helper to compute variant max_case_size at compile time + template + constexpr uint32_t compute_variant_max_case_size() + { + uint32_t cs = 0; + ((cs = std::max(cs, ValTrait::size)), ...); + return cs; + } + + // Helper to compute variant size at compile time + template + constexpr uint32_t compute_variant_size() + { + uint32_t s = ValTrait::size; + s = align_to(s, MaxCaseAlignment); + s += MaxCaseSize; + return align_to(s, AlignmentVariant); + } + + // Helper to compute variant flat_types_len at compile time + template + constexpr size_t compute_variant_flat_types_len() + { + size_t i = 0; + ((i = std::max(i, ValTrait::flat_types.size())), ...); + return i + 1; + } + template using variant_t = std::variant; template @@ -726,34 +784,14 @@ namespace cmcpp discriminant_bytes <= 2, uint16_t, uint32_t>>; - static constexpr uint32_t max_case_alignment = []() constexpr - { - uint32_t a = 1; - ((a = std::max(a, ValTrait::alignment)), ...); - return a; - }(); + static constexpr uint32_t max_case_alignment = compute_variant_max_case_alignment(); static constexpr uint32_t alignment_variant = std::max(ValTrait::alignment, max_case_alignment); static constexpr uint32_t alignment = std::max(ValTrait::alignment, static_cast(1)); - static constexpr uint32_t max_case_size = []() constexpr - { - uint32_t cs = 0; - ((cs = std::max(cs, ValTrait::size)), ...); - return cs; - }(); - static constexpr uint32_t size = []() constexpr - { - uint32_t s = ValTrait::size; - s = align_to(s, max_case_alignment); - s += max_case_size; - return align_to(s, alignment_variant); - }(); + static constexpr uint32_t max_case_size = compute_variant_max_case_size(); + static constexpr uint32_t size = compute_variant_size(); static_assert(size > 0 && size < std::numeric_limits::max()); - static constexpr size_t flat_types_len = []() constexpr - { - size_t i = 0; - ((i = std::max(i, ValTrait::flat_types.size())), ...); - return i + 1; - }(); + static constexpr size_t flat_types_len = compute_variant_flat_types_len(); + template static constexpr void merge_case_types(std::array &flat) { @@ -823,6 +861,37 @@ namespace cmcpp template using func_t = typename func_t_impl::type; + // Helper to compute host_flat_params_types at compile time + template + constexpr std::array compute_host_flat_params_types() + { + std::array arr{}; + for (size_t i = 0; i < ValTrait::flat_types.size(); ++i) + { + arr[i] = ValTrait::flat_types[i]; + } + if constexpr (ValTrait::flat_types.size() > MAX_FLAT_RESULTS) + { + arr[ValTrait::flat_types.size()] = WasmValType::i32; + } + return arr; + } + + // Helper to compute host_flat_result_types at compile time + template + constexpr std::array compute_host_flat_result_types() + { + std::array arr{}; + if constexpr (ValTrait::flat_types.size() <= MAX_FLAT_RESULTS) + { + for (size_t i = 0; i < ValTrait::flat_types.size(); ++i) + { + arr[i] = ValTrait::flat_types[i]; + } + } + return arr; + } + template struct ValTrait> { @@ -836,34 +905,11 @@ namespace cmcpp static constexpr size_t host_flat_params_types_size = (ValTrait::flat_types.size() > MAX_FLAT_RESULTS) ? (ValTrait::flat_types.size() + 1) : ValTrait::flat_types.size(); - static constexpr auto host_flat_params_types = []() constexpr - { - std::array arr{}; - for (size_t i = 0; i < ValTrait::flat_types.size(); ++i) - { - arr[i] = ValTrait::flat_types[i]; - } - if constexpr (ValTrait::flat_types.size() > MAX_FLAT_RESULTS) - { - arr[ValTrait::flat_types.size()] = WasmValType::i32; - } - return arr; - }(); + static constexpr auto host_flat_params_types = compute_host_flat_params_types(); static constexpr size_t host_flat_result_types_size = (ValTrait::flat_types.size() > MAX_FLAT_RESULTS) ? 0 : ValTrait::flat_types.size(); - static constexpr auto host_flat_result_types = []() constexpr - { - std::array arr{}; - if constexpr (ValTrait::flat_types.size() <= MAX_FLAT_RESULTS) - { - for (size_t i = 0; i < ValTrait::flat_types.size(); ++i) - { - arr[i] = ValTrait::flat_types[i]; - } - } - return arr; - }(); + static constexpr auto host_flat_result_types = compute_host_flat_result_types(); }; template diff --git a/include/wamr.hpp b/include/wamr.hpp index 5f3b59a..2b956bd 100644 --- a/include/wamr.hpp +++ b/include/wamr.hpp @@ -1,3 +1,31 @@ +#pragma once + +// WAMR Integration for WebAssembly Component Model +// +// This header provides integration between WAMR (WebAssembly Micro Runtime) and the +// Component Model canonical ABI implementation. It enables C++ applications to: +// - Call WebAssembly guest functions with automatic type conversion +// - Export C++ functions to be called from WebAssembly guests +// - Create lift/lower contexts for marshaling data between host and guest +// +// Key Functions: +// - guest_function() - Create a callable wrapper for a guest function +// - host_function() - Export a C++ function to WebAssembly +// - create_guest_realloc() - Create a realloc function for guest memory +// - create_lift_lower_context() - Set up complete context for guest calls +// +// Example Usage: +// wasm_module_inst_t module_inst = /* initialize WAMR module */; +// wasm_exec_env_t exec_env = /* create execution environment */; +// wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc"); +// +// auto ctx = cmcpp::create_lift_lower_context(module_inst, exec_env, cabi_realloc); +// auto guest_fn = cmcpp::guest_function(module_inst, exec_env, ctx, "my-function"); +// bool result = guest_fn(true, false); +// +// Thread Safety: Functions are not thread-safe by default. Each thread should maintain +// its own execution environment and context. + #include "wasm_export.h" #include "cmcpp.hpp" #include @@ -5,12 +33,12 @@ namespace cmcpp { - void trap(const char *msg) + inline void trap(const char *msg) { - throw new std::runtime_error(msg); + throw std::runtime_error(msg); } - std::vector wasmVal2wam_val_t(const WasmValVector &values) + inline std::vector wasmVal2wam_val_t(const WasmValVector &values) { std::vector result; result.reserve(values.size()); @@ -42,7 +70,7 @@ namespace cmcpp return result; } - WasmValVector wam_val_t2wasmVal(size_t count, const wasm_val_t *values) + inline WasmValVector wam_val_t2wasmVal(size_t count, const wasm_val_t *values) { WasmValVector result; result.reserve(count); @@ -123,7 +151,18 @@ namespace cmcpp }; } - std::pair convert(void *dest, uint32_t dest_byte_len, const void *src, uint32_t src_byte_len, Encoding from_encoding, Encoding to_encoding) + // String encoding conversion function + // Currently only supports same-encoding passthrough (no actual conversion) + // For cross-encoding conversion, use ICU or another Unicode library + // @param dest: Destination buffer + // @param dest_byte_len: Size of destination buffer + // @param src: Source buffer + // @param src_byte_len: Size of source data + // @param from_encoding: Source encoding + // @param to_encoding: Target encoding + // @return: Pair of (destination pointer, bytes written) + // @note: Currently asserts if encodings differ - full conversion not implemented + inline std::pair convert(void *dest, uint32_t dest_byte_len, const void *src, uint32_t src_byte_len, Encoding from_encoding, Encoding to_encoding) { if (from_encoding == to_encoding) { @@ -135,7 +174,10 @@ namespace cmcpp } return std::make_pair(nullptr, 0); } - assert(false); + // TODO: Implement cross-encoding conversion using ICU + // See test/host-util.cpp for a reference implementation + assert(false && "Cross-encoding conversion not implemented"); + return std::make_pair(nullptr, 0); } template @@ -175,6 +217,54 @@ namespace cmcpp return retVal; } + // Create a GuestRealloc function from WAMR execution environment + // @param exec_env: WAMR execution environment + // @param cabi_realloc: The cabi_realloc function from the WASM module + // @return: GuestRealloc function ready to use with LiftLowerOptions + inline GuestRealloc create_guest_realloc(wasm_exec_env_t exec_env, wasm_function_inst_t cabi_realloc) + { + return [exec_env, cabi_realloc](int original_ptr, int original_size, int alignment, int new_size) -> int + { + uint32_t argv[4]; + argv[0] = original_ptr; + argv[1] = original_size; + argv[2] = alignment; + argv[3] = new_size; + wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv); + return argv[0]; + }; + } + + // Create a complete LiftLowerContext from WAMR module instance + // This helper combines all the steps needed to set up a context for calling guest functions + // @param module_inst: WAMR module instance + // @param exec_env: WAMR execution environment + // @param cabi_realloc: The cabi_realloc function from the WASM module + // @param encoding: String encoding (default: Utf8) + // @return: LiftLowerContext ready for use with guest_function<>() + // @throws: std::runtime_error if memory lookup fails + inline LiftLowerContext create_lift_lower_context( + wasm_module_inst_t module_inst, + wasm_exec_env_t exec_env, + wasm_function_inst_t cabi_realloc, + Encoding encoding = Encoding::Utf8) + { + wasm_memory_inst_t memory = wasm_runtime_lookup_memory(module_inst, "memory"); + if (!memory) + { + throw std::runtime_error("Failed to lookup memory instance"); + } + + uint8_t *mem_start_addr = static_cast(wasm_memory_get_base_address(memory)); + uint8_t *mem_end_addr = nullptr; + wasm_runtime_get_native_addr_range(module_inst, mem_start_addr, nullptr, &mem_end_addr); + + GuestRealloc realloc = create_guest_realloc(exec_env, cabi_realloc); + LiftLowerOptions opts(encoding, std::span(mem_start_addr, mem_end_addr - mem_start_addr), realloc); + + return LiftLowerContext(trap, convert, opts); + } + template void export_func(wasm_exec_env_t exec_env, uint64_t *args) { @@ -187,24 +277,10 @@ namespace cmcpp auto lower_params = rawWamrArg2Wasm(exec_env, args); wasm_module_inst_t module_inst = wasm_runtime_get_module_inst(exec_env); - wasm_memory_inst_t memory = wasm_runtime_lookup_memory(module_inst, "memory"); wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc"); - GuestRealloc realloc = [exec_env, cabi_realloc](int original_ptr, int original_size, int alignment, int new_size) -> int - { - uint32_t argv[4]; - argv[0] = original_ptr; - argv[1] = original_size; - argv[2] = alignment; - argv[3] = new_size; - wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv); - return argv[0]; - }; - uint8_t *mem_start_addr = (uint8_t *)wasm_memory_get_base_address(memory); - uint8_t *mem_end_addr = NULL; - wasm_runtime_get_native_addr_range(module_inst, mem_start_addr, NULL, &mem_end_addr); - LiftLowerOptions opts(Encoding::Utf8, std::span(mem_start_addr, mem_end_addr - mem_start_addr), realloc); - LiftLowerContext liftLowerContext(trap, convert, opts); + // Use the helper function to create LiftLowerContext + LiftLowerContext liftLowerContext = create_lift_lower_context(module_inst, exec_env, cabi_realloc); auto params = lift_flat_values(liftLowerContext, MAX_FLAT_PARAMS, lower_params); if constexpr (ValTrait::flat_types.size() > 0) @@ -238,7 +314,7 @@ namespace cmcpp return symbol; } - bool host_module(const char *module_name, NativeSymbol *native_symbols, uint32_t n_native_symbols) + inline bool host_module(const char *module_name, NativeSymbol *native_symbols, uint32_t n_native_symbols) { return wasm_runtime_register_natives_raw(module_name, native_symbols, n_native_symbols); } diff --git a/ref/component-model b/ref/component-model index e53dc73..4626bdf 160000 --- a/ref/component-model +++ b/ref/component-model @@ -1 +1 @@ -Subproject commit e53dc737146b2e1cbf65fcffcfa2f7d744401ffa +Subproject commit 4626bdf5b3afac3ecfbfb040f36ccd95c5c549a6 diff --git a/ref/wasm-micro-runtime b/ref/wasm-micro-runtime index 3f4145e..df90804 160000 --- a/ref/wasm-micro-runtime +++ b/ref/wasm-micro-runtime @@ -1 +1 @@ -Subproject commit 3f4145e6914d5cfc717a257aa660605490f26908 +Subproject commit df908048de3990c9a74056d44c0870c5efb9dd04 diff --git a/ref/wit-bindgen b/ref/wit-bindgen new file mode 160000 index 0000000..3a2114d --- /dev/null +++ b/ref/wit-bindgen @@ -0,0 +1 @@ +Subproject commit 3a2114dd74544b0c71d99fb0e97d2edb56d0a5a6 diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..0d9bd91 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,327 @@ +# Component Model C++ Samples + +This directory contains sample implementations demonstrating how to use the Component Model C++ library with different WebAssembly runtimes. + +## Overview + +The samples showcase real-world integration between C++ host applications and WebAssembly Component Model guests, demonstrating: + +- **Host-Guest Communication**: Bidirectional function calls between C++ and WebAssembly +- **Type System**: Complete coverage of Component Model types (primitives, composites, resources) +- **Runtime Integration**: Working with different WebAssembly runtimes +- **Code Generation**: WIT-generated bindings for type-safe host integration +- **Memory Management**: Proper handling of linear memory, string encodings, and resource lifetimes + +## Directory Structure + +``` +samples/ +├── README.md # This file +├── CMakeLists.txt # Build configuration +├── wamr/ # WAMR runtime sample (fully implemented) +│ ├── main.cpp # Sample entry point and implementation +│ ├── host_impl.cpp # Host function implementations +│ └── generated/ # WIT-generated bindings (tracked in git) +│ ├── sample.hpp +│ ├── sample_wamr.hpp +│ └── sample_wamr.cpp +└── wasm/ # Guest WebAssembly modules + ├── sample.wit # WIT interface definition + ├── main.cpp # Guest implementation + └── CMakeLists.txt # Guest build configuration +``` + +## Samples + +### WAMR Sample (Fully Implemented) + +**Runtime**: [WAMR (WebAssembly Micro Runtime)](https://github.com/bytecodealliance/wasm-micro-runtime) + +**Status**: ✅ Complete + +The WAMR sample is a comprehensive demonstration using WIT-generated bindings for type-safe host integration. + +**Features Demonstrated**: +- All Component Model primitive types (bool, integers, floats, char, strings) +- Composite types (tuples, records, lists) +- Advanced types (variants, options, results, enums, flags) +- String encoding conversions (UTF-8, UTF-16, Latin-1+UTF-16) +- Memory management and proper resource cleanup +- Error handling and debugging + +**Quick Start**: +```bash +# Build the samples +cmake --preset linux-ninja-Debug -DBUILD_SAMPLES=ON +cmake --build --preset linux-ninja-Debug + +# Run the sample +cd build/samples/wamr +./wamr +``` + +See [`wamr/README.md`](wamr/README.md) for detailed documentation, including: +- Complete API reference +- Type system examples +- Building and running instructions +- Troubleshooting guide + +### WasmTime Sample (Planned) + +**Runtime**: [WasmTime](https://github.com/bytecodealliance/wasmtime) + +**Status**: 🚧 Planned + +Future sample demonstrating integration with the WasmTime runtime, including: +- Component Model native support +- Async/streaming APIs +- Resource management +- WASI support + +### WasmEdge Sample (Planned) + +**Runtime**: [WasmEdge](https://github.com/WasmEdge/WasmEdge) + +**Status**: 🚧 Planned + +Future sample showcasing WasmEdge integration with: +- AI/ML extensions +- Networking capabilities +- Edge computing scenarios + +### Wasmer Sample (Planned) + +**Runtime**: [Wasmer](https://github.com/wasmerio/wasmer) + +**Status**: 🚧 Planned + +Future sample demonstrating Wasmer integration. + +## Building Samples + +### Prerequisites + +1. **Install dependencies** via vcpkg (handled automatically by CMake presets) +2. **Install WASI SDK** for guest module compilation: + ```bash + # Via vcpkg (recommended) + vcpkg install wasi-sdk + + # Or set manually + export WASI_SDK_PREFIX=/path/to/wasi-sdk + ``` +3. **Install Rust tools** for WIT processing: + ```bash + cargo install wasm-tools wit-bindgen-cli + ``` + +### Build Commands + +**Linux/macOS**: +```bash +# Configure with samples enabled +cmake --preset linux-ninja-Debug -DBUILD_SAMPLES=ON + +# Build +cmake --build --preset linux-ninja-Debug + +# Run WAMR sample +./build/samples/wamr/wamr +``` + +**Windows**: +```bash +# Configure with samples enabled +cmake --preset windows-ninja-Debug -DBUILD_SAMPLES=ON + +# Build +cmake --build --preset windows-ninja-Debug + +# Run WAMR sample +.\build\samples\wamr\wamr.exe +``` + +### Build Options + +- `-DBUILD_SAMPLES=ON`: Enable sample builds (default: OFF) +- `-DWASI_SDK_PREFIX=/path`: Override WASI SDK location +- `-DBUILD_TESTS=ON`: Also build unit tests + +## Guest WebAssembly Modules + +The `wasm/` directory contains the guest-side WebAssembly component implementations: + +### `sample.wit` +WIT interface definition that describes the host-guest contract: +```wit +package example:sample; + +interface booleans { + and: func(a: bool, b: bool) -> bool; +} + +interface floats { + add: func(a: f64, b: f64) -> f64; +} + +// ... more interfaces +``` + +### `main.cpp` +C++ implementation of the guest module that exports functions defined in the WIT file and imports host functions. + +### Building Guest Modules + +Guest modules are built automatically as part of the sample build process. To rebuild manually: + +```bash +cd wasm +mkdir -p build && cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PREFIX/share/cmake/wasi-sdk.cmake +cmake --build . +``` + +This produces `sample.wasm` which is then used by the host samples. + +## Code Generation + +The WAMR sample includes generated bindings in `wamr/generated/` that are **tracked in git** for convenience: + +- `sample.hpp`: Type definitions and interfaces from WIT +- `sample_wamr.hpp`: WAMR-specific host function wrappers +- `sample_wamr.cpp`: Implementation of WAMR integration + +These files are auto-generated from `wasm/sample.wit` using the `wit-codegen` tool: + +```bash +# Regenerate bindings (if WIT changes) +cd build +./tools/wit-parser/wit-codegen \ + --wit ../../wasm/sample.wit \ + --output ../samples/wamr/generated +``` + +## Usage Patterns + +### Host Function Registration + +```cpp +#include "generated/sample_wamr.hpp" + +// Implement interface - bindings auto-generated from WIT +class MyHostImpl : public sample::host::logger { + void log(cmcpp::LiftLowerContext& cx, cmcpp::string_t msg) override { + std::cout << msg << std::endl; + } +}; +``` + +### Guest Function Invocation + +```cpp +auto booleans = sample::guest::booleans::create(module, instance); +bool result = booleans.and_(true, false); // Type-safe call +``` + +## Implementation Approach + +The samples use **WIT-generated bindings** for type-safe host integration: + +**Advantages**: +- Type-safe, idiomatic C++ API +- Automatic marshaling and memory management +- WIT-driven development workflow +- Less boilerplate code +- Compile-time interface validation + +**Requirements**: +- WIT interface definitions (`.wit` files) +- `wit-codegen` tool for code generation +- Component Model-compliant WebAssembly runtime + +**Development Workflow**: +1. Define interfaces in WIT format +2. Generate C++ bindings using `wit-codegen` +3. Implement host interface classes +4. Build and link guest WebAssembly modules +5. Run and test the integrated application + +## Testing + +Each sample includes its own test suite demonstrating correct behavior: + +```bash +# Build and run all tests +cmake --preset linux-ninja-Debug -DBUILD_SAMPLES=ON -DBUILD_TESTS=ON +cmake --build --preset linux-ninja-Debug +ctest --test-dir build --output-on-failure +``` + +## Troubleshooting + +### "WASI_SDK_PREFIX is not set" + +The guest modules require WASI SDK for compilation: + +```bash +# Install via vcpkg +vcpkg install wasi-sdk + +# Or download manually from: +# https://github.com/WebAssembly/wasi-sdk/releases +export WASI_SDK_PREFIX=/path/to/wasi-sdk +``` + +### "Cannot find wasm-tools or wit-bindgen-cli" + +Install the required Rust tools: + +```bash +cargo install wasm-tools wit-bindgen-cli +``` + +### Link Errors with WAMR + +Ensure WAMR is properly built by vcpkg: + +```bash +# Clean and rebuild +rm -rf build vcpkg_installed +cmake --preset linux-ninja-Debug -DBUILD_SAMPLES=ON +cmake --build --preset linux-ninja-Debug +``` + +### Runtime Errors + +Enable verbose logging: +```cpp +// In your code +#define WAMR_VERBOSE 1 +``` + +See individual sample READMEs for runtime-specific troubleshooting. + +## Contributing + +To add a new sample: + +1. Create a directory under `samples/` for your runtime +2. Add CMake configuration in `samples/CMakeLists.txt` +3. Define WIT interfaces for your sample +4. Generate bindings using `wit-codegen` +5. Implement host interface classes +6. Add comprehensive README with usage instructions +7. Add tests demonstrating key functionality + +See [`CONTRIBUTING.md`](../CONTRIBUTING.md) (if exists) for general contribution guidelines. + +## References + +- [Component Model Specification](https://github.com/WebAssembly/component-model) +- [Canonical ABI Documentation](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md) +- [WIT Format](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) +- [WAMR Documentation](https://github.com/bytecodealliance/wasm-micro-runtime) + +## License + +See [LICENSE](../LICENSE) in the root directory. diff --git a/samples/wamr/CMakeLists.txt b/samples/wamr/CMakeLists.txt index ef6a03f..e4ebc77 100644 --- a/samples/wamr/CMakeLists.txt +++ b/samples/wamr/CMakeLists.txt @@ -86,15 +86,53 @@ if (NOT WAMR_INCLUDE_DIRS OR NOT IWASM_LIB OR (_wamr_require_vmlib AND NOT _wamr endif() endif() -set(WAMR_SAMPLE_AVAILABLE TRUE) +# Check if wit-bindgen is available (required for wasm guest build) +find_program(WIT_BINDGEN_EXECUTABLE wit-bindgen HINTS $ENV{HOME}/.cargo/bin) +if (NOT WIT_BINDGEN_EXECUTABLE) + message(WARNING "wit-bindgen not found. Wasm guest samples will not be built. Install with: cargo install wit-bindgen-cli") + set(WAMR_SAMPLE_AVAILABLE FALSE) +else() + set(WAMR_SAMPLE_AVAILABLE TRUE) +endif() +# Generate host bindings from WIT file using wit-codegen +set(WIT_FILE "${CMAKE_SOURCE_DIR}/samples/wasm/sample.wit") +set(GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated") +# Package name 'example:sample' will be extracted to 'sample' by wit-codegen +set(GENERATED_HEADER "${GENERATED_DIR}/sample.hpp") +set(GENERATED_WAMR_BINDINGS "${GENERATED_DIR}/sample_wamr.cpp") + +# Create custom command to generate bindings +# Note: wit-codegen automatically derives the output name from the package name in the WIT file +add_custom_command( + OUTPUT ${GENERATED_HEADER} ${GENERATED_WAMR_BINDINGS} + COMMAND ${CMAKE_COMMAND} -E make_directory ${GENERATED_DIR} + COMMAND cd ${GENERATED_DIR} && $ ${WIT_FILE} + DEPENDS wit-codegen ${WIT_FILE} + COMMENT "Generating host bindings from WIT file (output name derived from package)" + VERBATIM +) + +# Create a custom target for the generated files +add_custom_target(generate-sample-host-bindings + DEPENDS ${GENERATED_HEADER} ${GENERATED_WAMR_BINDINGS} +) + +# WAMR sample executable that runs both hand-coded and generated implementations add_executable(wamr main.cpp + host_impl.cpp # Host-provided implementations of generated interfaces + ${GENERATED_WAMR_BINDINGS} # Generated WAMR symbol arrays ) +# Make sure bindings are generated before building +add_dependencies(wamr generate-sample-host-bindings) + target_include_directories(wamr PRIVATE ${WAMR_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${GENERATED_DIR} # Add generated directory to include path ) if (MSVC) @@ -115,4 +153,9 @@ endif() target_link_libraries(wamr PRIVATE cmcpp ${_wamr_link_libs}) -set(WAMR_SAMPLE_AVAILABLE TRUE PARENT_SCOPE) +# Only propagate TRUE to parent if wit-bindgen is available +if (WIT_BINDGEN_EXECUTABLE) + set(WAMR_SAMPLE_AVAILABLE TRUE PARENT_SCOPE) +else() + set(WAMR_SAMPLE_AVAILABLE FALSE PARENT_SCOPE) +endif() diff --git a/samples/wamr/README.md b/samples/wamr/README.md deleted file mode 100644 index 622f897..0000000 --- a/samples/wamr/README.md +++ /dev/null @@ -1,332 +0,0 @@ -# WAMR Component Model Sample - -This sample demonstrates how to use the Component Model C++ library with WAMR (WebAssembly Micro Runtime) to interact between C++ host code and WebAssembly guest modules. - -## Overview - -The sample showcases: -- **Host Function Registration**: Exporting C++ functions to WebAssembly modules -- **Guest Function Invocation**: Calling WebAssembly functions from C++ code -- **Component Model Types**: Working with all major Component Model types including: - - Primitive types (bool, integers, floats, strings) - - Composite types (tuples, records, lists) - - Advanced types (variants, options, results, enums, flags) -- **Memory Management**: Proper handling of WebAssembly linear memory -- **Error Handling**: Comprehensive error checking and resource cleanup - -## Architecture - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ C++ Host │ │ Component Model │ │ WASM Guest │ -│ Application │◄──►│ C++ Library │◄──►│ Module │ -│ │ │ │ │ │ -│ • Host funcs │ │ • Type mapping │ │ • Guest funcs │ -│ • Memory mgmt │ │ • ABI handling │ │ • Exports │ -│ • Error handling│ │ • Serialization │ │ • Imports │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ -``` - -## Prerequisites - -- **WAMR**: WebAssembly Micro Runtime (installed via vcpkg) -- **C++20**: Modern C++ compiler with C++20 support -- **CMake**: Build system -- **Component Model WASM**: The sample.wasm file (built from samples/wasm) - -## Building - -From the project root: - -```bash -mkdir -p build -cd build -cmake .. -cmake --build . --target wamr --parallel -``` - -This will: -1. Build the WASM guest module (`samples/wasm` → `sample.wasm`) -2. Build the WAMR host application (`samples/wamr` → `wamr`) - -## Running - -```bash -cd build/samples/wamr -./wamr -``` - -### Expected Output - -The sample produces well-organized output demonstrating each Component Model feature: - -``` -WAMR Component Model C++ Sample -=============================== -Starting WAMR runtime initialization... -WAMR runtime initialized successfully -Successfully loaded WASM file (104637 bytes) -Successfully loaded WASM module -Successfully instantiated WASM module - -=== Testing Guest Functions === - ---- String Functions --- -call_reverse("Hello World!"): !DLROW OLLEH -call_reverse(call_reverse("Hello World!")): HELLO WORLD! - ---- Variant Functions --- -variant_func((uint32_t)40)80 -variant_func((bool_t)true)0 -variant_func((bool_t)false)1 - ---- Option Functions --- -option_func((uint32_t)40).has_value()1 -option_func((uint32_t)40).value()80 -option_func(std::nullopt).has_value()0 - ---- Void Functions --- -Hello, Void_Func! - ---- Result Functions --- -ok_func result: 42 -err_func result: error - ---- Boolean Functions --- -call_and(false, false): 0 -call_and(false, true): 0 -call_and(true, false): 0 -call_and(true, true): 1 - ---- Float Functions --- -call_add(3.1, 0.2): 3.3 -call_add(1.5, 2.5): 4 -call_add(DBL_MAX, 0.0): 8.98847e+307 -call_add(DBL_MAX / 2, DBL_MAX / 2): 1.79769e+308 - ---- Complex String Functions --- -call_lots result: 42 - ---- Tuple Functions --- -call_reverse_tuple({false, "Hello World!"}): !DLROW OLLEH, 1 - ---- List Functions --- -call_list_filter result: 2 - ---- Enum Functions --- -enum_func(e::a): 0 -enum_func(e::b): 1 -enum_func(e::c): 2 - -=== Cleanup and Summary === -Sample completed successfully! -``` - -## Code Structure - -### Host Functions (C++ → WASM) - -Host functions are C++ functions exported to the WebAssembly module: - -```cpp -// Simple host function -void_t void_func() { - std::cout << "Hello from host!" << std::endl; -} - -// Host function with parameters and return value -cmcpp::bool_t and_func(cmcpp::bool_t a, cmcpp::bool_t b) { - return a && b; -} - -// Register host functions -NativeSymbol booleans_symbol[] = { - host_function("and", and_func), -}; -wasm_runtime_register_natives_raw("example:sample/booleans", - booleans_symbol, - sizeof(booleans_symbol) / sizeof(NativeSymbol)); -``` - -### Guest Functions (WASM → C++) - -Guest functions are WebAssembly functions called from C++ code: - -```cpp -// Create a callable wrapper for a WASM function -auto call_reverse = guest_function( - module_inst, exec_env, liftLowerContext, - "example:sample/strings#reverse" -); - -// Call the WASM function -auto result = call_reverse("Hello World!"); -std::cout << "Result: " << result << std::endl; -``` - -### Component Model Types - -The sample demonstrates all major Component Model types: - -```cpp -// Primitive types -bool_t, uint32_t, float64_t, string_t - -// Composite types -tuple_t -list_t -variant_t -option_t -result_t -enum_t -``` - -## Key Components - -### 1. Runtime Initialization - -```cpp -// Initialize WAMR runtime -wasm_runtime_init(); - -// Load and instantiate module -module = wasm_runtime_load(buffer, size, error_buf, sizeof(error_buf)); -module_inst = wasm_runtime_instantiate(module, stack_size, heap_size, - error_buf, sizeof(error_buf)); -``` - -### 2. Memory Management - -```cpp -// Set up guest memory reallocation function -GuestRealloc realloc = [exec_env, cabi_realloc](int original_ptr, - int original_size, - int alignment, - int new_size) -> int { - uint32_t argv[4] = {original_ptr, original_size, alignment, new_size}; - wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv); - return argv[0]; -}; -``` - -### 3. Context Setup - -```cpp -// Create lift/lower context for type conversions -LiftLowerOptions opts(Encoding::Utf8, memory_span, realloc); -LiftLowerContext liftLowerContext(trap, convert, opts); -``` - -### 4. Error Handling - -The sample includes comprehensive error checking: -- File I/O errors -- WAMR initialization failures -- Module loading/instantiation errors -- Function lookup failures -- Runtime execution errors - -## WebAssembly Interface Types (WIT) - -The sample uses the following WIT definition (`samples/wasm/sample.wit`): - -```wit -package example:sample; - -interface booleans { - and: func(a: bool, b: bool) -> bool; -} - -interface strings { - reverse: func(a: string) -> string; - lots: func(p1: string, p2: string, /* ... */) -> u32; -} - -// ... more interfaces - -world sample { - export booleans; - export strings; - // ... more exports - - import logging; - import booleans; - // ... more imports -} -``` - -## Performance Considerations - -- **Memory Management**: The sample uses efficient memory allocation with proper cleanup -- **Type Safety**: Strong typing prevents ABI mismatches at compile time -- **Zero-copy**: String and binary data can be shared without copying when possible -- **Flat Values**: Simple types are passed directly without heap allocation - -## Troubleshooting - -### Common Issues - -1. **WASM file not found** - - Ensure `sample.wasm` is built: `cmake --build . --target wasm` - - Check file path in error message - -2. **Library linking errors** - - Verify WAMR is installed via vcpkg - - Check CMake finds the libraries correctly - -3. **Runtime crashes** - - Enable debug symbols: `CMAKE_BUILD_TYPE=Debug` - - Check WAMR error messages in `error_buf` - -4. **Function not found** - - Verify function name matches WIT specification exactly - - Check exports in the WASM module - -### Debug Mode - -Build with debug information for better error messages: - -```bash -cmake -DCMAKE_BUILD_TYPE=Debug .. -cmake --build . --target wamr -``` - -## Recent Improvements - -This sample has been enhanced with: - -### Code Quality -- **Comprehensive Error Handling**: Added proper error checking for all WAMR operations -- **Resource Management**: Improved cleanup and memory management -- **Configuration Constants**: Added configurable parameters for stack/heap sizes -- **Better Diagnostics**: Enhanced logging and status messages - -### Build System -- **Improved CMakeLists.txt**: Better library detection and error reporting -- **Build Scripts**: Added convenient `build.sh` and `run.sh` scripts -- **Cross-platform Support**: Better handling of library paths and linking - -### Documentation -- **Organized Output**: Added section headers for different test categories -- **Comprehensive README**: Detailed documentation with examples and troubleshooting -- **Code Comments**: Better inline documentation and explanations - -### Testing Coverage -- **All Component Model Types**: Comprehensive testing of primitives, composites, and advanced types -- **Error Scenarios**: Testing of error conditions and edge cases -- **Performance Examples**: Demonstrations of efficient memory usage patterns - -## Related Files - -- **`main.cpp`**: Main sample application -- **`CMakeLists.txt`**: Build configuration -- **`../wasm/sample.wit`**: WebAssembly Interface Types definition -- **`../wasm/main.cpp`**: Guest-side implementation -- **`../../include/wamr.hpp`**: WAMR integration header -- **`../../include/cmcpp/`**: Component Model C++ library headers - -## Further Reading - -- [WebAssembly Component Model Specification](https://github.com/WebAssembly/component-model) -- [WAMR Documentation](https://github.com/bytecodealliance/wasm-micro-runtime) -- [Component Model C++ Library Documentation](../../README.md) diff --git a/samples/wamr/build.sh b/samples/wamr/build.sh deleted file mode 100755 index 79d1b3b..0000000 --- a/samples/wamr/build.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Build script for WAMR Component Model Sample -# Usage: ./build.sh [clean] - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -BUILD_DIR="$PROJECT_ROOT/build" - -echo "WAMR Component Model Sample - Build Script" -echo "==========================================" - -# Check if clean is requested -if [[ "$1" == "clean" ]]; then - echo "Cleaning build directory..." - rm -rf "$BUILD_DIR" -fi - -# Create build directory if it doesn't exist -if [[ ! -d "$BUILD_DIR" ]]; then - echo "Creating build directory..." - mkdir -p "$BUILD_DIR" -fi - -# Configure and build -echo "Configuring project..." -cd "$PROJECT_ROOT" -cmake -B "$BUILD_DIR" -S . - -echo "Building WASM guest module..." -cmake --build "$BUILD_DIR" --target wasm --parallel - -echo "Building WAMR host application..." -cmake --build "$BUILD_DIR" --target wamr --parallel - -echo "Build completed successfully!" -echo "" -echo "To run the sample:" -echo " cd $BUILD_DIR/samples/wamr" -echo " ./wamr" diff --git a/samples/wamr/generated/sample.hpp b/samples/wamr/generated/sample.hpp new file mode 100644 index 0000000..204aef3 --- /dev/null +++ b/samples/wamr/generated/sample.hpp @@ -0,0 +1,179 @@ +#pragma once + +#ifndef GENERATED_SAMPLE_HPP_HPP +#define GENERATED_SAMPLE_HPP_HPP + +#include + +// Generated host function declarations from WIT +// - 'host' namespace: Guest imports (host implements these) +// - 'guest' namespace: Guest exports (guest implements these, host calls them) + +namespace host { + +// Interface: booleans +// Package: example:sample +namespace booleans { + +cmcpp::bool_t and_(cmcpp::bool_t a, cmcpp::bool_t b); + +} // namespace booleans + +// Interface: floats +// Package: example:sample +namespace floats { + +cmcpp::float64_t add(cmcpp::float64_t a, cmcpp::float64_t b); + +} // namespace floats + +// Interface: strings +// Package: example:sample +namespace strings { + +cmcpp::string_t reverse(cmcpp::string_t a); + +uint32_t lots(cmcpp::string_t p1, cmcpp::string_t p2, cmcpp::string_t p3, cmcpp::string_t p4, cmcpp::string_t p5, cmcpp::string_t p6, cmcpp::string_t p7, cmcpp::string_t p8, cmcpp::string_t p9, cmcpp::string_t p10, cmcpp::string_t p11, cmcpp::string_t p12, cmcpp::string_t p13, cmcpp::string_t p14, cmcpp::string_t p15, cmcpp::string_t p16, cmcpp::string_t p17); + +} // namespace strings + +// Interface: lists +// Package: example:sample +namespace lists { + +using v = cmcpp::variant_t; + +cmcpp::list_t filter_bool(cmcpp::list_t a); + +} // namespace lists + +// Interface: logging +// Package: example:sample +namespace logging { + +void log_bool(cmcpp::bool_t a, cmcpp::string_t s); + +void log_u32(uint32_t a, cmcpp::string_t s); + +void log_u64(uint64_t a, cmcpp::string_t s); + +void log_f32(cmcpp::float32_t a, cmcpp::string_t s); + +void log_f64(cmcpp::float64_t a, cmcpp::string_t s); + +void log_str(cmcpp::string_t a, cmcpp::string_t s); + +} // namespace logging + +// Standalone function: void-func +// Package: example:sample +void void_func(); + + +} // namespace host + +namespace guest { + +// Interface: booleans +// Package: example:sample +namespace booleans { + +// Guest function signature for use with guest_function() +using and_t = cmcpp::bool_t(cmcpp::bool_t, cmcpp::bool_t); + +} // namespace booleans + +// Interface: floats +// Package: example:sample +namespace floats { + +// Guest function signature for use with guest_function() +using add_t = cmcpp::float64_t(cmcpp::float64_t, cmcpp::float64_t); + +} // namespace floats + +// Interface: strings +// Package: example:sample +namespace strings { + +// Guest function signature for use with guest_function() +using reverse_t = cmcpp::string_t(cmcpp::string_t); + +// Guest function signature for use with guest_function() +using lots_t = uint32_t(cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t, cmcpp::string_t); + +} // namespace strings + +// Interface: tuples +// Package: example:sample +namespace tuples { + +// Guest function signature for use with guest_function() +using reverse_t = cmcpp::tuple_t(cmcpp::tuple_t); + +} // namespace tuples + +// Interface: lists +// Package: example:sample +namespace lists { + +using v = cmcpp::variant_t; + +// Guest function signature for use with guest_function() +using filter_bool_t = cmcpp::list_t(cmcpp::list_t); + +} // namespace lists + +// Interface: variants +// Package: example:sample +namespace variants { + +using v = cmcpp::variant_t; + +// Guest function signature for use with guest_function() +using variant_func_t = v(v); + +} // namespace variants + +// Interface: enums +// Package: example:sample +namespace enums { + +enum class e { + a, + b, + c +}; + +// Guest function signature for use with guest_function() +using enum_func_t = cmcpp::enum_t(cmcpp::enum_t); + +} // namespace enums + +// Standalone function: void-func +// Package: example:sample +// Guest function signature for use with guest_function() +using void_func_t = void(); + + +// Standalone function: ok-func +// Package: example:sample +// Guest function signature for use with guest_function() +using ok_func_t = cmcpp::result_t(uint32_t, uint32_t); + + +// Standalone function: err-func +// Package: example:sample +// Guest function signature for use with guest_function() +using err_func_t = cmcpp::result_t(uint32_t, uint32_t); + + +// Standalone function: option-func +// Package: example:sample +// Guest function signature for use with guest_function() +using option_func_t = cmcpp::option_t(cmcpp::option_t); + + +} // namespace guest + +#endif // GENERATED_SAMPLE_HPP_HPP diff --git a/samples/wamr/generated/sample_wamr.cpp b/samples/wamr/generated/sample_wamr.cpp new file mode 100644 index 0000000..9387215 --- /dev/null +++ b/samples/wamr/generated/sample_wamr.cpp @@ -0,0 +1,103 @@ +#include "sample_wamr.hpp" + +#include +#include + +// Generated WAMR bindings for package: example:sample +// These symbol arrays can be used with wasm_runtime_register_natives_raw() +// NOTE: You must implement the functions declared in the imports namespace +// (See sample.hpp for declarations, provide implementations in your host code) + +using namespace cmcpp; + +// WAMR Native Symbol arrays organized by interface +// Register these with wasm_runtime_register_natives_raw(namespace, array, count) + +// Import interface: booleans +// Register with: wasm_runtime_register_natives_raw("example:sample/booleans", booleans_symbols, 1) +NativeSymbol booleans_symbols[] = { + host_function("and", host::booleans::and_), +}; + +// Import interface: floats +// Register with: wasm_runtime_register_natives_raw("example:sample/floats", floats_symbols, 1) +NativeSymbol floats_symbols[] = { + host_function("add", host::floats::add), +}; + +// Import interface: strings +// Register with: wasm_runtime_register_natives_raw("example:sample/strings", strings_symbols, 2) +NativeSymbol strings_symbols[] = { + host_function("reverse", host::strings::reverse), + host_function("lots", host::strings::lots), +}; + +// Import interface: lists +// Register with: wasm_runtime_register_natives_raw("example:sample/lists", lists_symbols, 1) +NativeSymbol lists_symbols[] = { + host_function("filter-bool", host::lists::filter_bool), +}; + +// Import interface: logging +// Register with: wasm_runtime_register_natives_raw("example:sample/logging", logging_symbols, 6) +NativeSymbol logging_symbols[] = { + host_function("log-bool", host::logging::log_bool), + host_function("log-u32", host::logging::log_u32), + host_function("log-u64", host::logging::log_u64), + host_function("log-f32", host::logging::log_f32), + host_function("log-f64", host::logging::log_f64), + host_function("log-str", host::logging::log_str), +}; + +// Import interface: void-func +// Register with: wasm_runtime_register_natives_raw("$root", void_func_symbols, 1) +NativeSymbol void_func_symbols[] = { + host_function("void-func", host::void_func), +}; + +// Get all import interfaces for registration +// Usage: +// for (const auto& reg : get_import_registrations()) { +// wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count); +// } +std::vector get_import_registrations() { + return { + {"example:sample/booleans", booleans_symbols, 1}, + {"example:sample/floats", floats_symbols, 1}, + {"example:sample/strings", strings_symbols, 2}, + {"example:sample/lists", lists_symbols, 1}, + {"example:sample/logging", logging_symbols, 6}, + {"$root", void_func_symbols, 1}, + }; +} + +// Helper function to register all import interfaces at once +// Returns the number of functions registered +int register_all_imports() { + int count = 0; + for (const auto& reg : get_import_registrations()) { + if (!wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count)) { + return -1; // Registration failed + } + count += reg.count; + } + return count; +} + +// Helper function to unregister all import interfaces +void unregister_all_imports() { + for (const auto& reg : get_import_registrations()) { + wasm_runtime_unregister_natives(reg.module_name, reg.symbols); + } +} + +// WASM file utilities +namespace wasm_utils { + +const uint32_t DEFAULT_STACK_SIZE = 8192; +const uint32_t DEFAULT_HEAP_SIZE = 8192; + +// Note: create_guest_realloc() and create_lift_lower_context() have been +// moved to in the cmcpp namespace and are available for use directly + +} // namespace wasm_utils diff --git a/samples/wamr/generated/sample_wamr.hpp b/samples/wamr/generated/sample_wamr.hpp new file mode 100644 index 0000000..12a63d5 --- /dev/null +++ b/samples/wamr/generated/sample_wamr.hpp @@ -0,0 +1,46 @@ +#ifndef GENERATED_WAMR_BINDINGS_HPP +#define GENERATED_WAMR_BINDINGS_HPP + +// Generated WAMR helper functions for package: example:sample +// This header provides utility functions for initializing and using WAMR with Component Model bindings + +#include +#include +#include "sample.hpp" + +#include +#include +#include + +// Forward declarations +struct NativeSymbol; +struct NativeRegistration { + const char* module_name; + NativeSymbol* symbols; + size_t count; +}; + +// Get all import interface registrations +// Returns a vector of all import interfaces that need to be registered with WAMR +std::vector get_import_registrations(); + +// Register all import interfaces at once +// Returns the number of functions registered, or -1 on failure +int register_all_imports(); + +// Unregister all import interfaces +void unregister_all_imports(); + +// WASM file utilities +namespace wasm_utils { + +// Default WAMR runtime configuration +extern const uint32_t DEFAULT_STACK_SIZE; +extern const uint32_t DEFAULT_HEAP_SIZE; + +} // namespace wasm_utils + +// Note: Helper functions create_guest_realloc() and create_lift_lower_context() +// are now available directly from in the cmcpp namespace + +#endif // GENERATED_WAMR_BINDINGS_HPP diff --git a/samples/wamr/host_impl.cpp b/samples/wamr/host_impl.cpp new file mode 100644 index 0000000..b17e786 --- /dev/null +++ b/samples/wamr/host_impl.cpp @@ -0,0 +1,119 @@ +#include "generated/sample.hpp" +#include +#include + +// Host implementations of the generated interface functions +// These are the actual implementations that the host provides for guest functions + +namespace host +{ + + namespace booleans + { + + cmcpp::bool_t and_(cmcpp::bool_t a, cmcpp::bool_t b) + { + std::cout << "[HOST] and(" << (a ? "true" : "false") << ", " << (b ? "true" : "false") << ") = " << (a && b ? "true" : "false") << std::endl; + return a && b; + } + + } // namespace booleans + + namespace floats + { + + cmcpp::float64_t add(cmcpp::float64_t a, cmcpp::float64_t b) + { + std::cout << "[HOST] add(" << a << ", " << b << ") = " << (a + b) << std::endl; + return a + b; + } + + } // namespace floats + + namespace strings + { + + cmcpp::string_t reverse(cmcpp::string_t a) + { + std::string result = a; + std::reverse(result.begin(), result.end()); + std::cout << "[HOST] reverse(\"" << a << "\") = \"" << result << "\"" << std::endl; + return result; + } + + uint32_t lots(cmcpp::string_t p1, cmcpp::string_t p2, cmcpp::string_t p3, cmcpp::string_t p4, + cmcpp::string_t p5, cmcpp::string_t p6, cmcpp::string_t p7, cmcpp::string_t p8, + cmcpp::string_t p9, cmcpp::string_t p10, cmcpp::string_t p11, cmcpp::string_t p12, + cmcpp::string_t p13, cmcpp::string_t p14, cmcpp::string_t p15, cmcpp::string_t p16, + cmcpp::string_t p17) + { + size_t total_length = p1.length() + p2.length() + p3.length() + p4.length() + p5.length() + + p6.length() + p7.length() + p8.length() + p9.length() + p10.length() + + p11.length() + p12.length() + p13.length() + p14.length() + p15.length() + + p16.length() + p17.length(); + std::cout << "[HOST] lots(...17 strings...) = " << total_length << std::endl; + return static_cast(total_length); + } + + } // namespace strings + + namespace lists + { + cmcpp::list_t filter_bool(cmcpp::list_t a) + { + std::cout << "[HOST] filter_bool([" << a.size() << " variants])" << std::endl; + cmcpp::list_t result; + for (const auto &variant : a) + { + if (std::holds_alternative(variant)) + { + result.push_back(std::get(variant)); + } + } + return result; + } + + } + + namespace logging + { + + void log_bool(cmcpp::bool_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": " << (a ? "true" : "false") << std::endl; + } + + void log_u32(uint32_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": " << a << std::endl; + } + + void log_u64(uint64_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": " << a << std::endl; + } + + void log_f32(cmcpp::float32_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": " << a << std::endl; + } + + void log_f64(cmcpp::float64_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": " << a << std::endl; + } + + void log_str(cmcpp::string_t a, cmcpp::string_t s) + { + std::cout << "[HOST LOG] " << s << ": \"" << a << "\"" << std::endl; + } + + } // namespace logging + + // Standalone function implementations (not in a sub-namespace) + void void_func() + { + std::cout << "[HOST] void_func() called" << std::endl; + } + +} // namespace host diff --git a/samples/wamr/main.cpp b/samples/wamr/main.cpp index f84a575..67c3058 100644 --- a/samples/wamr/main.cpp +++ b/samples/wamr/main.cpp @@ -1,4 +1,4 @@ -#include "wamr.hpp" +#include "generated/sample_wamr.hpp" // Includes wamr.hpp, sample.hpp and all helpers #include #include #include @@ -10,9 +10,7 @@ using namespace cmcpp; -// Configuration constants -constexpr uint32_t DEFAULT_STACK_SIZE = 8192; -constexpr uint32_t DEFAULT_HEAP_SIZE = 8192; +// WASM file location const std::filesystem::path WASM_RELATIVE_PATH = std::filesystem::path("bin") / "sample.wasm"; std::filesystem::path resolve_wasm_path(const std::filesystem::path &start_dir) @@ -74,59 +72,12 @@ char *read_wasm_binary_to_buffer(const std::filesystem::path &filename, uint32_t return buffer; } -void_t void_func() -{ - std::cout << "Hello, Void_Func!" << std::endl; -} -NativeSymbol root_symbol[] = { - host_function("void-func", void_func), -}; - -cmcpp::bool_t and_func(cmcpp::bool_t a, cmcpp::bool_t b) -{ - return a && b; -} -NativeSymbol booleans_symbol[] = { - host_function("and", and_func), -}; - -float64_t add(float64_t a, float64_t b) -{ - return a + b; -} -NativeSymbol floats_symbol[] = { - host_function("add", add), -}; - -string_t reverse(const string_t &a) -{ - std::string result = a; - std::transform(result.begin(), result.end(), result.begin(), ::toupper); - return result; -} -uint32_t lots(const string_t &p1, const string_t &p2, const string_t &p3, const string_t &p4, const string_t &p5, const string_t &p6, const string_t &p7, const string_t &p8, const string_t &p9, const string_t &p10, const string_t &p11, const string_t &p12, const string_t &p13, const string_t &p14, const string_t &p15, const string_t &p16, const string_t &p17) -{ - return p1.length() + p2.length() + p3.length() + p4.length() + p5.length() + p6.length() + p7.length() + p8.length() + p9.length() + p10.length() + p11.length() + p12.length() + p13.length() + p14.length() + p15.length() + p16.length() + p17.length(); -} -NativeSymbol strings_symbol[] = { - host_function("reverse", reverse), - host_function("lots", lots), -}; - -void_t log_u32(uint32_t a, string_t b) -{ - std::cout << "wasm-log: " << b << a << std::endl; -} -NativeSymbol logging_symbol[] = { - host_function("log-u32", log_u32), -}; - int main(int argc, char **argv) { static_cast(argc); - std::cout << "WAMR Component Model C++ Sample" << std::endl; - std::cout << "===============================" << std::endl; + std::cout << "WAMR Component Model C++ Sample (Using Generated Code)" << std::endl; + std::cout << "======================================================" << std::endl; std::cout << "Starting WAMR runtime initialization..." << std::endl; std::filesystem::path exe_dir = std::filesystem::current_path(); @@ -158,7 +109,7 @@ int main(int argc, char **argv) wasm_module_inst_t module_inst; wasm_function_inst_t cabi_realloc; wasm_exec_env_t exec_env; - uint32_t size, stack_size = DEFAULT_STACK_SIZE, heap_size = DEFAULT_HEAP_SIZE; + uint32_t size, stack_size = wasm_utils::DEFAULT_STACK_SIZE, heap_size = wasm_utils::DEFAULT_HEAP_SIZE; /* initialize the wasm runtime by default configurations */ wasm_runtime_init(); @@ -173,39 +124,21 @@ int main(int argc, char **argv) } std::cout << "Successfully loaded WASM file (" << size << " bytes)" << std::endl; - // /* add line below if we want to export native functions to WASM app */ - bool success = wasm_runtime_register_natives_raw("$root", root_symbol, sizeof(root_symbol) / sizeof(NativeSymbol)); - if (!success) - { - std::cerr << "Failed to register $root natives" << std::endl; - return 1; - } - success = wasm_runtime_register_natives_raw("example:sample/booleans", booleans_symbol, sizeof(booleans_symbol) / sizeof(NativeSymbol)); - if (!success) - { - std::cerr << "Failed to register booleans natives" << std::endl; - return 1; - } - success = wasm_runtime_register_natives_raw("example:sample/floats", floats_symbol, sizeof(floats_symbol) / sizeof(NativeSymbol)); - if (!success) - { - std::cerr << "Failed to register floats natives" << std::endl; - return 1; - } - success = wasm_runtime_register_natives_raw("example:sample/strings", strings_symbol, sizeof(strings_symbol) / sizeof(NativeSymbol)); - if (!success) - { - std::cerr << "Failed to register strings natives" << std::endl; - return 1; - } - success = wasm_runtime_register_natives_raw("example:sample/logging", logging_symbol, sizeof(logging_symbol) / sizeof(NativeSymbol)); - if (!success) + // Register native functions using generated code + // Note: We only register functions that are IMPORTED by the guest (what the host provides) + std::cout << "\n=== Registering Host Functions (Imports) ===" << std::endl; + + // Use the generated helper to register all interface imports + int registered_count = register_all_imports(); + if (registered_count < 0) { - std::cerr << "Failed to register logging natives" << std::endl; + std::cerr << "Failed to register import interfaces" << std::endl; return 1; } - // /* parse the WASM file from buffer and create a WASM module */ + std::cout << "\nTotal: " << (registered_count + 1) << " host functions registered (all imports from guest perspective)" << std::endl; + + // Parse the WASM file from buffer and create a WASM module module = wasm_runtime_load((uint8_t *)buffer, size, error_buf, sizeof(error_buf)); if (!module) { @@ -214,9 +147,9 @@ int main(int argc, char **argv) wasm_runtime_destroy(); return 1; } - std::cout << "Successfully loaded WASM module" << std::endl; + std::cout << "\nSuccessfully loaded WASM module" << std::endl; - // /* create an instance of the WASM module (WASM linear memory is ready) */ + // Create an instance of the WASM module (WASM linear memory is ready) module_inst = wasm_runtime_instantiate(module, stack_size, heap_size, error_buf, sizeof(error_buf)); if (!module_inst) { @@ -228,17 +161,6 @@ int main(int argc, char **argv) } std::cout << "Successfully instantiated WASM module" << std::endl; - wasm_memory_inst_t memory = wasm_runtime_lookup_memory(module_inst, "memory"); - if (!memory) - { - std::cerr << "Failed to lookup memory instance" << std::endl; - wasm_runtime_deinstantiate(module_inst); - wasm_runtime_unload(module); - delete[] buffer; - wasm_runtime_destroy(); - return 1; - } - cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc"); if (!cabi_realloc) { @@ -250,80 +172,70 @@ int main(int argc, char **argv) return 1; } - // auto cx = createCallContext(&heap, Encoding::Utf8); - exec_env = wasm_runtime_create_exec_env(module_inst, stack_size); - GuestRealloc realloc = [exec_env, cabi_realloc](int original_ptr, int original_size, int alignment, int new_size) -> int - { - uint32_t argv[4]; - argv[0] = original_ptr; - argv[1] = original_size; - argv[2] = alignment; - argv[3] = new_size; - wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv); - return argv[0]; - }; + // Use the helper from wamr.hpp to create the LiftLowerContext + LiftLowerContext liftLowerContext = cmcpp::create_lift_lower_context(module_inst, exec_env, cabi_realloc); - uint8_t *mem_start_addr = (uint8_t *)wasm_memory_get_base_address(memory); - uint8_t *mem_end_addr = NULL; - wasm_runtime_get_native_addr_range(module_inst, mem_start_addr, NULL, &mem_end_addr); - LiftLowerOptions opts(Encoding::Utf8, std::span(mem_start_addr, mem_end_addr - mem_start_addr), realloc); + std::cout << "\n=== Testing Guest Functions (Exports) ===" << std::endl; + std::cout << "Note: These are functions the GUEST implements, HOST calls them\n" + << std::endl; - LiftLowerContext liftLowerContext(trap, convert, opts); + // Using generated typedefs from sample_host.hpp for guest exports + std::cout << "\n--- Boolean Functions (Guest Export) ---" << std::endl; + auto call_and = guest_function(module_inst, exec_env, liftLowerContext, + "example:sample/booleans#and"); + std::cout << "call_and(false, false): " << call_and(false, false) << std::endl; + std::cout << "call_and(false, true): " << call_and(false, true) << std::endl; + std::cout << "call_and(true, false): " << call_and(true, false) << std::endl; + std::cout << "call_and(true, true): " << call_and(true, true) << std::endl; - std::cout << "\n=== Testing Guest Functions ===" << std::endl; + std::cout << "\n--- Float Functions ---" << std::endl; + auto call_add = guest_function(module_inst, exec_env, liftLowerContext, + "example:sample/floats#add"); + std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl; + std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl; + std::cout << "call_add(DBL_MAX, 0.0): " << call_add(DBL_MAX / 2, 0.0) << std::endl; + std::cout << "call_add(DBL_MAX / 2, DBL_MAX / 2): " << call_add(DBL_MAX / 2, DBL_MAX / 2) << std::endl; std::cout << "\n--- String Functions ---" << std::endl; - auto call_reverse = guest_function(module_inst, exec_env, liftLowerContext, - "example:sample/strings#reverse"); + auto call_reverse = guest_function(module_inst, exec_env, liftLowerContext, + "example:sample/strings#reverse"); auto call_reverse_result = call_reverse("Hello World!"); - std::cout << "call_reverse(\"Hello World!\"): " << call_reverse_result << std::endl; - std::cout << "call_reverse(call_reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl; + std::cout << "reverse(\"Hello World!\"): " << call_reverse_result << std::endl; + std::cout << "reverse(reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl; std::cout << "\n--- Variant Functions ---" << std::endl; - auto variant_func = guest_function(variant_t)>(module_inst, exec_env, liftLowerContext, "example:sample/variants#variant-func"); - std::cout << "variant_func((uint32_t)40)" << std::get<1>(variant_func((uint32_t)40)) << std::endl; - std::cout << "variant_func((bool_t)true)" << std::get<0>(variant_func((bool_t) true)) << std::endl; - std::cout << "variant_func((bool_t)false)" << std::get<0>(variant_func((bool_t) false)) << std::endl; + auto variant_func = guest_function( + module_inst, exec_env, liftLowerContext, "example:sample/variants#variant-func"); + std::cout << "variant_func((uint32_t)40): " << std::get<1>(variant_func((uint32_t)40)) << std::endl; + std::cout << "variant_func((bool_t)true): " << std::get<0>(variant_func((bool_t) true)) << std::endl; + std::cout << "variant_func((bool_t)false): " << std::get<0>(variant_func((bool_t) false)) << std::endl; std::cout << "\n--- Option Functions ---" << std::endl; - auto option_func = guest_function(option_t)>(module_inst, exec_env, liftLowerContext, "option-func"); - std::cout << "option_func((uint32_t)40).has_value()" << option_func((uint32_t)40).has_value() << std::endl; - std::cout << "option_func((uint32_t)40).value()" << option_func((uint32_t)40).value() << std::endl; - std::cout << "option_func(std::nullopt).has_value()" << option_func(std::nullopt).has_value() << std::endl; + auto option_func = guest_function( + module_inst, exec_env, liftLowerContext, "option-func"); + std::cout << "option_func((uint32_t)40).has_value(): " << option_func((uint32_t)40).has_value() << std::endl; + std::cout << "option_func((uint32_t)40).value(): " << option_func((uint32_t)40).value() << std::endl; + std::cout << "option_func(std::nullopt).has_value(): " << option_func(std::nullopt).has_value() << std::endl; std::cout << "\n--- Void Functions ---" << std::endl; - auto void_func = guest_function(module_inst, exec_env, liftLowerContext, "void-func"); - void_func(); + auto void_func_guest = guest_function(module_inst, exec_env, liftLowerContext, "void-func"); + void_func_guest(); std::cout << "\n--- Result Functions ---" << std::endl; - auto ok_func = guest_function(uint32_t, uint32_t)>(module_inst, exec_env, liftLowerContext, "ok-func"); - auto ok_func_result = ok_func(40, 2); - std::cout << "ok_func result: " << std::get(ok_func_result) << std::endl; + auto ok_func = guest_function( + module_inst, exec_env, liftLowerContext, "ok-func"); + auto ok_result = ok_func(40, 2); + std::cout << "ok_func result: " << std::get(ok_result) << std::endl; - auto err_func = guest_function(uint32_t, uint32_t)>(module_inst, exec_env, liftLowerContext, "err-func"); - auto err_func_result = err_func(40, 2); - std::cout << "err_func result: " << std::get(err_func_result) << std::endl; - - std::cout << "\n--- Boolean Functions ---" << std::endl; - auto call_and = guest_function(module_inst, exec_env, liftLowerContext, - "example:sample/booleans#and"); - std::cout << "call_and(false, false): " << call_and(false, false) << std::endl; - std::cout << "call_and(false, true): " << call_and(false, true) << std::endl; - std::cout << "call_and(true, false): " << call_and(true, false) << std::endl; - std::cout << "call_and(true, true): " << call_and(true, true) << std::endl; - - std::cout << "\n--- Float Functions ---" << std::endl; - auto call_add = guest_function(module_inst, exec_env, liftLowerContext, - "example:sample/floats#add"); - std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl; - std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl; - std::cout << "call_add(DBL_MAX, 0.0): " << call_add(DBL_MAX / 2, 0.0) << std::endl; - std::cout << "call_add(DBL_MAX / 2, DBL_MAX / 2): " << call_add(DBL_MAX / 2, DBL_MAX / 2) << std::endl; + auto err_func = guest_function( + module_inst, exec_env, liftLowerContext, "err-func"); + auto err_result = err_func(40, 2); + std::cout << "err_func result: " << std::get(err_result) << std::endl; std::cout << "\n--- Complex String Functions ---" << std::endl; - auto call_lots = guest_function( + auto call_lots = guest_function( module_inst, exec_env, liftLowerContext, "example:sample/strings#lots"); @@ -333,45 +245,34 @@ int main(int argc, char **argv) std::cout << "call_lots result: " << call_lots_result << std::endl; std::cout << "\n--- Tuple Functions ---" << std::endl; - func_t(tuple_t)> host_reverse_tuple = [](tuple_t a) -> tuple_t - { - return {std::get(a), std::get(a)}; - }; - // auto xxxx = host(tuple_t)>(liftLowerContext, - // "example:sample/tuples#reverse", - // host_reverse_tuple); - auto call_reverse_tuple = guest_function(tuple_t)>(module_inst, exec_env, liftLowerContext, - "example:sample/tuples#reverse"); + auto call_reverse_tuple = guest_function(module_inst, exec_env, liftLowerContext, + "example:sample/tuples#reverse"); auto call_reverse_tuple_result = call_reverse_tuple({false, "Hello World!"}); std::cout << "call_reverse_tuple({false, \"Hello World!\"}): " << std::get<0>(call_reverse_tuple_result) << ", " << std::get<1>(call_reverse_tuple_result) << std::endl; std::cout << "\n--- List Functions ---" << std::endl; - auto call_list_filter = guest_function(list_t>)>(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool"); + auto call_list_filter = guest_function(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool"); auto call_list_filter_result = call_list_filter({{false}, {"Hello World!"}, {"Another String"}, {true}, {false}}); std::cout << "call_list_filter result: " << call_list_filter_result.size() << std::endl; std::cout << "\n--- Enum Functions ---" << std::endl; - enum e - { - a, - b, - c - }; + using e = guest::enums::e; + + auto enum_func = guest_function(module_inst, exec_env, liftLowerContext, "example:sample/enums#enum-func"); + std::cout << "enum_func(e::a): " << enum_func(static_cast>(e::a)) << std::endl; + std::cout << "enum_func(e::b): " << enum_func(static_cast>(e::b)) << std::endl; + std::cout << "enum_func(e::c): " << enum_func(static_cast>(e::c)) << std::endl; - auto enum_func = guest_function(enum_t)>(module_inst, exec_env, liftLowerContext, "example:sample/enums#enum-func"); - std::cout << "enum_func(e::a): " << enum_func(e::a) << std::endl; - std::cout << "enum_func(e::b): " << enum_func(e::b) << std::endl; - std::cout << "enum_func(e::c): " << enum_func(e::c) << std::endl; + std::cout << "\n=== Test Complete ===" << std::endl; std::cout << "\n=== Cleanup and Summary ===" << std::endl; // Clean up resources delete[] buffer; wasm_runtime_destroy_exec_env(exec_env); - wasm_runtime_unregister_natives("$root", root_symbol); - wasm_runtime_unregister_natives("example:sample/booleans", booleans_symbol); - wasm_runtime_unregister_natives("example:sample/floats", floats_symbol); - wasm_runtime_unregister_natives("example:sample/strings", strings_symbol); - wasm_runtime_unregister_natives("example:sample/logging", logging_symbol); + + // Unregister all interface imports using the generated helper + unregister_all_imports(); + wasm_runtime_deinstantiate(module_inst); wasm_runtime_unload(module); wasm_runtime_destroy(); diff --git a/samples/wamr/run.sh b/samples/wamr/run.sh deleted file mode 100755 index bde3541..0000000 --- a/samples/wamr/run.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -# Run script for WAMR Component Model Sample -# Usage: ./run.sh [debug|gdb|valgrind] - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -BUILD_DIR="$PROJECT_ROOT/build" -WAMR_DIR="$BUILD_DIR/samples/wamr" -WAMR_BINARY="$WAMR_DIR/wamr" - -echo "WAMR Component Model Sample - Run Script" -echo "========================================" - -# Check if binary exists -if [[ ! -f "$WAMR_BINARY" ]]; then - echo "Error: WAMR binary not found at $WAMR_BINARY" - echo "Please run ./build.sh first" - exit 1 -fi - -# Check if WASM file exists -WASM_FILE="$BUILD_DIR/samples/bin/sample.wasm" -if [[ ! -f "$WASM_FILE" ]]; then - echo "Error: WASM file not found at $WASM_FILE" - echo "Please run ./build.sh first" - exit 1 -fi - -cd "$WAMR_DIR" - -case "${1:-normal}" in - "debug") - echo "Running with debug output..." - WAMR_LOG_LEVEL=5 "$WAMR_BINARY" - ;; - "gdb") - echo "Running with GDB debugger..." - gdb --args "$WAMR_BINARY" - ;; - "valgrind") - echo "Running with Valgrind..." - valgrind --leak-check=full --show-leak-kinds=all "$WAMR_BINARY" - ;; - "strace") - echo "Running with strace..." - strace -o wamr.trace "$WAMR_BINARY" - echo "Trace saved to wamr.trace" - ;; - *) - echo "Running WAMR sample..." - echo "Available WASM file: $(ls -lh ../bin/sample.wasm)" - echo "" - "$WAMR_BINARY" - ;; -esac diff --git a/samples/wasm/sample.wit b/samples/wasm/sample.wit index 05609e6..96772c7 100644 --- a/samples/wasm/sample.wit +++ b/samples/wasm/sample.wit @@ -77,4 +77,5 @@ world sample { import booleans; import floats; import strings; + import lists; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6c03f37..7974c1e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,3 +37,93 @@ add_test( COMMAND $ ) +# ===== Grammar Test Suite ===== +# Build the test executable that validates the WIT grammar against wit-bindgen test files +# This test is only built if BUILD_GRAMMAR is enabled and generate-grammar target exists + +# Check if grammar generation was configured (only if BUILD_GRAMMAR is ON) +if(BUILD_GRAMMAR AND TARGET generate-grammar) + message(STATUS "Building grammar test executable...") + + # Find ANTLR4 runtime (should have been found by grammar/CMakeLists.txt) + find_package(antlr4-runtime CONFIG) + if(NOT antlr4-runtime_FOUND) + message(WARNING "antlr4-runtime not found. Grammar test will not be built.") + return() + endif() + + # Determine which ANTLR4 library to use based on platform + # Windows uses shared library (DLL), Linux uses static library + if(TARGET antlr4_shared) + set(ANTLR4_LIBRARY antlr4_shared) + message(STATUS "Using ANTLR4 shared library for grammar tests") + elseif(TARGET antlr4_static) + set(ANTLR4_LIBRARY antlr4_static) + message(STATUS "Using ANTLR4 static library for grammar tests") + else() + message(FATAL_ERROR "Neither antlr4_shared nor antlr4_static targets are available") + endif() + + # Get the ANTLR generated code directory + set(ANTLR_OUTPUT_DIR "${CMAKE_BINARY_DIR}/grammar/grammar") + + # List the generated source files explicitly (they will be generated by generate-grammar target) + set(ANTLR_GENERATED_SOURCES + "${ANTLR_OUTPUT_DIR}/WitLexer.cpp" + "${ANTLR_OUTPUT_DIR}/WitParser.cpp" + "${ANTLR_OUTPUT_DIR}/WitVisitor.cpp" + "${ANTLR_OUTPUT_DIR}/WitBaseVisitor.cpp" + ) + + # Mark the generated sources as GENERATED so CMake doesn't check for them at configure time + set_source_files_properties(${ANTLR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) + + # Create the grammar test executable + add_executable(test-wit-grammar + test_grammar.cpp + ${ANTLR_GENERATED_SOURCES} + ) + + # Link against ANTLR4 runtime directly + target_link_libraries(test-wit-grammar + PRIVATE ${ANTLR4_LIBRARY} + ) + + # Set C++ standard for the test + target_compile_features(test-wit-grammar PUBLIC cxx_std_17) + + # Define the test directory path at compile time + target_compile_definitions(test-wit-grammar PRIVATE + WIT_TEST_DIR="${CMAKE_SOURCE_DIR}/ref/wit-bindgen/tests/codegen" + ) + + # Add include directories + target_include_directories(test-wit-grammar PRIVATE + ${CMAKE_BINARY_DIR}/grammar + ${CMAKE_BINARY_DIR}/grammar/grammar + ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/antlr4-runtime + ) + + # Depend on grammar generation + add_dependencies(test-wit-grammar generate-grammar) + + # Add the test + add_test( + NAME wit-grammar-test + COMMAND test-wit-grammar + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + + # Set test properties + set_tests_properties(wit-grammar-test PROPERTIES + LABELS "grammar" + TIMEOUT 120 + ) + + message(STATUS "Grammar test configuration:") + message(STATUS " Test executable: test-wit-grammar") + message(STATUS " Test directory: ref/wit-bindgen/tests/codegen") +else() + message(STATUS "Grammar library not built, skipping grammar tests") + message(STATUS " Enable with: cmake -DBUILD_GRAMMAR=ON") +endif() diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..c1c9e78 --- /dev/null +++ b/test/README.md @@ -0,0 +1,335 @@ +# Component Model C++ Tests + +This directory contains the test suite for the component-model-cpp library. + +## Test Components + +### 1. Component Model ABI Tests (`main.cpp`, `scratchpad.cpp`) + +Core tests for the WebAssembly Component Model canonical ABI implementation: +- Type marshaling (lift/lower operations) +- String encoding conversions +- List handling +- Record and variant types +- Resource management +- Async primitives (futures, streams, tasks) +- Error contexts + +**Running:** +```bash +cmake --build build +cd build && ctest -R component-model-test +# Or directly: +./build/test/component-model-test +``` + +### 2. WIT Grammar Tests (`test_grammar.cpp`) + +Validates the ANTLR4-based WIT parser against official test files from the [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) project. + +#### Requirements +- Java Runtime Environment (for ANTLR) +- CMake flag: `-DBUILD_GRAMMAR=ON` + +#### Quick Start + +**Linux/macOS:** +```bash +# 1. Configure CMake with grammar support +cmake -B build -DBUILD_GRAMMAR=ON + +# 2. Build the grammar and test executable +cmake --build build --target test-wit-grammar + +# 3. Run the tests +cd build && ctest -R wit-grammar-test --verbose +``` + +**Windows:** +```bash +# 1. Configure CMake with grammar support +cmake -B build -DBUILD_GRAMMAR=ON + +# 2. Build the grammar and test executable (specify configuration) +cmake --build build --target test-wit-grammar --config Release + +# 3. Run the tests (must specify -C configuration on Windows) +cd build && ctest -C Release -R wit-grammar-test --verbose +``` + +#### What Gets Tested + +The test suite validates the grammar against **95+ official WIT test files** including: + +**Basic Types** +- ✅ Integers (u8, u16, u32, u64, s8, s16, s32, s64) +- ✅ Floats (f32, f64) +- ✅ Strings and characters +- ✅ Booleans + +**Complex Types** +- ✅ Records (structs with named fields) +- ✅ Variants (tagged unions) +- ✅ Enums (variants without payloads) +- ✅ Flags (bitsets) +- ✅ Tuples +- ✅ Lists (including fixed-length lists) +- ✅ Options and Results + +**Async Features (Recently Added)** +- ✅ Async functions (`async func`) +- ✅ Futures (`future` and `future`) +- ✅ Streams (`stream` and `stream`) + +**Resources** +- ✅ Resource declarations +- ✅ Resource methods +- ✅ Static resource functions +- ✅ Constructors (infallible and fallible) +- ✅ Owned handles (`resource-name`) +- ✅ Borrowed handles (`borrow`) + +**Package & Interface Features** +- ✅ Package declarations with versions +- ✅ Nested namespaces and packages +- ✅ Interface definitions +- ✅ World definitions +- ✅ Import and export statements +- ✅ Use statements (local and cross-package) +- ✅ Include statements with renaming + +**Feature Gates** +- ✅ `@unstable(feature = name)` gates +- ✅ `@since(version = x.y.z)` gates +- ✅ `@deprecated(version = x.y.z)` gates + +**Real-World Examples** +- ✅ WASI specifications (wasi-clocks, wasi-filesystem, wasi-http, wasi-io) +- ✅ Error contexts +- ✅ Complex nested types +- ✅ Multiple interfaces and worlds per file + +#### Test Files Location + +The test suite uses files from: +``` +ref/wit-bindgen/tests/codegen/ +``` + +This directory is included as a git submodule and contains the canonical test suite maintained by the WebAssembly community. + +#### Running Grammar Tests + +**Method 1: Using CTest (Recommended)** + +```bash +# From the build directory +cd build +ctest -R wit-grammar-test + +# With verbose output +ctest -R wit-grammar-test --verbose + +# Run all tests including grammar +ctest +``` + +**Method 2: Direct Execution** + +```bash +# From project root +./build/test/test-wit-grammar + +# With verbose output (shows each file being parsed) +./build/test/test-wit-grammar --verbose + +# Custom test directory +./build/test/test-wit-grammar --directory /path/to/wit/files +``` + +**Method 3: Using VS Code** + +Use the VS Code debugger with the provided launch configurations: +- **`(gdb) test-wit-grammar`** - Run grammar tests on Linux +- **`(gdb) test-wit-grammar (verbose)`** - Run with verbose output on Linux +- **`(win) test-wit-grammar`** - Run grammar tests on Windows +- **`(win) test-wit-grammar (verbose)`** - Run with verbose output on Windows + +Press `F5` or use the Run and Debug panel (Ctrl+Shift+D) to select and run a configuration. + +#### Test Output + +**Success Output:** +``` +WIT Grammar Test Suite +====================== +Test directory: ../ref/wit-bindgen/tests/codegen + +Found 95 WIT files + +====================== +Test Results: + Total files: 95 + Successful: 95 + Failed: 0 + +✓ All tests passed! +``` + +**Failure Output:** +When parsing fails, detailed error messages are provided: +``` +Failed to parse: example.wit +Errors in example.wit: + line 5:10 mismatched input 'invalid' expecting {'u8', 'u16', ...} + +====================== +Test Results: + Total files: 95 + Successful: 94 + Failed: 1 + +Failed files: + - example.wit +``` + +#### Troubleshooting Grammar Tests + +**Java Not Found** +``` +Error: Java runtime not found. ANTLR4 requires Java to generate code. +``` +*Solution:* Install Java Runtime Environment (JRE) 8 or later. + +**ANTLR Jar Not Found** + +The CMake configuration automatically downloads ANTLR. If this fails: +```bash +# Manually download +wget https://www.antlr.org/download/antlr-4.13.2-complete.jar -O antlr-4.13.2-complete.jar +``` + +**Submodule Not Initialized** +``` +Error: wit-bindgen test files not found +``` +*Solution:* +```bash +git submodule update --init ref/wit-bindgen +``` + +**Build Errors** + +If the grammar library fails to build: +```bash +# Clean build and regenerate +rm -rf build +cmake -B build -DBUILD_GRAMMAR=ON +cmake --build build --target generate-grammar +cmake --build build --target test-wit-grammar +``` + +#### Grammar Coverage + +The test suite ensures complete coverage of the WIT specification: +- ✅ All keywords (as, async, bool, borrow, char, constructor, enum, export, f32, f64, flags, from, func, future, import, include, interface, list, option, own, package, record, resource, result, s8-s64, static, stream, string, tuple, type, u8-u64, use, variant, with, world) +- ✅ All operators (=, ,, :, ;, (, ), {, }, <, >, *, ->, /, ., @) +- ✅ Package versions (SemVer 2.0.0) +- ✅ Comments (line and block) +- ✅ Nested structures + +## Running All Tests + +```bash +# Build all tests +cmake --build build + +# Run all tests +cd build && ctest + +# Run with verbose output +cd build && ctest --verbose + +# Run with output on failure +cd build && ctest --output-on-failure +``` + +## Test Coverage + +When building with GCC or Clang, coverage instrumentation is automatically enabled. Generate coverage reports: + +```bash +# After running tests +cd build +lcov --capture --directory . --output-file coverage.info +lcov --remove coverage.info '/usr/*' '*/vcpkg_installed/*' '*/test/*' --output-file coverage_filtered.info +genhtml coverage_filtered.info --output-directory coverage_html +``` + +## Test Organization + +``` +test/ +├── main.cpp # Main test runner (doctest) +├── scratchpad.cpp # Component model ABI tests +├── host-util.hpp # Test utilities +├── host-util.cpp # Host function helpers +├── test_grammar.cpp # WIT grammar parser tests +├── CMakeLists.txt # Test build configuration +└── README.md # This file +``` + +VS Code launch configurations are available in `.vscode/launch.json` for running and debugging all tests. + +## Dependencies + +- **doctest**: C++ testing framework (from vcpkg) +- **ICU**: Unicode string handling (from vcpkg) +- **ANTLR4**: Grammar parser runtime (from vcpkg, for grammar tests) + +## Continuous Integration + +Tests are designed to run in CI/CD environments: + +```yaml +# Example GitHub Actions +- name: Run Tests + run: | + cmake -B build + cmake --build build + cd build && ctest --output-on-failure + +- name: Run Grammar Tests (if enabled) + run: | + cmake -B build -DBUILD_GRAMMAR=ON + cmake --build build --target test-wit-grammar + cd build && ctest -R wit-grammar-test --output-on-failure +``` + +## Adding New Tests + +### Component Model Tests + +Add test cases to `scratchpad.cpp` using doctest: + +```cpp +TEST_CASE("My new test") { + // Your test code + CHECK(expected == actual); +} +``` + +### Grammar Tests + +Grammar tests automatically pick up all `.wit` files in the test directory. To test additional files: + +```bash +./build/test/test-wit-grammar --directory /path/to/wit/files +``` + +## Related Documentation + +- [Main README](../README.md) - Project overview +- [Grammar README](../grammar/README.md) - Grammar documentation +- [Component Model Specification](../ref/component-model/design/mvp/CanonicalABI.md) +- [WIT Specification](../ref/component-model/design/mvp/WIT.md) diff --git a/test/TESTING_GRAMMAR.md b/test/TESTING_GRAMMAR.md new file mode 100644 index 0000000..bab750f --- /dev/null +++ b/test/TESTING_GRAMMAR.md @@ -0,0 +1,247 @@ +# WIT Grammar Testing Guide + +## Overview + +This directory contains a comprehensive test suite for the WIT (WebAssembly Interface Types) grammar implementation using ANTLR4. The tests validate the grammar against the official test suite from the [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) project. + +## Quick Start + +```bash +# 1. Configure CMake with grammar support +cmake -B build -DBUILD_GRAMMAR=ON + +# 2. Build the grammar and test executable +cmake --build build --target test-wit-grammar + +# 3. Run the tests +cd build && ctest -R wit-grammar-test --verbose +``` + +## What Gets Tested + +The test suite validates the grammar against **95+ official WIT test files** including: + +### Basic Types +- ✅ Integers (u8, u16, u32, u64, s8, s16, s32, s64) +- ✅ Floats (f32, f64) +- ✅ Strings and characters +- ✅ Booleans + +### Complex Types +- ✅ Records (structs with named fields) +- ✅ Variants (tagged unions) +- ✅ Enums (variants without payloads) +- ✅ Flags (bitsets) +- ✅ Tuples +- ✅ Lists (including fixed-length lists) +- ✅ Options and Results + +### Async Features (Recently Added) +- ✅ Async functions (`async func`) +- ✅ Futures (`future` and `future`) +- ✅ Streams (`stream` and `stream`) + +### Resources +- ✅ Resource declarations +- ✅ Resource methods +- ✅ Static resource functions +- ✅ Constructors (infallible and fallible) +- ✅ Owned handles (`resource-name`) +- ✅ Borrowed handles (`borrow`) + +### Package & Interface Features +- ✅ Package declarations with versions +- ✅ Nested namespaces and packages +- ✅ Interface definitions +- ✅ World definitions +- ✅ Import and export statements +- ✅ Use statements (local and cross-package) +- ✅ Include statements with renaming + +### Feature Gates +- ✅ `@unstable(feature = name)` gates +- ✅ `@since(version = x.y.z)` gates +- ✅ `@deprecated(version = x.y.z)` gates + +### Real-World Examples +- ✅ WASI specifications (wasi-clocks, wasi-filesystem, wasi-http, wasi-io) +- ✅ Error contexts +- ✅ Complex nested types +- ✅ Multiple interfaces and worlds per file + +## Test Files Location + +The test suite uses files from: +``` +ref/wit-bindgen/tests/codegen/ +``` + +This directory is included as a git submodule and contains the canonical test suite maintained by the WebAssembly community. + +## Running Tests + +### Method 1: Using CTest (Recommended) + +**Linux/macOS:** +```bash +# From the build directory +cd build +ctest -R wit-grammar-test + +# With verbose output +ctest -R wit-grammar-test --verbose + +# Run all tests including grammar +ctest --output-on-failure +``` + +**Windows (Visual Studio):** +```bash +# From the build directory +cd build + +# Specify configuration (Debug or Release) +ctest -C Release -R wit-grammar-test + +# With verbose output +ctest -C Release -R wit-grammar-test --verbose + +# Run all tests +ctest -C Release --output-on-failure +``` + +### Method 2: Direct Execution + +**Linux/macOS:** +```bash +# From project root +./build/test/test-wit-grammar + +# With verbose output (shows each file being parsed) +./build/test/test-wit-grammar --verbose + +# Custom test directory +./build/test/test-wit-grammar --directory /path/to/wit/files +``` + +**Windows:** +```cmd +REM From project root (adjust Debug/Release as needed) +.\build\test\Release\test-wit-grammar.exe + +REM With verbose output +.\build\test\Release\test-wit-grammar.exe --verbose + +REM Custom test directory +.\build\test\Release\test-wit-grammar.exe --directory C:\path\to\wit\files +``` + +## Test Output + +### Success Output +``` +WIT Grammar Test Suite +====================== +Test directory: ../ref/wit-bindgen/tests/codegen + +Found 95 WIT files + +====================== +Test Results: + Total files: 95 + Successful: 95 + Failed: 0 + +✓ All tests passed! +``` + +### Failure Output +When parsing fails, detailed error messages are provided: +``` +Failed to parse: example.wit +Errors in example.wit: + line 5:10 mismatched input 'invalid' expecting {'u8', 'u16', ...} + +====================== +Test Results: + Total files: 95 + Successful: 94 + Failed: 1 + +Failed files: + - example.wit +``` + +## Troubleshooting + +### Java Not Found +``` +Error: Java runtime not found. ANTLR4 requires Java to generate code. +``` +**Solution:** Install Java Runtime Environment (JRE) 8 or later. + +### ANTLR Jar Not Found +The CMake configuration automatically downloads ANTLR. If this fails: +```bash +# Manually download +wget https://www.antlr.org/download/antlr-4.13.2-complete.jar -O antlr-4.13.2-complete.jar +``` + +### Submodule Not Initialized +``` +Error: wit-bindgen test files not found +``` +**Solution:** +```bash +git submodule update --init ref/wit-bindgen +``` + +### Build Errors +If the grammar library fails to build: +```bash +# Clean build and regenerate +rm -rf build +cmake -B build -DBUILD_GRAMMAR=ON +cmake --build build --target generate-grammar +cmake --build build --target test-wit-grammar +``` + +## Continuous Integration + +The grammar tests can be integrated into CI/CD pipelines: + +```yaml +# Example GitHub Actions workflow +- name: Test WIT Grammar + run: | + cmake -B build -DBUILD_GRAMMAR=ON + cmake --build build --target test-wit-grammar + cd build && ctest -R wit-grammar-test --output-on-failure +``` + +## Adding New Test Files + +To test against additional WIT files: + +1. Add `.wit` files to any directory +2. Run with custom directory: + ```bash + ./build/grammar/test-wit-grammar --directory /path/to/your/wit/files + ``` + +## Grammar Coverage + +The test suite ensures complete coverage of the WIT specification: +- ✅ All keywords (as, async, bool, borrow, char, constructor, enum, export, f32, f64, flags, from, func, future, import, include, interface, list, option, own, package, record, resource, result, s8-s64, static, stream, string, tuple, type, u8-u64, use, variant, with, world) +- ✅ All operators (=, ,, :, ;, (, ), {, }, <, >, *, ->, /, ., @) +- ✅ Package versions (SemVer 2.0.0) +- ✅ Comments (line and block) +- ✅ Nested structures + +## Related Documentation + +- [WIT Specification](../ref/component-model/design/mvp/WIT.md) +- [Grammar File](../grammar/Wit.g4) +- [Grammar README](../grammar/README.md) +- [Project README](../README.md) +- [Test README](README.md) diff --git a/test/main.cpp b/test/main.cpp index 1a477c3..ec3f59c 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1894,14 +1894,14 @@ TEST_CASE("Heap Memory Layout - Python Reference Parity") // Test list of chars - UTF-32 code points as 32-bit values { - list_t chars = {U'A', U'B', U'c', U'🌍'}; + list_t chars = {U'A', U'B', U'c', 0x1F30D}; // 🌍 Earth Globe emoji store(*cx, chars, 900); auto loaded = load>(*cx, 900); CHECK(loaded.size() == 4); CHECK(loaded[0] == U'A'); CHECK(loaded[1] == U'B'); CHECK(loaded[2] == U'c'); - CHECK(loaded[3] == U'🌍'); // Verify Unicode support + CHECK(loaded[3] == 0x1F30D); // 🌍 Earth Globe emoji - Verify Unicode support } // Test list of tuples - verifies padding between tuple fields diff --git a/test/test_grammar.cpp b/test/test_grammar.cpp new file mode 100644 index 0000000..5ae4371 --- /dev/null +++ b/test/test_grammar.cpp @@ -0,0 +1,239 @@ +/** + * @file test_grammar.cpp + * @brief Test suite for WIT grammar against official wit-bindgen test files + * + * This test validates that our ANTLR4 WIT grammar can successfully parse + * all the WIT files in the wit-bindgen test suite at: + * ref/wit-bindgen/tests/codegen + */ + +#include +#include +#include +#include +#include +#include + +#include "antlr4-runtime.h" +#include "grammar/WitLexer.h" +#include "grammar/WitParser.h" + +namespace fs = std::filesystem; + +/** + * Custom error listener to collect parsing errors + */ +class ErrorListener : public antlr4::BaseErrorListener +{ +public: + std::vector errors; + + void syntaxError( + antlr4::Recognizer *recognizer, + antlr4::Token *offendingSymbol, + size_t line, + size_t charPositionInLine, + const std::string &msg, + std::exception_ptr e) override + { + + std::ostringstream oss; + oss << "line " << line << ":" << charPositionInLine << " " << msg; + errors.push_back(oss.str()); + } + + bool hasErrors() const + { + return !errors.empty(); + } + + void printErrors(const std::string &filename) const + { + std::cerr << "Errors in " << filename << ":\n"; + for (const auto &error : errors) + { + std::cerr << " " << error << "\n"; + } + } +}; + +/** + * Parse a single WIT file and return true if successful + */ +bool parseWitFile(const fs::path &filePath, bool verbose = false) +{ + try + { + // Read the file + std::ifstream stream(filePath); + if (!stream.is_open()) + { + std::cerr << "Failed to open file: " << filePath << "\n"; + return false; + } + + // Create ANTLR input stream + antlr4::ANTLRInputStream input(stream); + + // Create lexer + WitLexer lexer(&input); + ErrorListener lexerErrorListener; + lexer.removeErrorListeners(); + lexer.addErrorListener(&lexerErrorListener); + + // Create token stream + antlr4::CommonTokenStream tokens(&lexer); + + // Create parser + WitParser parser(&tokens); + ErrorListener parserErrorListener; + parser.removeErrorListeners(); + parser.addErrorListener(&parserErrorListener); + + // Parse the file + antlr4::tree::ParseTree *tree = parser.witFile(); + + // Check for errors + if (lexerErrorListener.hasErrors() || parserErrorListener.hasErrors()) + { + std::cerr << "Failed to parse: " << filePath.filename() << "\n"; + lexerErrorListener.printErrors(filePath.string()); + parserErrorListener.printErrors(filePath.string()); + return false; + } + + if (verbose) + { + std::cout << "✓ Successfully parsed: " << filePath.filename() << "\n"; + } + + return true; + } + catch (const std::exception &e) + { + std::cerr << "Exception parsing " << filePath << ": " << e.what() << "\n"; + return false; + } +} + +/** + * Recursively find all .wit files in a directory + */ +std::vector findWitFiles(const fs::path &directory) +{ + std::vector witFiles; + + if (!fs::exists(directory)) + { + std::cerr << "Directory does not exist: " << directory << "\n"; + return witFiles; + } + + for (const auto &entry : fs::recursive_directory_iterator(directory)) + { + if (entry.is_regular_file() && entry.path().extension() == ".wit") + { + witFiles.push_back(entry.path()); + } + } + + // Sort for consistent ordering + std::sort(witFiles.begin(), witFiles.end()); + + return witFiles; +} + +/** + * Main test function + */ +int main(int argc, char *argv[]) +{ + bool verbose = false; + + // Use compile-time defined path if available, otherwise use relative path +#ifdef WIT_TEST_DIR + std::string testDir = WIT_TEST_DIR; +#else + std::string testDir = "../ref/wit-bindgen/tests/codegen"; +#endif + + // Parse command line arguments + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + if (arg == "-v" || arg == "--verbose") + { + verbose = true; + } + else if (arg == "-d" || arg == "--directory") + { + if (i + 1 < argc) + { + testDir = argv[++i]; + } + } + else if (arg == "-h" || arg == "--help") + { + std::cout << "Usage: " << argv[0] << " [options]\n" + << "Options:\n" + << " -v, --verbose Print verbose output\n" + << " -d, --directory DIR Specify test directory (default: " << testDir << ")\n" + << " -h, --help Show this help message\n"; + return 0; + } + } + + std::cout << "WIT Grammar Test Suite\n"; + std::cout << "======================\n"; + std::cout << "Test directory: " << testDir << "\n\n"; + + // Find all WIT files + auto witFiles = findWitFiles(testDir); + + if (witFiles.empty()) + { + std::cerr << "No .wit files found in " << testDir << "\n"; + return 1; + } + + std::cout << "Found " << witFiles.size() << " WIT files\n\n"; + + // Parse all files + int successCount = 0; + int failureCount = 0; + std::vector failedFiles; + + for (const auto &file : witFiles) + { + if (parseWitFile(file, verbose)) + { + successCount++; + } + else + { + failureCount++; + failedFiles.push_back(file); + } + } + + // Print summary + std::cout << "\n======================\n"; + std::cout << "Test Results:\n"; + std::cout << " Total files: " << witFiles.size() << "\n"; + std::cout << " Successful: " << successCount << "\n"; + std::cout << " Failed: " << failureCount << "\n"; + + if (failureCount > 0) + { + std::cout << "\nFailed files:\n"; + for (const auto &file : failedFiles) + { + std::cout << " - " << file.filename() << "\n"; + } + std::cout << "\n"; + return 1; + } + + std::cout << "\n✓ All tests passed!\n"; + return 0; +} diff --git a/tmp.txt b/tmp.txt deleted file mode 100644 index 26cf5e0..0000000 --- a/tmp.txt +++ /dev/null @@ -1 +0,0 @@ -sudo apt install -y bison flex build-essential binutils-dev curl pkg-config libtool autotools-dev automake autoconf-archive \ No newline at end of file diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..cec7897 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,61 @@ +# WIT Code Generator Tool + +This directory contains the `wit-codegen` tool that uses the generated ANTLR WIT grammar library. + +## wit-codegen + +A code generator that reads WIT files and generates C++ host function bindings using cmcpp types. + +**Usage:** +```bash +./build/tools/wit-codegen/wit-codegen +``` + +**Example:** +```bash +./build/tools/wit-codegen/wit-codegen test-example.wit host_functions +``` + +This generates: +- `host_functions.hpp` - Header with function declarations +- `host_functions.cpp` - Implementation stubs with TODO comments +- `host_functions_bindings.cpp` - WAMR NativeSymbol bindings + +See [docs/wit-codegen.md](../docs/wit-codegen.md) for detailed documentation. + +## Structure + +``` +tools/ +└── wit-codegen/ + ├── CMakeLists.txt + └── wit-codegen.cpp # wit-codegen tool +``` + +## Building + +The tool is built automatically when you enable grammar generation: + +```bash +cd build +cmake -DBUILD_GRAMMAR=ON .. +cmake --build . --target wit-codegen +``` + +Or build everything: +```bash +cmake --build . +``` + +## Implementation Notes + +- The tool links directly against ANTLR4 runtime (platform-specific: `antlr4_shared` on Windows, `antlr4_static` on Linux) +- Generated ANTLR source files are compiled into the tool +- Depends on the `generate-grammar` CMake target to ensure grammar code is generated first +- Generated grammar headers are available via: + - `#include "grammar/WitLexer.h"` + - `#include "grammar/WitParser.h"` + - `#include "grammar/WitBaseVisitor.h"` +- The grammar code is generated in the build tree and not installed +- Uses `WitBaseVisitor` to implement custom visitor for parse tree traversal + diff --git a/tools/wit-codegen/CMakeLists.txt b/tools/wit-codegen/CMakeLists.txt new file mode 100644 index 0000000..2dc9826 --- /dev/null +++ b/tools/wit-codegen/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.10) + +# Find ANTLR4 runtime (should have been found by grammar/CMakeLists.txt) +find_package(antlr4-runtime CONFIG) +if(NOT antlr4-runtime_FOUND) + message(WARNING "antlr4-runtime not found. Tools will not be built.") + return() +endif() + +# Determine which ANTLR4 library to use based on platform +# Windows uses shared library (DLL), Linux uses static library +if(TARGET antlr4_shared) + set(ANTLR4_LIBRARY antlr4_shared) + message(STATUS "Using ANTLR4 shared library") +elseif(TARGET antlr4_static) + set(ANTLR4_LIBRARY antlr4_static) + message(STATUS "Using ANTLR4 static library") +else() + message(FATAL_ERROR "Neither antlr4_shared nor antlr4_static targets are available") +endif() + +# Get the ANTLR generated code directory +set(ANTLR_OUTPUT_DIR "${CMAKE_BINARY_DIR}/grammar/grammar") + +# List the generated source files explicitly (they will be generated by generate-grammar target) +set(ANTLR_GENERATED_SOURCES + "${ANTLR_OUTPUT_DIR}/WitLexer.cpp" + "${ANTLR_OUTPUT_DIR}/WitParser.cpp" + "${ANTLR_OUTPUT_DIR}/WitVisitor.cpp" + "${ANTLR_OUTPUT_DIR}/WitBaseVisitor.cpp" +) + +# Mark the generated sources as GENERATED so CMake doesn't check for them at configure time +set_source_files_properties(${ANTLR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) + +# WIT code generator using ANTLR grammar +add_executable(wit-codegen + wit-codegen.cpp + ${ANTLR_GENERATED_SOURCES} +) + +# Link against ANTLR4 runtime directly +target_link_libraries(wit-codegen PRIVATE + ${ANTLR4_LIBRARY} +) + +# Add include directories for ANTLR generated code +target_include_directories(wit-codegen PRIVATE + ${CMAKE_BINARY_DIR}/grammar + ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/antlr4-runtime +) + +# Depend on grammar generation +add_dependencies(wit-codegen generate-grammar) + +# Set C++ standard +target_compile_features(wit-codegen PRIVATE cxx_std_17) + diff --git a/tools/wit-codegen/wit-codegen.cpp b/tools/wit-codegen/wit-codegen.cpp new file mode 100644 index 0000000..b998a26 --- /dev/null +++ b/tools/wit-codegen/wit-codegen.cpp @@ -0,0 +1,1453 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "grammar/WitLexer.h" +#include "grammar/WitParser.h" +#include "grammar/WitBaseVisitor.h" + +using namespace antlr4; + +// Data structures for code generation +struct Parameter +{ + std::string name; + std::string type; +}; + +struct FunctionSignature +{ + std::string interface_name; + std::string name; + std::vector parameters; + std::vector results; + bool is_import = false; // Track if this is an import (for standalone functions) +}; + +// Type definition structures +struct VariantCase +{ + std::string name; + std::string type; // empty if no associated type +}; + +struct VariantDef +{ + std::string name; + std::vector cases; +}; + +struct EnumDef +{ + std::string name; + std::vector values; +}; + +struct RecordField +{ + std::string name; + std::string type; +}; + +struct RecordDef +{ + std::string name; + std::vector fields; +}; + +enum class InterfaceKind +{ + Import, // Host implements, guest calls + Export // Guest implements, host calls +}; + +struct InterfaceInfo +{ + std::string package_name; + std::string name; + InterfaceKind kind; + std::vector functions; + std::vector variants; + std::vector enums; + std::vector records; + bool is_standalone_function = false; // True if this is a synthetic interface for a world-level function +}; + +// Type mapper from WIT types to cmcpp types +class TypeMapper +{ +public: + static std::string mapType(const std::string &witType, const InterfaceInfo *iface = nullptr) + { + // Remove whitespace + std::string type = witType; + type.erase(std::remove_if(type.begin(), type.end(), ::isspace), type.end()); + + // Basic types + if (type == "bool") + return "cmcpp::bool_t"; + if (type == "u8") + return "uint8_t"; + if (type == "u16") + return "uint16_t"; + if (type == "u32") + return "uint32_t"; + if (type == "u64") + return "uint64_t"; + if (type == "s8") + return "int8_t"; + if (type == "s16") + return "int16_t"; + if (type == "s32") + return "int32_t"; + if (type == "s64") + return "int64_t"; + if (type == "f32") + return "cmcpp::float32_t"; + if (type == "f64") + return "cmcpp::float64_t"; + if (type == "char") + return "cmcpp::char_t"; + if (type == "string") + return "cmcpp::string_t"; + + // Check if it's a locally defined type in the current interface + if (iface) + { + // Check enums - wrap with cmcpp::enum_t<> + for (const auto &enumDef : iface->enums) + { + if (enumDef.name == type) + return "cmcpp::enum_t<" + enumDef.name + ">"; + } + // Check variants + for (const auto &variant : iface->variants) + { + if (variant.name == type) + return variant.name; // TODO: sanitize when type definitions are implemented + } + // Check records + for (const auto &record : iface->records) + { + if (record.name == type) + return record.name; // TODO: sanitize when type definitions are implemented + } + } + + // List types + if (type.find("list<") == 0) + { + size_t start = type.find('<') + 1; + size_t end = type.rfind('>'); + std::string innerType = type.substr(start, end - start); + return "cmcpp::list_t<" + mapType(innerType, iface) + ">"; + } + + // Option types + if (type.find("option<") == 0) + { + size_t start = type.find('<') + 1; + size_t end = type.rfind('>'); + std::string innerType = type.substr(start, end - start); + return "cmcpp::option_t<" + mapType(innerType, iface) + ">"; + } + + // Result types + if (type.find("result<") == 0) + { + size_t start = type.find('<') + 1; + size_t end = type.rfind('>'); + std::string innerTypes = type.substr(start, end - start); + size_t comma = innerTypes.find(','); + if (comma != std::string::npos) + { + std::string okType = mapType(innerTypes.substr(0, comma), iface); + std::string errType = mapType(innerTypes.substr(comma + 1), iface); + return "cmcpp::result_t<" + okType + ", " + errType + ">"; + } + else + { + return "cmcpp::result_t<" + mapType(innerTypes, iface) + ">"; + } + } + + // Tuple types + if (type.find("tuple<") == 0) + { + size_t start = type.find('<') + 1; + size_t end = type.rfind('>'); + std::string innerTypes = type.substr(start, end - start); + std::stringstream ss(innerTypes); + std::string item; + std::vector types; + while (std::getline(ss, item, ',')) + { + types.push_back(mapType(item, iface)); + } + std::string result = "cmcpp::tuple_t<"; + for (size_t i = 0; i < types.size(); ++i) + { + if (i > 0) + result += ", "; + result += types[i]; + } + result += ">"; + return result; + } + + // Unknown type - return as-is with warning + std::cerr << "Warning: Unknown type '" << type << "', using as-is" << std::endl; + return type; + } +}; + +// WIT Interface visitor that extracts functions using ANTLR grammar +class WitInterfaceVisitor : public WitBaseVisitor +{ +private: + std::vector &interfaces; + InterfaceInfo *currentInterface = nullptr; + std::string currentPackage; + std::set importedInterfaces; // Track which interfaces are imported in the world + std::set exportedInterfaces; // Track which interfaces are exported in the world + std::vector standaloneFunctions; // Standalone functions from world (not in interfaces) + +public: + WitInterfaceVisitor(std::vector &ifaces) : interfaces(ifaces) {} + + antlrcpp::Any visitPackageDecl(WitParser::PackageDeclContext *ctx) override + { + // Package format: package ns:pkg/name@version + // Extract just the package identifier (ns:pkg/name@version), not the "package" keyword + if (ctx) + { + std::string fullText = ctx->getText(); + // Remove "package" prefix + if (fullText.find("package") == 0) + { + fullText = fullText.substr(7); // Remove "package" + // Trim leading whitespace + size_t start = fullText.find_first_not_of(" \t\n\r"); + if (start != std::string::npos) + { + fullText = fullText.substr(start); + } + } + // Remove trailing semicolon if present + if (!fullText.empty() && fullText.back() == ';') + { + fullText.pop_back(); + } + currentPackage = fullText; + } + return visitChildren(ctx); + } + + // Visit import items in world to track which interfaces to generate + antlrcpp::Any visitImportItem(WitParser::ImportItemContext *ctx) override + { + // Check for "import id : externType" pattern (function import or inline interface) + if (ctx->id() && ctx->externType()) + { + std::string importName = ctx->id()->getText(); + + // Check if externType is a funcType (standalone function) + if (ctx->externType()->funcType()) + { + // This is a standalone function import + FunctionSignature func; + func.name = importName; + func.interface_name = ""; // Empty indicates standalone function + func.is_import = true; // Mark as import + + // Parse the function type + parseFuncType(ctx->externType()->funcType(), func); + + // Mark as imported + standaloneFunctions.push_back(func); + std::cout << " Found standalone import function: " << importName << std::endl; + } + else + { + // This is an inline interface import + importedInterfaces.insert(importName); + std::cout << " Found import: " << importName << std::endl; + } + } + // Check for "import usePath ;" pattern (interface import) + else if (ctx->usePath()) + { + std::string interfaceName = ctx->usePath()->getText(); + importedInterfaces.insert(interfaceName); + std::cout << " Found import: " << interfaceName << std::endl; + } + return visitChildren(ctx); + } + + // Visit export items in world to track which interfaces to generate + antlrcpp::Any visitExportItem(WitParser::ExportItemContext *ctx) override + { + // Check for "export id : externType" pattern (function export or inline interface) + if (ctx->id() && ctx->externType()) + { + std::string exportName = ctx->id()->getText(); + + // Check if externType is a funcType (standalone function) + if (ctx->externType()->funcType()) + { + // This is a standalone function export + FunctionSignature func; + func.name = exportName; + func.interface_name = ""; // Empty indicates standalone function + func.is_import = false; // Mark as export + + // Parse the function type + parseFuncType(ctx->externType()->funcType(), func); + + // Mark as exported + standaloneFunctions.push_back(func); + std::cout << " Found standalone export function: " << exportName << std::endl; + } + else + { + // This is an inline interface export + exportedInterfaces.insert(exportName); + std::cout << " Found export: " << exportName << std::endl; + } + } + // Check for "export usePath ;" pattern (interface export) + else if (ctx->usePath()) + { + std::string interfaceName = ctx->usePath()->getText(); + exportedInterfaces.insert(interfaceName); + std::cout << " Found export: " << interfaceName << std::endl; + } + return visitChildren(ctx); + } + + // Helper to parse funcType into a FunctionSignature + void parseFuncType(WitParser::FuncTypeContext *funcType, FunctionSignature &func) + { + if (!funcType) + return; + + // Get parameters from paramList -> namedTypeList + if (funcType->paramList() && funcType->paramList()->namedTypeList()) + { + auto namedTypeList = funcType->paramList()->namedTypeList(); + for (auto namedType : namedTypeList->namedType()) + { + if (namedType->id() && namedType->ty()) + { + Parameter param; + param.name = namedType->id()->getText(); + param.type = namedType->ty()->getText(); + func.parameters.push_back(param); + } + } + } + + // Get results from resultList + if (funcType->resultList() && funcType->resultList()->ty()) + { + func.results.push_back(funcType->resultList()->ty()->getText()); + } + } + + antlrcpp::Any visitInterfaceItem(WitParser::InterfaceItemContext *ctx) override + { + if (ctx->id()) + { + std::string interfaceName = ctx->id()->getText(); + + // Only add interface if it's imported (or if we haven't seen any world yet) + interfaces.emplace_back(); + currentInterface = &interfaces.back(); + currentInterface->name = interfaceName; + currentInterface->package_name = currentPackage; + } + return visitChildren(ctx); + } + + antlrcpp::Any visitFuncItem(WitParser::FuncItemContext *ctx) override + { + if (!currentInterface) + { + return nullptr; + } + + FunctionSignature func; + func.interface_name = currentInterface->name; + + // Get function name (id before ':') + if (ctx->id()) + { + func.name = ctx->id()->getText(); + } + + // Get function type (params and results) + if (ctx->funcType()) + { + parseFuncType(ctx->funcType(), func); + } + + currentInterface->functions.push_back(func); + return nullptr; + } + + // Visit variant type definitions + antlrcpp::Any visitVariantItems(WitParser::VariantItemsContext *ctx) override + { + if (!currentInterface || !ctx->id()) + { + return visitChildren(ctx); + } + + VariantDef variant; + variant.name = ctx->id()->getText(); + + // Parse variant cases recursively + if (ctx->variantCases()) + { + parseVariantCases(ctx->variantCases(), variant); + } + + currentInterface->variants.push_back(variant); + return visitChildren(ctx); + } + + // Helper to recursively parse variant cases + void parseVariantCases(WitParser::VariantCasesContext *ctx, VariantDef &variant) + { + if (!ctx) + return; + + // Get the first variant case + if (ctx->variantCase()) + { + VariantCase vcase; + auto variantCase = ctx->variantCase(); + + // Get case name (always present) + if (variantCase->id()) + { + vcase.name = variantCase->id()->getText(); + + // Check if the case has an associated type: id '(' ty ')' + if (variantCase->ty()) + { + vcase.type = variantCase->ty()->getText(); + } + + variant.cases.push_back(vcase); + } + } + + // Recursively parse remaining cases + if (ctx->variantCases()) + { + parseVariantCases(ctx->variantCases(), variant); + } + } + + // Visit enum type definitions + antlrcpp::Any visitEnumItems(WitParser::EnumItemsContext *ctx) override + { + if (!currentInterface || !ctx->id()) + { + return visitChildren(ctx); + } + + EnumDef enumDef; + enumDef.name = ctx->id()->getText(); + + // Parse enum values recursively + if (ctx->enumCases()) + { + parseEnumCases(ctx->enumCases(), enumDef); + } + + currentInterface->enums.push_back(enumDef); + return visitChildren(ctx); + } + + // Helper to recursively parse enum cases + void parseEnumCases(WitParser::EnumCasesContext *ctx, EnumDef &enumDef) + { + if (!ctx) + return; + + // Get the first enum case + if (ctx->id()) + { + enumDef.values.push_back(ctx->id()->getText()); + } + + // Recursively parse remaining cases + if (ctx->enumCases()) + { + parseEnumCases(ctx->enumCases(), enumDef); + } + } + + // Get the set of imported interfaces from the world + const std::set &getImportedInterfaces() const + { + return importedInterfaces; + } + + // Get the set of exported interfaces from the world + const std::set &getExportedInterfaces() const + { + return exportedInterfaces; + } + + // Get standalone functions from the world + const std::vector &getStandaloneFunctions() const + { + return standaloneFunctions; + } + + // Get the package name from the WIT file + const std::string &getPackageName() const + { + return currentPackage; + } +}; + +// Result of parsing a WIT file +struct ParseResult +{ + std::vector interfaces; + std::string packageName; +}; + +// Parse WIT file using ANTLR grammar +class WitGrammarParser +{ +public: + static ParseResult parse(const std::string &filename) + { + ParseResult result; + + try + { + std::ifstream stream(filename); + if (!stream.is_open()) + { + throw std::runtime_error("Cannot open file: " + filename); + } + + ANTLRInputStream input(stream); + WitLexer lexer(&input); + CommonTokenStream tokens(&lexer); + WitParser parser(&tokens); + + // Parse the file + WitParser::WitFileContext *tree = parser.witFile(); + + // Visit the parse tree to extract interfaces and functions + WitInterfaceVisitor visitor(result.interfaces); + visitor.visit(tree); + + // Get the package name + result.packageName = visitor.getPackageName(); + + // Categorize interfaces as imports or exports + auto importedInterfaces = visitor.getImportedInterfaces(); + auto exportedInterfaces = visitor.getExportedInterfaces(); + auto standaloneFunctions = visitor.getStandaloneFunctions(); + + // Process standalone functions by creating synthetic interfaces for them + if (!standaloneFunctions.empty()) + { + std::cout << "Processing standalone functions..." << std::endl; + for (const auto &func : standaloneFunctions) + { + // Create a synthetic interface for this standalone function + InterfaceInfo syntheticInterface; + syntheticInterface.name = func.name; // Use function name as interface name + syntheticInterface.package_name = result.packageName; + syntheticInterface.kind = func.is_import ? InterfaceKind::Import : InterfaceKind::Export; + syntheticInterface.is_standalone_function = true; // Mark as standalone + + // Add the function to this interface + FunctionSignature funcCopy = func; + funcCopy.interface_name = func.name; // Set interface name to match + syntheticInterface.functions.push_back(funcCopy); + + result.interfaces.push_back(syntheticInterface); + + std::cout << " " << func.name << " -> " + << (func.is_import ? "Import" : "Export") + << " (standalone function)" << std::endl; + } + } + + if (!importedInterfaces.empty() || !exportedInterfaces.empty()) + { + std::cout << "Categorizing interfaces..." << std::endl; + std::vector expandedInterfaces; + + for (auto &iface : result.interfaces) + { + // Skip standalone functions - they're already categorized + if (iface.is_standalone_function) + { + expandedInterfaces.push_back(iface); + continue; + } + + bool isImport = importedInterfaces.find(iface.name) != importedInterfaces.end(); + bool isExport = exportedInterfaces.find(iface.name) != exportedInterfaces.end(); + + if (isImport && isExport) + { + // Interface is both imported and exported - create two copies + std::cout << " " << iface.name << " -> Import + Export (bidirectional)" << std::endl; + + InterfaceInfo importCopy = iface; + importCopy.kind = InterfaceKind::Import; + expandedInterfaces.push_back(importCopy); + + InterfaceInfo exportCopy = iface; + exportCopy.kind = InterfaceKind::Export; + expandedInterfaces.push_back(exportCopy); + } + else if (isImport) + { + iface.kind = InterfaceKind::Import; + std::cout << " " << iface.name << " -> Import (host implements)" << std::endl; + expandedInterfaces.push_back(iface); + } + else if (isExport) + { + iface.kind = InterfaceKind::Export; + std::cout << " " << iface.name << " -> Export (guest implements)" << std::endl; + expandedInterfaces.push_back(iface); + } + else + { + // Default to export if not explicitly imported + iface.kind = InterfaceKind::Export; + std::cout << " " << iface.name << " -> Export (default)" << std::endl; + expandedInterfaces.push_back(iface); + } + } + + result.interfaces = expandedInterfaces; + } + } + catch (const std::exception &e) + { + throw std::runtime_error("Parse error: " + std::string(e.what())); + } + + return result; + } +}; + +// Code generator for C++ host functions +class CodeGenerator +{ +public: + static void generateHeader(const std::vector &interfaces, const std::string &filename) + { + std::ofstream out(filename); + if (!out.is_open()) + { + throw std::runtime_error("Cannot create header file: " + filename); + } + + // Generate header guard + std::string guard = "GENERATED_" + sanitize_identifier(filename) + "_HPP"; + std::transform(guard.begin(), guard.end(), guard.begin(), ::toupper); + + out << "#pragma once\n\n"; + out << "#ifndef " << guard << "\n"; + out << "#define " << guard << "\n\n"; + out << "#include \n\n"; + out << "// Generated host function declarations from WIT\n"; + out << "// - 'host' namespace: Guest imports (host implements these)\n"; + out << "// - 'guest' namespace: Guest exports (guest implements these, host calls them)\n\n"; + + // Separate interfaces by kind + std::vector imports; + std::vector exports; + + for (const auto &iface : interfaces) + { + if (iface.kind == InterfaceKind::Import) + { + imports.push_back(&iface); + } + else + { + exports.push_back(&iface); + } + } + + std::cout << "Generating header with " << imports.size() << " imports and " << exports.size() << " exports" << std::endl; + for (const auto *iface : imports) + { + std::cout << " Import: " << iface->name << std::endl; + } + for (const auto *iface : exports) + { + std::cout << " Export: " << iface->name << std::endl; + } + + // Generate host namespace (host implements) + if (!imports.empty()) + { + out << "namespace host {\n\n"; + + for (const auto *iface : imports) + { + // Check if this is a standalone function (world-level function, not in an interface) + if (iface->is_standalone_function) + { + // Generate standalone function directly in host namespace without sub-namespace + out << "// Standalone function: " << iface->name << "\n"; + if (!iface->package_name.empty()) + { + out << "// Package: " << iface->package_name << "\n"; + } + + for (const auto &func : iface->functions) + { + generateFunctionDeclaration(out, func, iface); + } + out << "\n"; + } + else + { + // Regular interface - create namespace + out << "// Interface: " << iface->name << "\n"; + if (!iface->package_name.empty()) + { + out << "// Package: " << iface->package_name << "\n"; + } + out << "namespace " << sanitize_identifier(iface->name) << " {\n\n"; + + // Generate type definitions first + generateTypeDefinitions(out, *iface); + + // Then generate function declarations + for (const auto &func : iface->functions) + { + generateFunctionDeclaration(out, func, iface); + } + + out << "} // namespace " << sanitize_identifier(iface->name) << "\n\n"; + } + } + + out << "} // namespace host\n\n"; + } + + // Generate guest namespace (guest implements, host calls) + if (!exports.empty()) + { + out << "namespace guest {\n\n"; + + for (const auto *iface : exports) + { + // Check if this is a standalone function (world-level function, not in an interface) + if (iface->is_standalone_function) + { + // Generate standalone function directly in guest namespace without sub-namespace + out << "// Standalone function: " << iface->name << "\n"; + if (!iface->package_name.empty()) + { + out << "// Package: " << iface->package_name << "\n"; + } + + for (const auto &func : iface->functions) + { + generateGuestFunctionTypeAlias(out, func, iface); + } + out << "\n"; + } + else + { + // Regular interface - create namespace + out << "// Interface: " << iface->name << "\n"; + if (!iface->package_name.empty()) + { + out << "// Package: " << iface->package_name << "\n"; + } + out << "namespace " << sanitize_identifier(iface->name) << " {\n\n"; + + // Generate type definitions first + generateTypeDefinitions(out, *iface); + + // Then generate function type aliases for guest functions + for (const auto &func : iface->functions) + { + generateGuestFunctionTypeAlias(out, func, iface); + } + + out << "} // namespace " << sanitize_identifier(iface->name) << "\n\n"; + } + } + + out << "} // namespace guest\n\n"; + } + + out << "#endif // " << guard << "\n"; + } + + // NOTE: This function generates stub implementations but is intentionally not called. + // Host applications are responsible for providing their own implementations of the + // declared functions. This function is kept for reference or optional stub generation. + static void generateImplementation(const std::vector &interfaces, const std::string &filename, const std::string &headerName) + { + std::ofstream out(filename); + if (!out.is_open()) + { + throw std::runtime_error("Cannot create implementation file: " + filename); + } + + // Extract just the filename from the header path for the include directive + std::string headerBasename = headerName.substr(headerName.find_last_of("/\\") + 1); + out << "#include \"" << headerBasename << "\"\n\n"; + out << "// Generated host function implementations (stubs)\n\n"; + + out << "namespace host {\n\n"; + + for (const auto &iface : interfaces) + { + out << "// Interface: " << iface.name << "\n"; + out << "namespace " << sanitize_identifier(iface.name) << " {\n\n"; + + for (const auto &func : iface.functions) + { + generateFunctionImplementation(out, func, &iface); + } + + out << "} // namespace " << sanitize_identifier(iface.name) << "\n\n"; + } + + out << "} // namespace host\n"; + } + + static void generateWAMRBindings(const std::vector &interfaces, const std::string &filename, const std::string &packageName, const std::string &headerFile, const std::string &wamrHeaderFile) + { + std::ofstream out(filename); + if (!out.is_open()) + { + throw std::runtime_error("Cannot create bindings file: " + filename); + } + + out << "#include \"" << wamrHeaderFile << "\"\n\n"; + out << "#include \n"; + out << "#include \n\n"; + out << "// Generated WAMR bindings for package: " << packageName << "\n"; + out << "// These symbol arrays can be used with wasm_runtime_register_natives_raw()\n"; + out << "// NOTE: You must implement the functions declared in the imports namespace\n"; + out << "// (See " << headerFile << " for declarations, provide implementations in your host code)\n\n"; + + out << "using namespace cmcpp;\n\n"; + + // Group interfaces by kind (Import vs Export) + std::vector importInterfaces; + std::vector exportInterfaces; + + for (const auto &iface : interfaces) + { + if (iface.kind == InterfaceKind::Import) + { + importInterfaces.push_back(&iface); + } + else + { + exportInterfaces.push_back(&iface); + } + } + + out << "// WAMR Native Symbol arrays organized by interface\n"; + out << "// Register these with wasm_runtime_register_natives_raw(namespace, array, count)\n\n"; + + // Generate symbol arrays for each IMPORT interface + for (const auto *iface : importInterfaces) + { + std::string arrayName = sanitize_identifier(iface->name) + "_symbols"; + std::string moduleName = iface->is_standalone_function ? "$root" : (packageName + "/" + iface->name); + + out << "// Import interface: " << iface->name << "\n"; + out << "// Register with: wasm_runtime_register_natives_raw(\"" << moduleName << "\", " << arrayName << ", " << iface->functions.size() << ")\n"; + out << "NativeSymbol " << arrayName << "[] = {\n"; + + for (const auto &func : iface->functions) + { + std::string funcName = sanitize_identifier(func.name); + + // For standalone functions, reference directly in host namespace + // For interface functions, reference within their interface namespace + if (iface->is_standalone_function) + { + out << " host_function(\"" << func.name << "\", host::" << funcName << "),\n"; + } + else + { + out << " host_function(\"" << func.name << "\", host::" << sanitize_identifier(iface->name) << "::" << funcName << "),\n"; + } + } + + out << "};\n\n"; + } + + // Add a helper function that returns all symbol arrays + out << "// Get all import interfaces for registration\n"; + out << "// Usage:\n"; + out << "// for (const auto& reg : get_import_registrations()) {\n"; + out << "// wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count);\n"; + out << "// }\n"; + out << "std::vector get_import_registrations() {\n"; + out << " return {\n"; + + for (const auto *iface : importInterfaces) + { + std::string arrayName = sanitize_identifier(iface->name) + "_symbols"; + std::string moduleName = iface->is_standalone_function ? "$root" : (packageName + "/" + iface->name); + out << " {\"" << moduleName << "\", " << arrayName << ", " << iface->functions.size() << "},\n"; + } + + out << " };\n"; + out << "}\n\n"; + + // Add helper functions for common WAMR operations + out << "// Helper function to register all import interfaces at once\n"; + out << "// Returns the number of functions registered\n"; + out << "int register_all_imports() {\n"; + out << " int count = 0;\n"; + out << " for (const auto& reg : get_import_registrations()) {\n"; + out << " if (!wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count)) {\n"; + out << " return -1; // Registration failed\n"; + out << " }\n"; + out << " count += reg.count;\n"; + out << " }\n"; + out << " return count;\n"; + out << "}\n\n"; + + out << "// Helper function to unregister all import interfaces\n"; + out << "void unregister_all_imports() {\n"; + out << " for (const auto& reg : get_import_registrations()) {\n"; + out << " wasm_runtime_unregister_natives(reg.module_name, reg.symbols);\n"; + out << " }\n"; + out << "}\n\n"; + + // WASM utilities namespace + out << "// WASM file utilities\n"; + out << "namespace wasm_utils {\n\n"; + + out << "const uint32_t DEFAULT_STACK_SIZE = 8192;\n"; + out << "const uint32_t DEFAULT_HEAP_SIZE = 8192;\n\n"; + + out << "// Note: create_guest_realloc() and create_lift_lower_context() have been\n"; + out << "// moved to in the cmcpp namespace and are available for use directly\n\n"; + + out << "} // namespace wasm_utils\n"; + } + + static void generateWAMRHeader(const std::vector &interfaces, const std::string &filename, const std::string &packageName, const std::string &sampleHeaderFile) + { + std::ofstream out(filename); + if (!out.is_open()) + { + throw std::runtime_error("Cannot create WAMR header file: " + filename); + } + + // Header guard + std::string guard = "GENERATED_WAMR_BINDINGS_HPP"; + out << "#ifndef " << guard << "\n"; + out << "#define " << guard << "\n\n"; + + out << "// Generated WAMR helper functions for package: " << packageName << "\n"; + out << "// This header provides utility functions for initializing and using WAMR with Component Model bindings\n\n"; + + out << "#include \n"; + out << "#include \n"; + out << "#include \"" << sampleHeaderFile << "\"\n\n"; + out << "#include \n"; + out << "#include \n"; + out << "#include \n\n"; + + // Forward declarations + out << "// Forward declarations\n"; + out << "struct NativeSymbol;\n"; + out << "struct NativeRegistration {\n"; + out << " const char* module_name;\n"; + out << " NativeSymbol* symbols;\n"; + out << " size_t count;\n"; + out << "};\n\n"; + + // Function declarations + out << "// Get all import interface registrations\n"; + out << "// Returns a vector of all import interfaces that need to be registered with WAMR\n"; + out << "std::vector get_import_registrations();\n\n"; + + out << "// Register all import interfaces at once\n"; + out << "// Returns the number of functions registered, or -1 on failure\n"; + out << "int register_all_imports();\n\n"; + + out << "// Unregister all import interfaces\n"; + out << "void unregister_all_imports();\n\n"; + + // WASM utilities namespace with constants + out << "// WASM file utilities\n"; + out << "namespace wasm_utils {\n\n"; + + out << "// Default WAMR runtime configuration\n"; + out << "extern const uint32_t DEFAULT_STACK_SIZE;\n"; + out << "extern const uint32_t DEFAULT_HEAP_SIZE;\n\n"; + + out << "} // namespace wasm_utils\n\n"; + + out << "// Note: Helper functions create_guest_realloc() and create_lift_lower_context()\n"; + out << "// are now available directly from in the cmcpp namespace\n\n"; + + out << "#endif // " << guard << "\n"; + } + +private: + // Generate type definitions (variants, enums, records) + static void generateTypeDefinitions(std::ofstream &out, const InterfaceInfo &iface) + { + // Generate enums + for (const auto &enumDef : iface.enums) + { + out << "enum class " << sanitize_identifier(enumDef.name) << " {\n"; + for (size_t i = 0; i < enumDef.values.size(); ++i) + { + out << " " << sanitize_identifier(enumDef.values[i]); + if (i < enumDef.values.size() - 1) + out << ","; + out << "\n"; + } + out << "};\n\n"; + } + + // Generate variants + for (const auto &variant : iface.variants) + { + out << "using " << sanitize_identifier(variant.name) << " = cmcpp::variant_t<"; + for (size_t i = 0; i < variant.cases.size(); ++i) + { + if (i > 0) + out << ", "; + if (variant.cases[i].type.empty()) + { + // Case with no payload - use monostate or unit type + out << "cmcpp::monostate"; + } + else + { + out << TypeMapper::mapType(variant.cases[i].type, &iface); + } + } + out << ">;\n\n"; + } + + // Generate records + for (const auto &record : iface.records) + { + out << "struct " << sanitize_identifier(record.name) << " {\n"; + for (const auto &field : record.fields) + { + out << " " << TypeMapper::mapType(field.type, &iface) << " " << sanitize_identifier(field.name) << ";\n"; + } + out << "};\n\n"; + } + } + + static void generateFunctionDeclaration(std::ofstream &out, const FunctionSignature &func, const InterfaceInfo *iface) + { + // Determine return type + std::string return_type = "void"; + if (func.results.size() == 1) + { + return_type = TypeMapper::mapType(func.results[0], iface); + } + else if (func.results.size() > 1) + { + // Multiple results become a tuple + return_type = "cmcpp::tuple_t<"; + for (size_t i = 0; i < func.results.size(); ++i) + { + if (i > 0) + return_type += ", "; + return_type += TypeMapper::mapType(func.results[i], iface); + } + return_type += ">"; + } + + // Generate host function declaration + out << return_type << " " << sanitize_identifier(func.name) << "("; + + // Parameters + for (size_t i = 0; i < func.parameters.size(); ++i) + { + if (i > 0) + out << ", "; + out << TypeMapper::mapType(func.parameters[i].type, iface) << " " << sanitize_identifier(func.parameters[i].name); + } + + out << ");\n\n"; + } + + static void generateGuestFunctionTypeAlias(std::ofstream &out, const FunctionSignature &func, const InterfaceInfo *iface) + { + // Check if function uses unknown local types (variant/enum/record) + // Now that we parse these types, we can check if they're actually defined + auto has_unknown_type = [iface](const std::string &type_str) + { + if (!iface) + return false; + + // Check if the type string contains single lowercase letters that might be local types + std::string trimmed = type_str; + trimmed.erase(std::remove_if(trimmed.begin(), trimmed.end(), ::isspace), trimmed.end()); + + // Extract potential local type names (single lowercase letters) + std::vector potential_types; + + // Simple heuristic: look for single letter types + if (trimmed.length() == 1 && std::islower(trimmed[0])) + { + potential_types.push_back(trimmed); + } + // Check for single letter types inside angle brackets (generic types like list) + for (size_t i = 0; i + 2 < trimmed.length(); ++i) + { + if (trimmed[i] == '<' && std::islower(trimmed[i + 1]) && trimmed[i + 2] == '>') + { + potential_types.push_back(std::string(1, trimmed[i + 1])); + } + } + + // Check if any potential local type is NOT defined in the interface + for (const auto &potential_type : potential_types) + { + bool found = false; + + // Check in enums + for (const auto &enumDef : iface->enums) + { + if (enumDef.name == potential_type) + { + found = true; + break; + } + } + + // Check in variants + if (!found) + { + for (const auto &variant : iface->variants) + { + if (variant.name == potential_type) + { + found = true; + break; + } + } + } + + // Check in records + if (!found) + { + for (const auto &record : iface->records) + { + if (record.name == potential_type) + { + found = true; + break; + } + } + } + + // If not found in any defined types, it's unknown + if (!found) + { + return true; + } + } + + return false; + }; + + bool skip = false; + for (const auto ¶m : func.parameters) + { + if (has_unknown_type(param.type)) + { + skip = true; + break; + } + } + for (const auto &result : func.results) + { + if (has_unknown_type(result)) + { + skip = true; + break; + } + } + + if (skip) + { + out << "// TODO: " << func.name << " - Type definitions for local types (variant/enum/record) not yet parsed\n"; + out << "// Will be available in a future update\n\n"; + return; + } + + // Determine return type + std::string return_type = "void"; + if (func.results.size() == 1) + { + return_type = TypeMapper::mapType(func.results[0], iface); + } + else if (func.results.size() > 1) + { + // Multiple results become a tuple + return_type = "cmcpp::tuple_t<"; + for (size_t i = 0; i < func.results.size(); ++i) + { + if (i > 0) + return_type += ", "; + return_type += TypeMapper::mapType(func.results[i], iface); + } + return_type += ">"; + } + + // Generate only the function signature typedef for guest functions + // Note: No need to sanitize the function name here since _t suffix prevents keyword conflicts + std::string type_alias_name = func.name; + std::replace(type_alias_name.begin(), type_alias_name.end(), '-', '_'); + std::replace(type_alias_name.begin(), type_alias_name.end(), '.', '_'); + + out << "// Guest function signature for use with guest_function<" << type_alias_name << "_t>()\n"; + out << "using " << type_alias_name << "_t = " << return_type << "("; + + // Parameters for function signature + for (size_t i = 0; i < func.parameters.size(); ++i) + { + if (i > 0) + out << ", "; + out << TypeMapper::mapType(func.parameters[i].type, iface); + } + + out << ");\n\n"; + } + + static void generateFunctionImplementation(std::ofstream &out, const FunctionSignature &func, const InterfaceInfo *iface) + { + // Determine return type + std::string return_type = "void"; + if (func.results.size() == 1) + { + return_type = TypeMapper::mapType(func.results[0], iface); + } + else if (func.results.size() > 1) + { + return_type = "cmcpp::tuple_t<"; + for (size_t i = 0; i < func.results.size(); ++i) + { + if (i > 0) + return_type += ", "; + return_type += TypeMapper::mapType(func.results[i], iface); + } + return_type += ">"; + } + + out << return_type << " " << sanitize_identifier(func.name) << "("; + + // Parameters + for (size_t i = 0; i < func.parameters.size(); ++i) + { + if (i > 0) + out << ", "; + out << TypeMapper::mapType(func.parameters[i].type, iface) << " " << sanitize_identifier(func.parameters[i].name); + } + + out << ") {\n"; + out << " // TODO: Implement " << func.name << "\n"; + + if (return_type != "void") + { + out << " return {}; // Return default value\n"; + } + + out << "}\n\n"; + } + + static std::string sanitize_identifier(const std::string &name) + { + std::string result = name; + std::replace(result.begin(), result.end(), '-', '_'); + std::replace(result.begin(), result.end(), '.', '_'); + std::replace(result.begin(), result.end(), ':', '_'); + std::replace(result.begin(), result.end(), '/', '_'); + + // Handle C++ keywords by appending underscore + static const std::set keywords = { + "and", "or", "not", "xor", "bool", "char", "int", "float", "double", + "void", "return", "if", "else", "while", "for", "do", "switch", + "case", "default", "break", "continue", "namespace", "class", "struct", + "enum", "union", "typedef", "using", "public", "private", "protected", + "virtual", "override", "final", "const", "static", "extern", "inline"}; + + if (keywords.find(result) != keywords.end()) + { + result += "_"; + } + + return result; + } +}; + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + std::cerr << "wit-codegen - WebAssembly Interface Types (WIT) Code Generator\n\n"; + std::cerr << "USAGE:\n"; + std::cerr << " " << argv[0] << " [output-prefix]\n\n"; + std::cerr << "ARGUMENTS:\n"; + std::cerr << " Path to the WIT file to parse\n"; + std::cerr << " [output-prefix] Optional output file prefix (default: derived from package name)\n\n"; + std::cerr << "DESCRIPTION:\n"; + std::cerr << " Generates C++ host function bindings from WebAssembly Interface Types (WIT)\n"; + std::cerr << " files. The tool parses WIT syntax and generates type-safe C++ code for\n"; + std::cerr << " interfacing with WebAssembly components.\n\n"; + std::cerr << "GENERATED FILES:\n"; + std::cerr << " .hpp - C++ header with type definitions and declarations\n"; + std::cerr << " _wamr.hpp - WAMR runtime integration header\n"; + std::cerr << " _wamr.cpp - WAMR binding implementation with NativeSymbol arrays\n\n"; + std::cerr << "FEATURES:\n"; + std::cerr << " - Supports all Component Model types (primitives, strings, lists, records,\n"; + std::cerr << " variants, enums, options, results, flags)\n"; + std::cerr << " - Generates bidirectional bindings (imports and exports)\n"; + std::cerr << " - Type-safe C++ wrappers using cmcpp canonical ABI\n"; + std::cerr << " - WAMR native function registration helpers\n"; + std::cerr << " - Automatic memory management for complex types\n\n"; + std::cerr << "EXAMPLES:\n"; + std::cerr << " # Generate bindings using package-derived prefix\n"; + std::cerr << " " << argv[0] << " example.wit\n\n"; + std::cerr << " # Generate bindings with custom prefix\n"; + std::cerr << " " << argv[0] << " example.wit my_bindings\n\n"; + std::cerr << "For more information, see: https://github.com/GordonSmith/component-model-cpp\n"; + return 1; + } + + std::string witFile = argv[1]; + std::string outputPrefix = argc >= 3 ? argv[2] : ""; + + try + { + std::cout << "Parsing WIT file with ANTLR grammar: " << witFile << std::endl; + + // Parse WIT file using ANTLR grammar + auto parseResult = WitGrammarParser::parse(witFile); + + if (parseResult.interfaces.empty()) + { + std::cerr << "Warning: No interfaces found in " << witFile << std::endl; + return 1; + } + + // If no output prefix provided, derive it from the package name + if (outputPrefix.empty()) + { + if (!parseResult.packageName.empty()) + { + // Package format: "package example:sample" or just "example:sample" + // Extract just the package part and sanitize it + std::string pkgName = parseResult.packageName; + + // Remove "package" keyword if present + size_t pkgPos = pkgName.find("package"); + if (pkgPos != std::string::npos) + { + pkgName = pkgName.substr(pkgPos + 7); // Skip "package" + } + + // Remove semicolon and trim + pkgName.erase(std::remove(pkgName.begin(), pkgName.end(), ';'), pkgName.end()); + pkgName.erase(std::remove_if(pkgName.begin(), pkgName.end(), ::isspace), pkgName.end()); + + // Extract the name part after the colon (e.g., "sample" from "example:sample") + size_t colonPos = pkgName.find(':'); + if (colonPos != std::string::npos && colonPos + 1 < pkgName.size()) + { + outputPrefix = pkgName.substr(colonPos + 1); + } + else + { + outputPrefix = pkgName; + } + + // Remove version if present (e.g., "@1.0.0") + size_t atPos = outputPrefix.find('@'); + if (atPos != std::string::npos) + { + outputPrefix = outputPrefix.substr(0, atPos); + } + + std::cout << "Using package-derived output prefix: " << outputPrefix << std::endl; + } + + if (outputPrefix.empty()) + { + outputPrefix = "generated"; + std::cout << "No package name found, using default prefix: " << outputPrefix << std::endl; + } + } + + std::cout << "Found " << parseResult.interfaces.size() << " interface(s)" << std::endl; + for (const auto &iface : parseResult.interfaces) + { + std::cout << " - " << iface.name << " (" << iface.functions.size() << " functions)" << std::endl; + } + + // Generate files + std::string headerFile = outputPrefix + ".hpp"; + std::string implFile = outputPrefix + ".cpp"; + std::string wamrBindingsFile = outputPrefix + "_wamr.cpp"; + std::string wamrHeaderFile = outputPrefix + "_wamr.hpp"; + + // Extract just the filename for includes (not the full path) + std::filesystem::path headerPath(headerFile); + std::string headerFilename = headerPath.filename().string(); + std::filesystem::path wamrHeaderPath(wamrHeaderFile); + std::string wamrHeaderFilename = wamrHeaderPath.filename().string(); + + std::cout << "\nGenerating files:" << std::endl; + std::cout << " " << headerFile << std::endl; + CodeGenerator::generateHeader(parseResult.interfaces, headerFile); + + std::cout << " " << wamrHeaderFile << std::endl; + CodeGenerator::generateWAMRHeader(parseResult.interfaces, wamrHeaderFile, parseResult.packageName, headerFilename); + + std::cout << " " << wamrBindingsFile << std::endl; + CodeGenerator::generateWAMRBindings(parseResult.interfaces, wamrBindingsFile, parseResult.packageName, headerFilename, wamrHeaderFilename); + + std::cout << "\nCode generation complete!" << std::endl; + std::cout << "Note: Host function implementations should be provided by the host application." << std::endl; + } + catch (const std::exception &e) + { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/vcpkg-usage.txt b/vcpkg-usage.txt new file mode 100644 index 0000000..5492ac7 --- /dev/null +++ b/vcpkg-usage.txt @@ -0,0 +1,22 @@ +[cmake] antlr4 provides CMake targets: +[cmake] +[cmake] # this is heuristically generated, and may not be correct +[cmake] find_package(antlr4-runtime CONFIG REQUIRED) +[cmake] target_link_libraries(main PRIVATE antlr4_shared) +[cmake] +[cmake] doctest provides CMake targets: +[cmake] +[cmake] # this is heuristically generated, and may not be correct +[cmake] find_package(doctest CONFIG REQUIRED) +[cmake] target_link_libraries(main PRIVATE doctest::doctest) +[cmake] +[cmake] icu provides pkg-config modules: +[cmake] +[cmake] # International Components for Unicode: Internationalization library +[cmake] icu-i18n +[cmake] +[cmake] # International Components for Unicode: Stream and I/O Library +[cmake] icu-io +[cmake] +[cmake] # International Components for Unicode: Common and Data libraries +[cmake] icu-uc \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index 88449b9..8bf5ddf 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,5 +1,6 @@ { "dependencies": [ + "antlr4", "doctest", { "name": "icu", diff --git a/vcpkg_overlays/wasi-sdk/portfile.cmake b/vcpkg_overlays/wasi-sdk/portfile.cmake index 8d16cd8..900ad0e 100644 --- a/vcpkg_overlays/wasi-sdk/portfile.cmake +++ b/vcpkg_overlays/wasi-sdk/portfile.cmake @@ -3,7 +3,7 @@ if(APPLE) vcpkg_download_distfile(ARCHIVE URLS "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-${VERSION}-arm64-macos.tar.gz" - FILENAME "wasi-sdk-${VERSION}-x86_64-linux.tar.gz" + FILENAME "wasi-sdk-${VERSION}-arm64-macos.tar.gz" SHA512 fa4852de1995eaaf5aa57dab9896604a27f157b6113ca0daa27fe7588f4276e18362e650bdb6c65fd83f14d4b8347f8134c9b531a8b872ad83c18d481eeef6c5 ) elseif(UNIX) @@ -28,6 +28,21 @@ vcpkg_extract_source_archive_ex( file(COPY ${SOURCE_PATH}/. DESTINATION ${CURRENT_PACKAGES_DIR}/wasi-sdk) +# On macOS, copy libclang-cpp.dylib to the lib directory if it exists in libexec +if(APPLE) + if(EXISTS "${CURRENT_PACKAGES_DIR}/wasi-sdk/libexec/libclang-cpp.dylib") + file(COPY "${CURRENT_PACKAGES_DIR}/wasi-sdk/libexec/libclang-cpp.dylib" + DESTINATION "${CURRENT_PACKAGES_DIR}/wasi-sdk/lib") + endif() + # Also set up proper @rpath for the clang binary + if(EXISTS "${CURRENT_PACKAGES_DIR}/wasi-sdk/bin/clang-19") + execute_process( + COMMAND install_name_tool -add_rpath "@executable_path/../lib" "${CURRENT_PACKAGES_DIR}/wasi-sdk/bin/clang-19" + ERROR_QUIET + ) + endif() +endif() + file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/wasi-sdk/share/wasi-sysroot/include/net" "${CURRENT_PACKAGES_DIR}/wasi-sdk/share/wasi-sysroot/include/scsi") # Handle copyright