diff --git a/.clang-format b/.clang-format deleted file mode 100644 index cb03ae8..0000000 --- a/.clang-format +++ /dev/null @@ -1,16 +0,0 @@ ---- - BasedOnStyle: Google - AccessModifierOffset: '-2' - AlignTrailingComments: 'true' - AllowAllParametersOfDeclarationOnNextLine: 'false' - AlwaysBreakTemplateDeclarations: 'No' - BreakBeforeBraces: Attach - ColumnLimit: '100' - ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' - IncludeBlocks: Regroup - IndentPPDirectives: AfterHash - IndentWidth: '2' - NamespaceIndentation: All - BreakBeforeBinaryOperators: All - BreakBeforeTernaryOperators: 'true' -... diff --git a/.cmake-format b/.cmake-format deleted file mode 100644 index 8c355cf..0000000 --- a/.cmake-format +++ /dev/null @@ -1,57 +0,0 @@ -format: - tab_size: 2 - line_width: 100 - dangle_parens: true - -parse: - additional_commands: - cpmaddpackage: - pargs: - nargs: '*' - flags: [] - spelling: CPMAddPackage - kwargs: &cpmaddpackagekwargs - NAME: 1 - FORCE: 1 - VERSION: 1 - GIT_TAG: 1 - DOWNLOAD_ONLY: 1 - GITHUB_REPOSITORY: 1 - GITLAB_REPOSITORY: 1 - GIT_REPOSITORY: 1 - SVN_REPOSITORY: 1 - SVN_REVISION: 1 - SOURCE_DIR: 1 - DOWNLOAD_COMMAND: 1 - FIND_PACKAGE_ARGUMENTS: 1 - NO_CACHE: 1 - GIT_SHALLOW: 1 - URL: 1 - URL_HASH: 1 - URL_MD5: 1 - DOWNLOAD_NAME: 1 - DOWNLOAD_NO_EXTRACT: 1 - HTTP_USERNAME: 1 - HTTP_PASSWORD: 1 - OPTIONS: + - cpmfindpackage: - pargs: - nargs: '*' - flags: [] - spelling: CPMFindPackage - kwargs: *cpmaddpackagekwargs - packageproject: - pargs: - nargs: '*' - flags: [] - spelling: packageProject - kwargs: - NAME: 1 - VERSION: 1 - NAMESPACE: 1 - INCLUDE_DIR: 1 - INCLUDE_DESTINATION: 1 - BINARY_DIR: 1 - COMPATIBILITY: 1 - VERSION_HEADER: 1 - DEPENDENCIES: + diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index f10b6dd..f763b64 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -3,11 +3,11 @@ name: Install on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: @@ -20,20 +20,26 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/cache@v3 with: path: "**/cpm_modules" key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + - name: install dependencies + run: | + sudo apt-get install ninja-build + - name: build and install library run: | - cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release + cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release sudo cmake --build build --target install rm -rf build - name: configure - run: cmake -Stest -Bbuild -DTEST_INSTALLED_VERSION=1 + run: cmake -Stest -Bbuild -DTEST_INSTALLED_VERSION=ON - name: build run: cmake --build build --config Debug -j4 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 141a7ac..151d435 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -3,11 +3,11 @@ name: MacOS on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: @@ -20,19 +20,27 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/cache@v3 with: path: "**/cpm_modules" key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + - name: install dependencies + run: | + brew install ninja + - name: configure - run: cmake -Stest -Bbuild -DCMAKE_BUILD_TYPE=Debug + run: | + cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Debug - name: build - run: cmake --build build -j4 + run: cmake --build build --parallel - name: test + working-directory: build/test run: | - cd build - ctest --build-config Debug + ctest + diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index dff55dc..6d058ca 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -3,11 +3,11 @@ name: Standalone on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index fa35e5b..0e40d75 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -3,11 +3,11 @@ name: Style on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 8839ff8..77a92e4 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,11 +3,11 @@ name: Ubuntu on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: @@ -21,22 +21,31 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/cache@v3 with: path: "**/cpm_modules" key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + - name: install dependencies + run: | + sudo apt-get install ninja-build + - name: configure - run: cmake -Stest -Bbuild -DENABLE_TEST_COVERAGE=1 -DCMAKE_BUILD_TYPE=Debug + run: | + cmake -G Ninja -S . -B build -DENABLE_TEST_COVERAGE=1 -DCMAKE_BUILD_TYPE=Debug + + - name: build-wasm + run: ./test/wasm/build.sh - name: build - run: cmake --build build -j4 + run: cmake --build build --parallel - name: test run: | - cd build - ctest --build-config Debug + ctest --test-dir ./build/test - name: collect code coverage run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 82d5a35..d432e54 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,11 +3,11 @@ name: Windows on: push: branches: - - master + - trunk - main pull_request: branches: - - master + - trunk - main env: @@ -20,6 +20,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/cache@v3 with: @@ -27,12 +29,14 @@ jobs: key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} - name: configure - run: cmake -Stest -Bbuild + run: | + cmake -S . -B build - name: build - run: cmake --build build --config Debug -j4 + run: cmake --build build --config Debug --parallel - name: test + working-directory: build/test run: | - cd build - ctest --build-config Debug + ctest + diff --git a/.gitignore b/.gitignore index d54a4f4..3205cf2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /build* /.vscode /cpm_modules -.DS_Store \ No newline at end of file +/vcpkg_installed +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8715e53 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg.git +[submodule "ref/component-model"] + path = ref/component-model + url = git@github.com:WebAssembly/component-model.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c2d81e0..c3163c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,14 @@ cmake_minimum_required(VERSION 3.14...3.22) -# ---- Project ---- +set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(VCPKG_FILES_DIR "${CMAKE_BINARY_DIR}" CACHE STRING "Folder for vcpkg download, build and installed files") +set(CMAKE_TOOLCHAIN_FILE ${ROOT_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) +set(VCPKG_ROOT ${ROOT_DIR}/vcpkg) +set(VCPKG_INSTALLED_DIR "${VCPKG_FILES_DIR}/vcpkg_installed") +set(VCPKG_INSTALL_OPTIONS "--x-abi-tools-use-exact-versions;--downloads-root=${VCPKG_FILES_DIR}/vcpkg_downloads;--x-buildtrees-root=${VCPKG_FILES_DIR}/vcpkg_buildtrees;--x-packages-root=${VCPKG_FILES_DIR}/vcpkg_packages") +set(VCPKG_VERBOSE OFF) -# Note: update this to your new project's name and version -project( - Greeter - VERSION 1.0 - LANGUAGES CXX -) +project(BuildAll LANGUAGES CXX) # ---- Include guards ---- @@ -18,61 +19,5 @@ if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) ) endif() -# ---- Add dependencies via CPM ---- -# see https://github.com/TheLartians/CPM.cmake for more info - -include(cmake/CPM.cmake) - -# PackageProject.cmake will be used to make our target installable -CPMAddPackage("gh:TheLartians/PackageProject.cmake@1.8.0") - -CPMAddPackage( - NAME fmt - GIT_TAG 9.1.0 - GITHUB_REPOSITORY fmtlib/fmt - OPTIONS "FMT_INSTALL YES" # create an installable target -) - -# ---- Add source files ---- - -# Note: globbing sources is considered bad practice as CMake's generators may not detect new files -# automatically. Keep that in mind when changing files, or explicitly mention them here. -file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") -file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") - -# ---- Create library ---- - -# Note: for header-only libraries change all PUBLIC flags to INTERFACE and create an interface -# target: add_library(${PROJECT_NAME} INTERFACE) -add_library(${PROJECT_NAME} ${headers} ${sources}) -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) - -# being a cross-platform target, we enforce standards conformance on MSVC -target_compile_options(${PROJECT_NAME} PUBLIC "$<$:/permissive->") - -# Link dependencies -target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt) - -target_include_directories( - ${PROJECT_NAME} PUBLIC $ - $ -) - -# ---- Create an installable target ---- -# this allows users to install and find the library via `find_package()`. - -# the location where the project's version header will be placed should match the project's regular -# header paths -string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION) - -packageProject( - NAME ${PROJECT_NAME} - VERSION ${PROJECT_VERSION} - NAMESPACE ${PROJECT_NAME} - BINARY_DIR ${PROJECT_BINARY_DIR} - INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include - INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION} - VERSION_HEADER "${VERSION_HEADER_LOCATION}" - COMPATIBILITY SameMajorVersion - DEPENDENCIES "fmt 9.1.0" -) +add_subdirectory(src) +add_subdirectory(test) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f4aae9d --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,227 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 22, + "patch": 1 + }, + "configurePresets": [ + { + "name": "default" + }, + { + "name": "vcpkg", + "displayName": "vcpkg", + "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", + "binaryDir": "${sourceDir}/build", + "installDir": "${sourceDir}/build/stage", + "cacheVariables": {}, + "hidden": true + }, + { + "name": "ninja", + "generator": "Ninja", + "hidden": true + }, + { + "name": "debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "hidden": true + }, + { + "name": "release", + "binaryDir": "${sourceDir}/build/Release", + "installDir": "${sourceDir}/build/Release/stage", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + }, + "hidden": true + }, + { + "name": "relwithdebinfo", + "binaryDir": "${sourceDir}/build/RelWithDebInfo", + "installDir": "${sourceDir}/build/RelWithDebInfo/stage", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + }, + "hidden": true + }, + { + "name": "ubuntu", + "cacheVariables": { + "USE_OPTIONAL": "OFF", + "USE_CPPUNIT": "ON", + "INCLUDE_PLUGINS": "ON", + "SUPPRESS_V8EMBED": "ON", + "SUPPRESS_REMBED": "ON" + }, + "hidden": true + }, + { + "name": "windows", + "cacheVariables": { + "USE_OPTIONAL": "OFF", + "CLIENTTOOLS_ONLY": "ON", + "USE_AZURE": "OFF", + "USE_CASSANDRA": "OFF", + "USE_JAVA": "OFF", + "USE_OPENLDAP": "OFF" + }, + "hidden": true + }, + { + "name": "ubuntu-ninja", + "inherits": [ + "vcpkg", + "ninja", + "ubuntu" + ] + }, + { + "name": "ubuntu-ninja-debug", + "inherits": [ + "ubuntu-ninja", + "debug" + ] + }, + { + "name": "ubuntu-ninja-debug-minimal", + "inherits": [ + "ubuntu-ninja", + "debug" + ], + "cacheVariables": { + "USE_CPPUNIT": "OFF", + "INCLUDE_PLUGINS": "OFF" + } + }, + { + "name": "ubuntu-ninja-release", + "inherits": [ + "ubuntu-ninja", + "release" + ] + }, + { + "name": "ubuntu-ninja-relwithdebinfo", + "inherits": [ + "ubuntu-ninja", + "relwithdebinfo" + ] + }, + { + "name": "VS-16", + "generator": "Visual Studio 16 2019", + "architecture": { + "strategy": "set", + "value": "x64" + }, + "hidden": true + }, + { + "name": "VS-17", + "generator": "Visual Studio 17 2022", + "architecture": { + "strategy": "set", + "value": "x64" + }, + "toolset": { + "strategy": "set", + "value": "host=x64" + }, + "hidden": true + }, + { + "name": "vcpkg-VS-16", + "inherits": [ + "vcpkg", + "windows", + "VS-16" + ] + }, + { + "name": "vcpkg-VS-17", + "inherits": [ + "vcpkg", + "windows", + "VS-17" + ] + } + ], + "buildPresets": [ + { + "name": "ninja-linux", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "hidden": true, + "nativeToolOptions": [] + }, + { + "name": "Ninja-Linux-Debug", + "inherits": "Ninja-Linux", + "configuration": "Debug" + }, + { + "name": "Ninja-Linux-Release", + "inherits": "Ninja-Linux", + "configuration": "Release" + }, + { + "name": "VS-16-Debug", + "configurePreset": "vcpkg-VS-16", + "configuration": "Debug", + "jobs": 0, + "nativeToolOptions": [ + "-m" + ] + }, + { + "name": "VS-16-Release", + "configurePreset": "vcpkg-VS-16", + "configuration": "Release", + "jobs": 0, + "nativeToolOptions": [ + "-m" + ] + }, + { + "name": "VS-17-Debug", + "configurePreset": "vcpkg-VS-17", + "configuration": "Debug", + "jobs": 0, + "nativeToolOptions": [ + "-m" + ] + }, + { + "name": "VS-17-Release", + "configurePreset": "vcpkg-VS-17", + "configuration": "Release", + "jobs": 0, + "nativeToolOptions": [ + "-m" + ] + }, + { + "name": "VS-17-RelWithDebInfo", + "configurePreset": "vcpkg-VS-17", + "configuration": "RelWithDebInfo", + "jobs": 0, + "nativeToolOptions": [ + "-m" + ] + } + ], + "testPresets": [ + { + "name": "xxx", + "description": "", + "displayName": "" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 46b6226..d341d30 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,81 @@ -[![Actions Status](https://github.com/TheLartians/ModernCppStarter/workflows/MacOS/badge.svg)](https://github.com/TheLartians/ModernCppStarter/actions) -[![Actions Status](https://github.com/TheLartians/ModernCppStarter/workflows/Windows/badge.svg)](https://github.com/TheLartians/ModernCppStarter/actions) -[![Actions Status](https://github.com/TheLartians/ModernCppStarter/workflows/Ubuntu/badge.svg)](https://github.com/TheLartians/ModernCppStarter/actions) -[![Actions Status](https://github.com/TheLartians/ModernCppStarter/workflows/Style/badge.svg)](https://github.com/TheLartians/ModernCppStarter/actions) -[![Actions Status](https://github.com/TheLartians/ModernCppStarter/workflows/Install/badge.svg)](https://github.com/TheLartians/ModernCppStarter/actions) -[![codecov](https://codecov.io/gh/TheLartians/ModernCppStarter/branch/master/graph/badge.svg)](https://codecov.io/gh/TheLartians/ModernCppStarter) +[![Actions Status](https://github.com/GordonSmith/component-model-cpp/workflows/MacOS/badge.svg)](https://github.com/GordonSmith/component-model-cpp/actions) +[![Actions Status](https://github.com/GordonSmith/component-model-cpp/workflows/Windows/badge.svg)](https://github.com/GordonSmith/component-model-cpp/actions) +[![Actions Status](https://github.com/GordonSmith/component-model-cpp/workflows/Ubuntu/badge.svg)](https://github.com/GordonSmith/component-model-cpp/actions) +[![Actions Status](https://github.com/GordonSmith/component-model-cpp/workflows/Style/badge.svg)](https://github.com/GordonSmith/component-model-cpp/actions) +[![Actions Status](https://github.com/GordonSmith/component-model-cpp/workflows/Install/badge.svg)](https://github.com/GordonSmith/component-model-cpp/actions) +[![codecov](https://codecov.io/gh/GordonSmith/component-model-cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/GordonSmith/component-model-cpp)

+

-# ModernCppStarter +# Component Model C++ -Setting up a new C++ project usually requires a significant amount of preparation and boilerplate code, even more so for modern C++ projects with tests, executables and continuous integration. -This template is the result of learnings from many previous projects and should help reduce the work required to setup up a modern C++ project. +This repository contains a C++ ABI implementation of the WebAssembly Component Model. ## Features -- [Modern CMake practices](https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/) -- Suited for single header libraries and projects of any scale -- Clean separation of library and executable code -- Integrated test suite -- Continuous integration via [GitHub Actions](https://help.github.com/en/actions/) -- Code coverage via [codecov](https://codecov.io) -- Code formatting enforced by [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and [cmake-format](https://github.com/cheshirekow/cmake_format) via [Format.cmake](https://github.com/TheLartians/Format.cmake) -- Reproducible dependency management via [CPM.cmake](https://github.com/TheLartians/CPM.cmake) -- Installable target with automatic versioning information and header generation via [PackageProject.cmake](https://github.com/TheLartians/PackageProject.cmake) -- Automatic [documentation](https://thelartians.github.io/ModernCppStarter) and deployment with [Doxygen](https://www.doxygen.nl) and [GitHub Pages](https://pages.github.com) -- Support for [sanitizer tools, and more](#additional-tools) +### OS +- [x] Linux +- [ ] MacOS +- [ ] Windows + +### Host Data Types +- [x] Bool +- [x] S8 +- [x] U8 +- [x] S16 +- [x] U16 +- [x] S32 +- [x] U32 +- [x] S64 +- [x] U64 +- [x] Float32 +- [x] Float64 +- [x] Char +- [x] String +- [x] utf8 String +- [ ] utf16 String +- [x] List +- [x] Field +- [x] Record +- [x] Tuple +- [x] Case +- [x] Variant +- [x] Enum +- [x] Option +- [x] Result +- [x] Flags +- [ ] Own +- [ ] Borrow + +### Host Functions +- [x] lower_values +- [x] lift_values + +### Tests +- [x] ABI +- [ ] WasmTime +- [ ] Wamr +- [ ] WasmEdge ## Usage -### Adjust the template to your needs +TODO -- Use this repo [as a template](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template). -- Replace all occurrences of "Greeter" in the relevant CMakeLists.txt with the name of your project - - Capitalization matters here: `Greeter` means the name of the project, while `greeter` is used in file names. - - Remember to rename the `include/greeter` directory to use your project's lowercase name and update all relevant `#include`s accordingly. -- Replace the source files with your own -- For header-only libraries: see the comments in [CMakeLists.txt](CMakeLists.txt) -- Add [your project's codecov token](https://docs.codecov.io/docs/quick-start) to your project's github secrets under `CODECOV_TOKEN` -- Happy coding! +## Related projects -Eventually, you can remove any unused files, such as the standalone directory or irrelevant github workflows for your project. -Feel free to replace the License with one suited for your project. - -To cleanly separate the library and subproject code, the outer `CMakeList.txt` only defines the library itself while the tests and other subprojects are self-contained in their own directories. -During development it is usually convenient to [build all subprojects at once](#build-everything-at-once). - -### Build and run the standalone target - -Use the following command to build and run the executable target. - -```bash -cmake -S standalone -B build/standalone -cmake --build build/standalone -./build/standalone/Greeter --help -``` - -### Build and run test suite - -Use the following commands from the project's root directory to run the test suite. - -```bash -cmake -S test -B build/test -cmake --build build/test -CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/test --target test - -# or simply call the executable: -./build/test/GreeterTests -``` - -To collect code coverage information, run CMake with the `-DENABLE_TEST_COVERAGE=1` option. - -### Run clang-format - -Use the following commands from the project's root directory to check and fix C++ and CMake source style. -This requires _clang-format_, _cmake-format_ and _pyyaml_ to be installed on the current system. - -```bash -cmake -S test -B build/test - -# view changes -cmake --build build/test --target format - -# apply changes -cmake --build build/test --target fix-format -``` - -See [Format.cmake](https://github.com/TheLartians/Format.cmake) for details. -These dependencies can be easily installed using pip. - -```bash -pip install clang-format==14.0.6 cmake_format==0.6.11 pyyaml -``` - -### Build the documentation - -The documentation is automatically built and [published](https://thelartians.github.io/ModernCppStarter) whenever a [GitHub Release](https://help.github.com/en/github/administering-a-repository/managing-releases-in-a-repository) is created. -To manually build documentation, call the following command. - -```bash -cmake -S documentation -B build/doc -cmake --build build/doc --target GenerateDocs -# view the docs -open build/doc/doxygen/html/index.html -``` - -To build the documentation locally, you will need Doxygen, jinja2 and Pygments installed on your system. - -### Build everything at once - -The project also includes an `all` directory that allows building all targets at the same time. -This is useful during development, as it exposes all subprojects to your IDE and avoids redundant builds of the library. - -```bash -cmake -S all -B build -cmake --build build - -# run tests -./build/test/GreeterTests -# format code -cmake --build build --target fix-format -# run standalone -./build/standalone/Greeter --help -# build docs -cmake --build build --target GenerateDocs -``` - -### Additional tools - -The test and standalone subprojects include the [tools.cmake](cmake/tools.cmake) file which is used to import additional tools on-demand through CMake configuration arguments. -The following are currently supported. - -#### Sanitizers - -Sanitizers can be enabled by configuring CMake with `-DUSE_SANITIZER=
`. - -#### Static Analyzers - -Static Analyzers can be enabled by setting `-DUSE_STATIC_ANALYZER=`, or a combination of those in quotation marks, separated by semicolons. -By default, analyzers will automatically find configuration files such as `.clang-format`. -Additional arguments can be passed to the analyzers by setting the `CLANG_TIDY_ARGS`, `IWYU_ARGS` or `CPPCHECK_ARGS` variables. - -#### Ccache - -Ccache can be enabled by configuring with `-DUSE_CCACHE=`. - -## FAQ - -> Can I use this for header-only libraries? - -Yes, however you will need to change the library type to an `INTERFACE` library as documented in the [CMakeLists.txt](CMakeLists.txt). -See [here](https://github.com/TheLartians/StaticTypeInfo) for an example header-only library based on the template. - -> I don't need a standalone target / documentation. How can I get rid of it? - -Simply remove the standalone / documentation directory and according github workflow file. - -> Can I build the standalone and tests at the same time? / How can I tell my IDE about all subprojects? - -To keep the template modular, all subprojects derived from the library have been separated into their own CMake modules. -This approach makes it trivial for third-party projects to re-use the projects library code. -To allow IDEs to see the full scope of the project, the template includes the `all` directory that will create a single build for all subprojects. -Use this as the main directory for best IDE support. - -> I see you are using `GLOB` to add source files in CMakeLists.txt. Isn't that evil? - -Glob is considered bad because any changes to the source file structure [might not be automatically caught](https://cmake.org/cmake/help/latest/command/file.html#filesystem) by CMake's builders and you will need to manually invoke CMake on changes. - I personally prefer the `GLOB` solution for its simplicity, but feel free to change it to explicitly listing sources. - -> I want create additional targets that depend on my library. Should I modify the main CMakeLists to include them? - -Avoid including derived projects from the libraries CMakeLists (even though it is a common sight in the C++ world), as this effectively inverts the dependency tree and makes the build system hard to reason about. -Instead, create a new directory or project with a CMakeLists that adds the library as a dependency (e.g. like the [standalone](standalone/CMakeLists.txt) directory). -Depending type it might make sense move these components into a separate repositories and reference a specific commit or version of the library. -This has the advantage that individual libraries and components can be improved and updated independently. - -> You recommend to add external dependencies using CPM.cmake. Will this force users of my library to use CPM.cmake as well? - -[CPM.cmake](https://github.com/TheLartians/CPM.cmake) should be invisible to library users as it's a self-contained CMake Script. -If problems do arise, users can always opt-out by defining the CMake or env variable [`CPM_USE_LOCAL_PACKAGES`](https://github.com/cpm-cmake/CPM.cmake#options), which will override all calls to `CPMAddPackage` with the according `find_package` call. -This should also enable users to use the project with their favorite external C++ dependency manager, such as vcpkg or Conan. - -> Can I configure and build my project offline? - -No internet connection is required for building the project, however when using CPM missing dependencies are downloaded at configure time. -To avoid redundant downloads, it's highly recommended to set a CPM.cmake cache directory, e.g.: `export CPM_SOURCE_CACHE=$HOME/.cache/CPM`. -This will enable shallow clones and allow offline configurations dependencies are already available in the cache. - -> Can I use CPack to create a package installer for my project? - -As there are a lot of possible options and configurations, this is not (yet) in the scope of this template. See the [CPack documentation](https://cmake.org/cmake/help/latest/module/CPack.html) for more information on setting up CPack installers. - -> This is too much, I just want to play with C++ code and test some libraries. - -Perhaps the [MiniCppStarter](https://github.com/TheLartians/MiniCppStarter) is something for you! - -## Related projects and alternatives - -- [**ModernCppStarter & PVS-Studio Static Code Analyzer**](https://github.com/viva64/pvs-studio-cmake-examples/tree/master/modern-cpp-starter): Official instructions on how to use the ModernCppStarter with the PVS-Studio Static Code Analyzer. -- [**cpp-best-practices/gui_starter_template**](https://github.com/cpp-best-practices/gui_starter_template/): A popular C++ starter project, created in 2017. -- [**filipdutescu/modern-cpp-template**](https://github.com/filipdutescu/modern-cpp-template): A recent starter using a more traditional approach for CMake structure and dependency management. -- [**vector-of-bool/pitchfork**](https://github.com/vector-of-bool/pitchfork/): Pitchfork is a Set of C++ Project Conventions. +- [**Component Model design and specification**](https://github.com/WebAssembly/component-model): Official Component Model specification. +- [**wit-bindgen c++ host**](https://github.com/cpetig/wit-bindgen): C++ host support for the WebAssembly Interface Types (WIT) Bindgen tool. ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=TheLartians/ModernCppStarter,cpp-best-practices/gui_starter_template,filipdutescu/modern-cpp-template&type=Date)](https://star-history.com/#TheLartians/ModernCppStarter&cpp-best-practices/gui_starter_template&filipdutescu/modern-cpp-template&Date) + + + + + Star History Chart + + + diff --git a/all/CMakeLists.txt b/all/CMakeLists.txt deleted file mode 100644 index 8092382..0000000 --- a/all/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# this script adds all subprojects to a single build to allow IDEs understand the full project -# structure. - -cmake_minimum_required(VERSION 3.14...3.22) - -project(BuildAll LANGUAGES CXX) - -include(../cmake/tools.cmake) - -# needed to generate test target -enable_testing() - -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../standalone ${CMAKE_BINARY_DIR}/standalone) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../test ${CMAKE_BINARY_DIR}/test) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../documentation ${CMAKE_BINARY_DIR}/documentation) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake deleted file mode 100644 index a786712..0000000 --- a/cmake/CPM.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: MIT -# -# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors - -set(CPM_DOWNLOAD_VERSION 0.38.5) -set(CPM_HASH_SUM "192aa0ccdc57dfe75bd9e4b176bf7fb5692fd2b3e3f7b09c74856fc39572b31c") - -if(CPM_SOURCE_CACHE) - set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -elseif(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -else() - set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -endif() - -# Expand relative path. This is important if the provided path contains a tilde (~) -get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) - -file(DOWNLOAD - https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake - ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} -) - -include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/tools.cmake b/cmake/tools.cmake deleted file mode 100644 index ece09c0..0000000 --- a/cmake/tools.cmake +++ /dev/null @@ -1,66 +0,0 @@ -# this file contains a list of tools that can be activated and downloaded on-demand each tool is -# enabled during configuration by passing an additional `-DUSE_=` argument to CMake - -# only activate tools for top level project -if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - return() -endif() - -include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) - -# enables sanitizers support using the the `USE_SANITIZER` flag available values are: Address, -# Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined' -if(USE_SANITIZER OR USE_STATIC_ANALYZER) - CPMAddPackage("gh:StableCoder/cmake-scripts@23.04") - - if(USE_SANITIZER) - include(${cmake-scripts_SOURCE_DIR}/sanitizers.cmake) - endif() - - if(USE_STATIC_ANALYZER) - if("clang-tidy" IN_LIST USE_STATIC_ANALYZER) - set(CLANG_TIDY - ON - CACHE INTERNAL "" - ) - else() - set(CLANG_TIDY - OFF - CACHE INTERNAL "" - ) - endif() - if("iwyu" IN_LIST USE_STATIC_ANALYZER) - set(IWYU - ON - CACHE INTERNAL "" - ) - else() - set(IWYU - OFF - CACHE INTERNAL "" - ) - endif() - if("cppcheck" IN_LIST USE_STATIC_ANALYZER) - set(CPPCHECK - ON - CACHE INTERNAL "" - ) - else() - set(CPPCHECK - OFF - CACHE INTERNAL "" - ) - endif() - - include(${cmake-scripts_SOURCE_DIR}/tools.cmake) - - clang_tidy(${CLANG_TIDY_ARGS}) - include_what_you_use(${IWYU_ARGS}) - cppcheck(${CPPCHECK_ARGS}) - endif() -endif() - -# enables CCACHE support through the USE_CCACHE flag possible values are: YES, NO or equivalent -if(USE_CCACHE) - CPMAddPackage("gh:TheLartians/Ccache.cmake@1.2.4") -endif() diff --git a/definitions.py b/definitions.py new file mode 100644 index 0000000..c9d315b --- /dev/null +++ b/definitions.py @@ -0,0 +1,1177 @@ +# After the Boilerplate section, this file is ordered to line up with the code +# blocks in ../CanonicalABI.md (split by # comment lines). If you update this +# file, don't forget to update ../CanonicalABI.md. + +### Boilerplate + +from __future__ import annotations +import math +import struct +import random +from dataclasses import dataclass +from typing import Optional +from typing import Callable +from typing import MutableMapping + +class Trap(BaseException): pass +class CoreWebAssemblyException(BaseException): pass + +def trap(): + raise Trap() + +def trap_if(cond): + if cond: + raise Trap() + +class Type: pass +class ValType(Type): pass +class ExternType(Type): pass +class CoreExternType(Type): pass + +@dataclass +class CoreImportDecl: + module: str + field: str + t: CoreExternType + +@dataclass +class CoreExportDecl: + name: str + t: CoreExternType + +@dataclass +class ModuleType(ExternType): + imports: list[CoreImportDecl] + exports: list[CoreExportDecl] + +@dataclass +class CoreFuncType(CoreExternType): + params: list[str] + results: list[str] + +@dataclass +class CoreMemoryType(CoreExternType): + initial: list[int] + maximum: Optional[int] + +@dataclass +class ExternDecl: + name: str + t: ExternType + +@dataclass +class ComponentType(ExternType): + imports: list[ExternDecl] + exports: list[ExternDecl] + +@dataclass +class InstanceType(ExternType): + exports: list[ExternDecl] + +@dataclass +class FuncType(ExternType): + params: list[tuple[str,ValType]] + results: list[ValType|tuple[str,ValType]] + def param_types(self): + return self.extract_types(self.params) + def result_types(self): + return self.extract_types(self.results) + def extract_types(self, vec): + if len(vec) == 0: + return [] + if isinstance(vec[0], ValType): + return vec + return [t for name,t in vec] + +@dataclass +class ValueType(ExternType): + t: ValType + +class Bounds: pass + +@dataclass +class Eq(Bounds): + t: Type + +@dataclass +class TypeType(ExternType): + bounds: Bounds + +class Bool(ValType): pass +class S8(ValType): pass +class U8(ValType): pass +class S16(ValType): pass +class U16(ValType): pass +class S32(ValType): pass +class U32(ValType): pass +class S64(ValType): pass +class U64(ValType): pass +class Float32(ValType): pass +class Float64(ValType): pass +class Char(ValType): pass +class String(ValType): pass + +@dataclass +class List(ValType): + t: ValType + +@dataclass +class Field: + label: str + t: ValType + +@dataclass +class Record(ValType): + fields: list[Field] + +@dataclass +class Tuple(ValType): + ts: list[ValType] + +@dataclass +class Case: + label: str + t: Optional[ValType] + refines: Optional[str] = None + +@dataclass +class Variant(ValType): + cases: list[Case] + +@dataclass +class Enum(ValType): + labels: list[str] + +@dataclass +class Option(ValType): + t: ValType + +@dataclass +class Result(ValType): + ok: Optional[ValType] + error: Optional[ValType] + +@dataclass +class Flags(ValType): + labels: list[str] + +@dataclass +class Own(ValType): + rt: ResourceType + +@dataclass +class Borrow(ValType): + rt: ResourceType + +### Despecialization + +def despecialize(t): + match t: + case Tuple(ts) : return Record([ Field(str(i), t) for i,t in enumerate(ts) ]) + case Enum(labels) : return Variant([ Case(l, None) for l in labels ]) + case Option(t) : return Variant([ Case("none", None), Case("some", t) ]) + case Result(ok, error) : return Variant([ Case("ok", ok), Case("error", error) ]) + case _ : return t + +### Alignment + +def alignment(t): + match despecialize(t): + case Bool() : return 1 + case S8() | U8() : return 1 + case S16() | U16() : return 2 + case S32() | U32() : return 4 + case S64() | U64() : return 8 + case Float32() : return 4 + case Float64() : return 8 + case Char() : return 4 + case String() | List(_) : return 4 + case Record(fields) : return alignment_record(fields) + case Variant(cases) : return alignment_variant(cases) + case Flags(labels) : return alignment_flags(labels) + case Own(_) | Borrow(_) : return 4 + +def alignment_record(fields): + a = 1 + for f in fields: + a = max(a, alignment(f.t)) + return a + +def alignment_variant(cases): + return max(alignment(discriminant_type(cases)), max_case_alignment(cases)) + +def discriminant_type(cases): + n = len(cases) + assert(0 < n < (1 << 32)) + match math.ceil(math.log2(n)/8): + case 0: return U8() + case 1: return U8() + case 2: return U16() + case 3: return U32() + +def max_case_alignment(cases): + a = 1 + for c in cases: + if c.t is not None: + a = max(a, alignment(c.t)) + return a + +def alignment_flags(labels): + n = len(labels) + if n <= 8: return 1 + if n <= 16: return 2 + return 4 + +### Size + +def size(t): + match despecialize(t): + case Bool() : return 1 + case S8() | U8() : return 1 + case S16() | U16() : return 2 + case S32() | U32() : return 4 + case S64() | U64() : return 8 + case Float32() : return 4 + case Float64() : return 8 + case Char() : return 4 + case String() | List(_) : return 8 + case Record(fields) : return size_record(fields) + case Variant(cases) : return size_variant(cases) + case Flags(labels) : return size_flags(labels) + case Own(_) | Borrow(_) : return 4 + +def size_record(fields): + s = 0 + for f in fields: + s = align_to(s, alignment(f.t)) + s += size(f.t) + assert(s > 0) + return align_to(s, alignment_record(fields)) + +def align_to(ptr, alignment): + return math.ceil(ptr / alignment) * alignment + +def size_variant(cases): + s = size(discriminant_type(cases)) + s = align_to(s, max_case_alignment(cases)) + cs = 0 + for c in cases: + if c.t is not None: + cs = max(cs, size(c.t)) + s += cs + return align_to(s, alignment_variant(cases)) + +def size_flags(labels): + n = len(labels) + assert(n > 0) + if n <= 8: return 1 + if n <= 16: return 2 + return 4 * num_i32_flags(labels) + +def num_i32_flags(labels): + return math.ceil(len(labels) / 32) + +### Runtime State + +class CallContext: + opts: CanonicalOptions + inst: ComponentInstance + lenders: list[HandleElem] + borrow_count: int + + def __init__(self, opts, inst): + self.opts = opts + self.inst = inst + self.lenders = [] + self.borrow_count = 0 + + def track_owning_lend(self, lending_handle): + assert(lending_handle.own) + lending_handle.lend_count += 1 + self.lenders.append(lending_handle) + + def exit_call(self): + trap_if(self.borrow_count != 0) + for h in self.lenders: + h.lend_count -= 1 + +class CanonicalOptions: + memory: bytearray + string_encoding: str + realloc: Callable[[int,int,int,int],int] + post_return: Callable[[],None] + +class ComponentInstance: + may_leave: bool + may_enter: bool + handles: HandleTables + + def __init__(self): + self.may_leave = True + self.may_enter = True + self.handles = HandleTables() + +class ResourceType(Type): + impl: ComponentInstance + dtor: Optional[Callable[[int],None]] + + def __init__(self, impl, dtor = None): + self.impl = impl + self.dtor = dtor + +class HandleElem: + rep: int + own: bool + scope: Optional[CallContext] + lend_count: int + + def __init__(self, rep, own, scope = None): + self.rep = rep + self.own = own + self.scope = scope + self.lend_count = 0 + +class HandleTable: + array: list[Optional[HandleElem]] + free: list[int] + + def __init__(self): + self.array = [None] + self.free = [] + + def get(self, i): + trap_if(i >= len(self.array)) + trap_if(self.array[i] is None) + return self.array[i] + + def add(self, h): + if self.free: + i = self.free.pop() + assert(self.array[i] is None) + self.array[i] = h + else: + i = len(self.array) + trap_if(i >= 2**30) + self.array.append(h) + return i + + def remove(self, rt, i): + h = self.get(i) + self.array[i] = None + self.free.append(i) + return h + +class HandleTables: + rt_to_table: MutableMapping[ResourceType, HandleTable] + + def __init__(self): + self.rt_to_table = dict() + + def table(self, rt): + if rt not in self.rt_to_table: + self.rt_to_table[rt] = HandleTable() + return self.rt_to_table[rt] + + def get(self, rt, i): + return self.table(rt).get(i) + def add(self, rt, h): + return self.table(rt).add(h) + def remove(self, rt, i): + return self.table(rt).remove(rt, i) + +### Loading + +def load(cx, ptr, t): + assert(ptr == align_to(ptr, alignment(t))) + assert(ptr + size(t) <= len(cx.opts.memory)) + match despecialize(t): + case Bool() : return convert_int_to_bool(load_int(cx, ptr, 1)) + case U8() : return load_int(cx, ptr, 1) + case U16() : return load_int(cx, ptr, 2) + case U32() : return load_int(cx, ptr, 4) + case U64() : return load_int(cx, ptr, 8) + case S8() : return load_int(cx, ptr, 1, signed=True) + case S16() : return load_int(cx, ptr, 2, signed=True) + case S32() : return load_int(cx, ptr, 4, signed=True) + case S64() : return load_int(cx, ptr, 8, signed=True) + case Float32() : return decode_i32_as_float(load_int(cx, ptr, 4)) + case Float64() : return decode_i64_as_float(load_int(cx, ptr, 8)) + case Char() : return convert_i32_to_char(cx, load_int(cx, ptr, 4)) + case String() : return load_string(cx, ptr) + case List(t) : return load_list(cx, ptr, t) + case Record(fields) : return load_record(cx, ptr, fields) + case Variant(cases) : return load_variant(cx, ptr, cases) + case Flags(labels) : return load_flags(cx, ptr, labels) + case Own() : return lift_own(cx, load_int(cx, ptr, 4), t) + case Borrow() : return lift_borrow(cx, load_int(cx, ptr, 4), t) + +def load_int(cx, ptr, nbytes, signed = False): + return int.from_bytes(cx.opts.memory[ptr : ptr+nbytes], 'little', signed=signed) + +def convert_int_to_bool(i): + assert(i >= 0) + return bool(i) + +DETERMINISTIC_PROFILE = False # or True +CANONICAL_FLOAT32_NAN = 0x7fc00000 +CANONICAL_FLOAT64_NAN = 0x7ff8000000000000 + +def canonicalize_nan32(f): + if math.isnan(f): + f = core_f32_reinterpret_i32(CANONICAL_FLOAT32_NAN) + assert(math.isnan(f)) + return f + +def canonicalize_nan64(f): + if math.isnan(f): + f = core_f64_reinterpret_i64(CANONICAL_FLOAT64_NAN) + assert(math.isnan(f)) + return f + +def decode_i32_as_float(i): + return canonicalize_nan32(core_f32_reinterpret_i32(i)) + +def decode_i64_as_float(i): + return canonicalize_nan64(core_f64_reinterpret_i64(i)) + +def core_f32_reinterpret_i32(i): + return struct.unpack('!f', struct.pack('!I', i))[0] # f32.reinterpret_i32 + +def core_f64_reinterpret_i64(i): + return struct.unpack('!d', struct.pack('!Q', i))[0] # f64.reinterpret_i64 + +def convert_i32_to_char(cx, i): + trap_if(i >= 0x110000) + trap_if(0xD800 <= i <= 0xDFFF) + return chr(i) + +def load_string(cx, ptr): + begin = load_int(cx, ptr, 4) + tagged_code_units = load_int(cx, ptr + 4, 4) + return load_string_from_range(cx, begin, tagged_code_units) + +UTF16_TAG = 1 << 31 + +def load_string_from_range(cx, ptr, tagged_code_units): + match cx.opts.string_encoding: + case 'utf8': + alignment = 1 + byte_length = tagged_code_units + encoding = 'utf-8' + case 'utf16': + alignment = 2 + byte_length = 2 * tagged_code_units + encoding = 'utf-16-le' + case 'latin1+utf16': + alignment = 2 + if bool(tagged_code_units & UTF16_TAG): + byte_length = 2 * (tagged_code_units ^ UTF16_TAG) + encoding = 'utf-16-le' + else: + byte_length = tagged_code_units + encoding = 'latin-1' + + trap_if(ptr != align_to(ptr, alignment)) + trap_if(ptr + byte_length > len(cx.opts.memory)) + try: + s = cx.opts.memory[ptr : ptr+byte_length].decode(encoding) + except UnicodeError: + trap() + + return (s, cx.opts.string_encoding, tagged_code_units) + +def load_list(cx, ptr, elem_type): + begin = load_int(cx, ptr, 4) + length = load_int(cx, ptr + 4, 4) + return load_list_from_range(cx, begin, length, elem_type) + +def load_list_from_range(cx, ptr, length, elem_type): + trap_if(ptr != align_to(ptr, alignment(elem_type))) + trap_if(ptr + length * size(elem_type) > len(cx.opts.memory)) + a = [] + for i in range(length): + a.append(load(cx, ptr + i * size(elem_type), elem_type)) + return a + +def load_record(cx, ptr, fields): + record = {} + for field in fields: + ptr = align_to(ptr, alignment(field.t)) + record[field.label] = load(cx, ptr, field.t) + ptr += size(field.t) + return record + +def load_variant(cx, ptr, cases): + disc_size = size(discriminant_type(cases)) + case_index = load_int(cx, ptr, disc_size) + ptr += disc_size + trap_if(case_index >= len(cases)) + c = cases[case_index] + ptr = align_to(ptr, max_case_alignment(cases)) + case_label = case_label_with_refinements(c, cases) + if c.t is None: + return { case_label: None } + return { case_label: load(cx, ptr, c.t) } + +def case_label_with_refinements(c, cases): + label = c.label + while c.refines is not None: + c = cases[find_case(c.refines, cases)] + label += '|' + c.label + return label + +def find_case(label, cases): + matches = [i for i,c in enumerate(cases) if c.label == label] + assert(len(matches) <= 1) + if len(matches) == 1: + return matches[0] + return -1 + +def load_flags(cx, ptr, labels): + i = load_int(cx, ptr, size_flags(labels)) + return unpack_flags_from_int(i, labels) + +def unpack_flags_from_int(i, labels): + record = {} + for l in labels: + record[l] = bool(i & 1) + i >>= 1 + return record + +def lift_own(cx, i, t): + h = cx.inst.handles.remove(t.rt, i) + trap_if(h.lend_count != 0) + trap_if(not h.own) + return h.rep + +def lift_borrow(cx, i, t): + h = cx.inst.handles.get(t.rt, i) + if h.own: + cx.track_owning_lend(h) + return h.rep + +### Storing + +def store(cx, v, t, ptr): + assert(ptr == align_to(ptr, alignment(t))) + assert(ptr + size(t) <= len(cx.opts.memory)) + match despecialize(t): + case Bool() : store_int(cx, int(bool(v)), ptr, 1) + case U8() : store_int(cx, v, ptr, 1) + case U16() : store_int(cx, v, ptr, 2) + case U32() : store_int(cx, v, ptr, 4) + case U64() : store_int(cx, v, ptr, 8) + case S8() : store_int(cx, v, ptr, 1, signed=True) + case S16() : store_int(cx, v, ptr, 2, signed=True) + case S32() : store_int(cx, v, ptr, 4, signed=True) + case S64() : store_int(cx, v, ptr, 8, signed=True) + case Float32() : store_int(cx, encode_float_as_i32(v), ptr, 4) + case Float64() : store_int(cx, encode_float_as_i64(v), ptr, 8) + case Char() : store_int(cx, char_to_i32(v), ptr, 4) + case String() : store_string(cx, v, ptr) + case List(t) : store_list(cx, v, ptr, t) + case Record(fields) : store_record(cx, v, ptr, fields) + case Variant(cases) : store_variant(cx, v, ptr, cases) + case Flags(labels) : store_flags(cx, v, ptr, labels) + case Own() : store_int(cx, lower_own(cx.opts, v, t), ptr, 4) + case Borrow() : store_int(cx, lower_borrow(cx.opts, v, t), ptr, 4) + +def store_int(cx, v, ptr, nbytes, signed = False): + cx.opts.memory[ptr : ptr+nbytes] = int.to_bytes(v, nbytes, 'little', signed=signed) + +def maybe_scramble_nan32(f): + if math.isnan(f): + if DETERMINISTIC_PROFILE: + f = core_f32_reinterpret_i32(CANONICAL_FLOAT32_NAN) + else: + f = core_f32_reinterpret_i32(random_nan_bits(32, 8)) + assert(math.isnan(f)) + return f + +def maybe_scramble_nan64(f): + if math.isnan(f): + if DETERMINISTIC_PROFILE: + f = core_f64_reinterpret_i64(CANONICAL_FLOAT64_NAN) + else: + f = core_f64_reinterpret_i64(random_nan_bits(64, 11)) + assert(math.isnan(f)) + return f + +def random_nan_bits(total_bits, exponent_bits): + fraction_bits = total_bits - exponent_bits - 1 + bits = random.getrandbits(total_bits) + bits |= ((1 << exponent_bits) - 1) << fraction_bits + bits |= 1 << random.randrange(fraction_bits - 1) + return bits + +def encode_float_as_i32(f): + return core_i32_reinterpret_f32(maybe_scramble_nan32(f)) + +def encode_float_as_i64(f): + return core_i64_reinterpret_f64(maybe_scramble_nan64(f)) + +def core_i32_reinterpret_f32(f): + return struct.unpack('!I', struct.pack('!f', f))[0] # i32.reinterpret_f32 + +def core_i64_reinterpret_f64(f): + return struct.unpack('!Q', struct.pack('!d', f))[0] # i64.reinterpret_f64 + +def char_to_i32(c): + i = ord(c) + assert(0 <= i <= 0xD7FF or 0xD800 <= i <= 0x10FFFF) + return i + +def store_string(cx, v, ptr): + begin, tagged_code_units = store_string_into_range(cx, v) + store_int(cx, begin, ptr, 4) + store_int(cx, tagged_code_units, ptr + 4, 4) + +def store_string_into_range(cx, v): + src, src_encoding, src_tagged_code_units = v + + if src_encoding == 'latin1+utf16': + if bool(src_tagged_code_units & UTF16_TAG): + src_simple_encoding = 'utf16' + src_code_units = src_tagged_code_units ^ UTF16_TAG + else: + src_simple_encoding = 'latin1' + src_code_units = src_tagged_code_units + else: + src_simple_encoding = src_encoding + src_code_units = src_tagged_code_units + + match cx.opts.string_encoding: + case 'utf8': + match src_simple_encoding: + case 'utf8' : return store_string_copy(cx, src, src_code_units, 1, 1, 'utf-8') + case 'utf16' : return store_utf16_to_utf8(cx, src, src_code_units) + case 'latin1' : return store_latin1_to_utf8(cx, src, src_code_units) + case 'utf16': + match src_simple_encoding: + case 'utf8' : return store_utf8_to_utf16(cx, src, src_code_units) + case 'utf16' : return store_string_copy(cx, src, src_code_units, 2, 2, 'utf-16-le') + case 'latin1' : return store_string_copy(cx, src, src_code_units, 2, 2, 'utf-16-le') + case 'latin1+utf16': + match src_encoding: + case 'utf8' : return store_string_to_latin1_or_utf16(cx, src, src_code_units) + case 'utf16' : return store_string_to_latin1_or_utf16(cx, src, src_code_units) + case 'latin1+utf16' : + match src_simple_encoding: + case 'latin1' : return store_string_copy(cx, src, src_code_units, 1, 2, 'latin-1') + case 'utf16' : return store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units) + +MAX_STRING_BYTE_LENGTH = (1 << 31) - 1 + +def store_string_copy(cx, src, src_code_units, dst_code_unit_size, dst_alignment, dst_encoding): + dst_byte_length = dst_code_unit_size * src_code_units + trap_if(dst_byte_length > MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(0, 0, dst_alignment, dst_byte_length) + trap_if(ptr != align_to(ptr, dst_alignment)) + trap_if(ptr + dst_byte_length > len(cx.opts.memory)) + encoded = src.encode(dst_encoding) + assert(dst_byte_length == len(encoded)) + cx.opts.memory[ptr : ptr+len(encoded)] = encoded + return (ptr, src_code_units) + +def store_utf16_to_utf8(cx, src, src_code_units): + worst_case_size = src_code_units * 3 + return store_string_to_utf8(cx, src, src_code_units, worst_case_size) + +def store_latin1_to_utf8(cx, src, src_code_units): + worst_case_size = src_code_units * 2 + return store_string_to_utf8(cx, src, src_code_units, worst_case_size) + +def store_string_to_utf8(cx, src, src_code_units, worst_case_size): + assert(src_code_units <= MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(0, 0, 1, src_code_units) + trap_if(ptr + src_code_units > len(cx.opts.memory)) + encoded = src.encode('utf-8') + assert(src_code_units <= len(encoded)) + cx.opts.memory[ptr : ptr+src_code_units] = encoded[0 : src_code_units] + if src_code_units < len(encoded): + trap_if(worst_case_size > MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(ptr, src_code_units, 1, worst_case_size) + trap_if(ptr + worst_case_size > len(cx.opts.memory)) + cx.opts.memory[ptr+src_code_units : ptr+len(encoded)] = encoded[src_code_units : ] + if worst_case_size > len(encoded): + ptr = cx.opts.realloc(ptr, worst_case_size, 1, len(encoded)) + trap_if(ptr + len(encoded) > len(cx.opts.memory)) + return (ptr, len(encoded)) + +def store_utf8_to_utf16(cx, src, src_code_units): + worst_case_size = 2 * src_code_units + trap_if(worst_case_size > MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(0, 0, 2, worst_case_size) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + worst_case_size > len(cx.opts.memory)) + encoded = src.encode('utf-16-le') + cx.opts.memory[ptr : ptr+len(encoded)] = encoded + if len(encoded) < worst_case_size: + ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded)) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + len(encoded) > len(cx.opts.memory)) + code_units = int(len(encoded) / 2) + return (ptr, code_units) + +def store_string_to_latin1_or_utf16(cx, src, src_code_units): + assert(src_code_units <= MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(0, 0, 2, src_code_units) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + src_code_units > len(cx.opts.memory)) + dst_byte_length = 0 + for usv in src: + if ord(usv) < (1 << 8): + cx.opts.memory[ptr + dst_byte_length] = ord(usv) + dst_byte_length += 1 + else: + worst_case_size = 2 * src_code_units + trap_if(worst_case_size > MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(ptr, src_code_units, 2, worst_case_size) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + worst_case_size > len(cx.opts.memory)) + for j in range(dst_byte_length-1, -1, -1): + cx.opts.memory[ptr + 2*j] = cx.opts.memory[ptr + j] + cx.opts.memory[ptr + 2*j + 1] = 0 + encoded = src.encode('utf-16-le') + cx.opts.memory[ptr+2*dst_byte_length : ptr+len(encoded)] = encoded[2*dst_byte_length : ] + if worst_case_size > len(encoded): + ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded)) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + len(encoded) > len(cx.opts.memory)) + tagged_code_units = int(len(encoded) / 2) | UTF16_TAG + return (ptr, tagged_code_units) + if dst_byte_length < src_code_units: + ptr = cx.opts.realloc(ptr, src_code_units, 2, dst_byte_length) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + dst_byte_length > len(cx.opts.memory)) + return (ptr, dst_byte_length) + +def store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units): + src_byte_length = 2 * src_code_units + trap_if(src_byte_length > MAX_STRING_BYTE_LENGTH) + ptr = cx.opts.realloc(0, 0, 2, src_byte_length) + trap_if(ptr != align_to(ptr, 2)) + trap_if(ptr + src_byte_length > len(cx.opts.memory)) + encoded = src.encode('utf-16-le') + cx.opts.memory[ptr : ptr+len(encoded)] = encoded + if any(ord(c) >= (1 << 8) for c in src): + tagged_code_units = int(len(encoded) / 2) | UTF16_TAG + return (ptr, tagged_code_units) + latin1_size = int(len(encoded) / 2) + for i in range(latin1_size): + cx.opts.memory[ptr + i] = cx.opts.memory[ptr + 2*i] + ptr = cx.opts.realloc(ptr, src_byte_length, 1, latin1_size) + trap_if(ptr + latin1_size > len(cx.opts.memory)) + return (ptr, latin1_size) + +def store_list(cx, v, ptr, elem_type): + begin, length = store_list_into_range(cx, v, elem_type) + store_int(cx, begin, ptr, 4) + store_int(cx, length, ptr + 4, 4) + +def store_list_into_range(cx, v, elem_type): + byte_length = len(v) * size(elem_type) + trap_if(byte_length >= (1 << 32)) + ptr = cx.opts.realloc(0, 0, alignment(elem_type), byte_length) + trap_if(ptr != align_to(ptr, alignment(elem_type))) + trap_if(ptr + byte_length > len(cx.opts.memory)) + for i,e in enumerate(v): + store(cx, e, elem_type, ptr + i * size(elem_type)) + return (ptr, len(v)) + +def store_record(cx, v, ptr, fields): + for f in fields: + ptr = align_to(ptr, alignment(f.t)) + store(cx, v[f.label], f.t, ptr) + ptr += size(f.t) + +def store_variant(cx, v, ptr, cases): + case_index, case_value = match_case(v, cases) + disc_size = size(discriminant_type(cases)) + store_int(cx, case_index, ptr, disc_size) + ptr += disc_size + ptr = align_to(ptr, max_case_alignment(cases)) + c = cases[case_index] + if c.t is not None: + store(cx, case_value, c.t, ptr) + +def match_case(v, cases): + assert(len(v.keys()) == 1) + key = list(v.keys())[0] + value = list(v.values())[0] + for label in key.split('|'): + case_index = find_case(label, cases) + if case_index != -1: + return (case_index, value) + +def store_flags(cx, v, ptr, labels): + i = pack_flags_into_int(v, labels) + store_int(cx, i, ptr, size_flags(labels)) + +def pack_flags_into_int(v, labels): + i = 0 + shift = 0 + for l in labels: + i |= (int(bool(v[l])) << shift) + shift += 1 + return i + +def lower_own(cx, rep, t): + h = HandleElem(rep, own=True) + return cx.inst.handles.add(t.rt, h) + +def lower_borrow(cx, rep, t): + if cx.inst is t.rt.impl: + return rep + h = HandleElem(rep, own=False, scope=cx) + cx.borrow_count += 1 + return cx.inst.handles.add(t.rt, h) + +### Flattening + +MAX_FLAT_PARAMS = 16 +MAX_FLAT_RESULTS = 1 + +def flatten_functype(ft, context): + flat_params = flatten_types(ft.param_types()) + if len(flat_params) > MAX_FLAT_PARAMS: + flat_params = ['i32'] + + flat_results = flatten_types(ft.result_types()) + if len(flat_results) > MAX_FLAT_RESULTS: + match context: + case 'lift': + flat_results = ['i32'] + case 'lower': + flat_params += ['i32'] + flat_results = [] + + return CoreFuncType(flat_params, flat_results) + +def flatten_types(ts): + return [ft for t in ts for ft in flatten_type(t)] + +def flatten_type(t): + match despecialize(t): + case Bool() : return ['i32'] + case U8() | U16() | U32() : return ['i32'] + case S8() | S16() | S32() : return ['i32'] + case S64() | U64() : return ['i64'] + case Float32() : return ['f32'] + case Float64() : return ['f64'] + case Char() : return ['i32'] + case String() | List(_) : return ['i32', 'i32'] + case Record(fields) : return flatten_record(fields) + case Variant(cases) : return flatten_variant(cases) + case Flags(labels) : return ['i32'] * num_i32_flags(labels) + case Own(_) | Borrow(_) : return ['i32'] + +def flatten_record(fields): + flat = [] + for f in fields: + flat += flatten_type(f.t) + return flat + +def flatten_variant(cases): + flat = [] + for c in cases: + if c.t is not None: + for i,ft in enumerate(flatten_type(c.t)): + if i < len(flat): + flat[i] = join(flat[i], ft) + else: + flat.append(ft) + return flatten_type(discriminant_type(cases)) + flat + +def join(a, b): + if a == b: return a + if (a == 'i32' and b == 'f32') or (a == 'f32' and b == 'i32'): return 'i32' + return 'i64' + +### Flat Lifting + +@dataclass +class Value: + t: str # 'i32'|'i64'|'f32'|'f64' + v: int|float + +@dataclass +class ValueIter: + values: list[Value] + i = 0 + def next(self, t): + v = self.values[self.i] + self.i += 1 + assert(v.t == t) + return v.v + +def lift_flat(cx, vi, t): + match despecialize(t): + case Bool() : return convert_int_to_bool(vi.next('i32')) + case U8() : return lift_flat_unsigned(vi, 32, 8) + case U16() : return lift_flat_unsigned(vi, 32, 16) + case U32() : return lift_flat_unsigned(vi, 32, 32) + case U64() : return lift_flat_unsigned(vi, 64, 64) + case S8() : return lift_flat_signed(vi, 32, 8) + case S16() : return lift_flat_signed(vi, 32, 16) + case S32() : return lift_flat_signed(vi, 32, 32) + case S64() : return lift_flat_signed(vi, 64, 64) + case Float32() : return canonicalize_nan32(vi.next('f32')) + case Float64() : return canonicalize_nan64(vi.next('f64')) + case Char() : return convert_i32_to_char(cx, vi.next('i32')) + case String() : return lift_flat_string(cx, vi) + case List(t) : return lift_flat_list(cx, vi, t) + case Record(fields) : return lift_flat_record(cx, vi, fields) + case Variant(cases) : return lift_flat_variant(cx, vi, cases) + case Flags(labels) : return lift_flat_flags(vi, labels) + case Own() : return lift_own(cx, vi.next('i32'), t) + case Borrow() : return lift_borrow(cx, vi.next('i32'), t) + +def lift_flat_unsigned(vi, core_width, t_width): + i = vi.next('i' + str(core_width)) + assert(0 <= i < (1 << core_width)) + return i % (1 << t_width) + +def lift_flat_signed(vi, core_width, t_width): + i = vi.next('i' + str(core_width)) + assert(0 <= i < (1 << core_width)) + i %= (1 << t_width) + if i >= (1 << (t_width - 1)): + return i - (1 << t_width) + return i + +def lift_flat_string(cx, vi): + ptr = vi.next('i32') + packed_length = vi.next('i32') + return load_string_from_range(cx, ptr, packed_length) + +def lift_flat_list(cx, vi, elem_type): + ptr = vi.next('i32') + length = vi.next('i32') + return load_list_from_range(cx, ptr, length, elem_type) + +def lift_flat_record(cx, vi, fields): + record = {} + for f in fields: + record[f.label] = lift_flat(cx, vi, f.t) + return record + +def lift_flat_variant(cx, vi, cases): + flat_types = flatten_variant(cases) + assert(flat_types.pop(0) == 'i32') + case_index = vi.next('i32') + trap_if(case_index >= len(cases)) + class CoerceValueIter: + def next(self, want): + have = flat_types.pop(0) + x = vi.next(have) + match (have, want): + case ('i32', 'f32') : return decode_i32_as_float(x) + case ('i64', 'i32') : return wrap_i64_to_i32(x) + case ('i64', 'f32') : return decode_i32_as_float(wrap_i64_to_i32(x)) + case ('i64', 'f64') : return decode_i64_as_float(x) + case _ : return x + c = cases[case_index] + if c.t is None: + v = None + else: + v = lift_flat(cx, CoerceValueIter(), c.t) + for have in flat_types: + _ = vi.next(have) + return { case_label_with_refinements(c, cases): v } + +def wrap_i64_to_i32(i): + assert(0 <= i < (1 << 64)) + return i % (1 << 32) + +def lift_flat_flags(vi, labels): + i = 0 + shift = 0 + for _ in range(num_i32_flags(labels)): + i |= (vi.next('i32') << shift) + shift += 32 + return unpack_flags_from_int(i, labels) + +### Flat Lowering + +def lower_flat(cx, v, t): + match despecialize(t): + case Bool() : return [Value('i32', int(v))] + case U8() : return [Value('i32', v)] + case U16() : return [Value('i32', v)] + case U32() : return [Value('i32', v)] + case U64() : return [Value('i64', v)] + case S8() : return lower_flat_signed(v, 32) + case S16() : return lower_flat_signed(v, 32) + case S32() : return lower_flat_signed(v, 32) + case S64() : return lower_flat_signed(v, 64) + case Float32() : return [Value('f32', maybe_scramble_nan32(v))] + case Float64() : return [Value('f64', maybe_scramble_nan64(v))] + case Char() : return [Value('i32', char_to_i32(v))] + case String() : return lower_flat_string(cx, v) + case List(t) : return lower_flat_list(cx, v, t) + case Record(fields) : return lower_flat_record(cx, v, fields) + case Variant(cases) : return lower_flat_variant(cx, v, cases) + case Flags(labels) : return lower_flat_flags(v, labels) + case Own() : return [Value('i32', lower_own(cx, v, t))] + case Borrow() : return [Value('i32', lower_borrow(cx, v, t))] + +def lower_flat_signed(i, core_bits): + if i < 0: + i += (1 << core_bits) + return [Value('i' + str(core_bits), i)] + +def lower_flat_string(cx, v): + ptr, packed_length = store_string_into_range(cx, v) + return [Value('i32', ptr), Value('i32', packed_length)] + +def lower_flat_list(cx, v, elem_type): + (ptr, length) = store_list_into_range(cx, v, elem_type) + return [Value('i32', ptr), Value('i32', length)] + +def lower_flat_record(cx, v, fields): + flat = [] + for f in fields: + flat += lower_flat(cx, v[f.label], f.t) + return flat + +def lower_flat_variant(cx, v, cases): + case_index, case_value = match_case(v, cases) + flat_types = flatten_variant(cases) + assert(flat_types.pop(0) == 'i32') + c = cases[case_index] + if c.t is None: + payload = [] + else: + payload = lower_flat(cx, case_value, c.t) + for i,have in enumerate(payload): + want = flat_types.pop(0) + match (have.t, want): + case ('f32', 'i32') : payload[i] = Value('i32', encode_float_as_i32(have.v)) + case ('i32', 'i64') : payload[i] = Value('i64', have.v) + case ('f32', 'i64') : payload[i] = Value('i64', encode_float_as_i32(have.v)) + case ('f64', 'i64') : payload[i] = Value('i64', encode_float_as_i64(have.v)) + case _ : pass + for want in flat_types: + payload.append(Value(want, 0)) + return [Value('i32', case_index)] + payload + +def lower_flat_flags(v, labels): + i = pack_flags_into_int(v, labels) + flat = [] + for _ in range(num_i32_flags(labels)): + flat.append(Value('i32', i & 0xffffffff)) + i >>= 32 + assert(i == 0) + return flat + +### Lifting and Lowering Values + +def lift_values(cx, max_flat, vi, ts): + flat_types = flatten_types(ts) + if len(flat_types) > max_flat: + ptr = vi.next('i32') + tuple_type = Tuple(ts) + trap_if(ptr != align_to(ptr, alignment(tuple_type))) + trap_if(ptr + size(tuple_type) > len(cx.opts.memory)) + return list(load(cx, ptr, tuple_type).values()) + else: + return [ lift_flat(cx, vi, t) for t in ts ] + +def lower_values(cx, max_flat, vs, ts, out_param = None): + flat_types = flatten_types(ts) + if len(flat_types) > max_flat: + tuple_type = Tuple(ts) + tuple_value = {str(i): v for i,v in enumerate(vs)} + if out_param is None: + ptr = cx.opts.realloc(0, 0, alignment(tuple_type), size(tuple_type)) + else: + ptr = out_param.next('i32') + trap_if(ptr != align_to(ptr, alignment(tuple_type))) + trap_if(ptr + size(tuple_type) > len(cx.opts.memory)) + store(cx, tuple_value, tuple_type, ptr) + return [ Value('i32', ptr) ] + else: + flat_vals = [] + for i in range(len(vs)): + flat_vals += lower_flat(cx, vs[i], ts[i]) + return flat_vals + +### `canon lift` + +def canon_lift(opts, inst, callee, ft, args): + cx = CallContext(opts, inst) + trap_if(not inst.may_enter) + + assert(inst.may_leave) + inst.may_leave = False + flat_args = lower_values(cx, MAX_FLAT_PARAMS, args, ft.param_types()) + inst.may_leave = True + + try: + flat_results = callee(flat_args) + except CoreWebAssemblyException: + trap() + + results = lift_values(cx, MAX_FLAT_RESULTS, ValueIter(flat_results), ft.result_types()) + + def post_return(): + if opts.post_return is not None: + opts.post_return(flat_results) + cx.exit_call() + + return (results, post_return) + +### `canon lower` + +def canon_lower(opts, inst, callee, calling_import, ft, flat_args): + cx = CallContext(opts, inst) + trap_if(not inst.may_leave) + + assert(inst.may_enter) + if calling_import: + inst.may_enter = False + + flat_args = ValueIter(flat_args) + args = lift_values(cx, MAX_FLAT_PARAMS, flat_args, ft.param_types()) + + results, post_return = callee(args) + + inst.may_leave = False + flat_results = lower_values(cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args) + inst.may_leave = True + + post_return() + cx.exit_call() + + if calling_import: + inst.may_enter = True + + return flat_results + +### `canon resource.new` + +def canon_resource_new(inst, rt, rep): + h = HandleElem(rep, own=True) + return inst.handles.add(rt, h) + +### `canon resource.drop` + +def canon_resource_drop(inst, rt, i): + h = inst.handles.remove(rt, i) + if h.own: + assert(h.scope is None) + trap_if(h.lend_count != 0) + trap_if(inst is not rt.impl and not rt.impl.may_enter) + if rt.dtor: + rt.dtor(h.rep) + else: + assert(h.scope is not None) + assert(h.scope.borrow_count > 0) + h.scope.borrow_count -= 1 + +### `canon resource.rep` + +def canon_resource_rep(inst, rt, i): + h = inst.handles.get(rt, i) + return h.rep diff --git a/documentation/CMakeLists.txt b/documentation/CMakeLists.txt deleted file mode 100644 index edc3f62..0000000 --- a/documentation/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -cmake_minimum_required(VERSION 3.14...3.22) - -project(GreeterDocs) - -# ---- Dependencies ---- - -include(../cmake/CPM.cmake) - -CPMAddPackage("gh:mosra/m.css#a0d292ec311b97fefd21e93cdefb60f88d19ede6") -CPMAddPackage(NAME Greeter SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) - -# ---- Doxygen variables ---- - -# set Doxyfile variables -set(DOXYGEN_PROJECT_NAME Greeter) -set(DOXYGEN_PROJECT_VERSION ${Greeter_VERSION}) -set(DOXYGEN_PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") -set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doxygen") - -configure_file(${CMAKE_CURRENT_LIST_DIR}/Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - -configure_file(${CMAKE_CURRENT_LIST_DIR}/conf.py ${CMAKE_CURRENT_BINARY_DIR}/conf.py) - -add_custom_target( - GenerateDocs - ${CMAKE_COMMAND} -E make_directory "${DOXYGEN_OUTPUT_DIRECTORY}" - COMMAND "${m.css_SOURCE_DIR}/documentation/doxygen.py" "${CMAKE_CURRENT_BINARY_DIR}/conf.py" - COMMAND echo "Docs written to: ${DOXYGEN_OUTPUT_DIRECTORY}" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" -) diff --git a/documentation/Doxyfile b/documentation/Doxyfile deleted file mode 100644 index 2c33e00..0000000 --- a/documentation/Doxyfile +++ /dev/null @@ -1,31 +0,0 @@ -# Configuration for Doxygen for use with CMake -# Only options that deviate from the default are included -# To create a new Doxyfile containing all available options, call `doxygen -g` - -# Get Project name and version from CMake -PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ -PROJECT_NUMBER = @DOXYGEN_PROJECT_VERSION@ - -# Add sources -INPUT = @DOXYGEN_PROJECT_ROOT@/README.md @DOXYGEN_PROJECT_ROOT@/include @DOXYGEN_PROJECT_ROOT@/documentation/pages -EXTRACT_ALL = YES -RECURSIVE = YES -OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@ - -# Use the README as a main page -USE_MDFILE_AS_MAINPAGE = @DOXYGEN_PROJECT_ROOT@/README.md - -# set relative include paths -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = @DOXYGEN_PROJECT_ROOT@/include @DOXYGEN_PROJECT_ROOT@ - -# We use m.css to generate the html documentation, so we only need XML output -GENERATE_XML = YES -GENERATE_HTML = NO -GENERATE_LATEX = NO -XML_PROGRAMLISTING = NO -CREATE_SUBDIRS = NO - -# Include all directories, files and namespaces in the documentation -# Disable to include only explicitly documented objects -M_SHOW_UNDOCUMENTED = YES diff --git a/documentation/conf.py b/documentation/conf.py deleted file mode 100644 index 6cc1a04..0000000 --- a/documentation/conf.py +++ /dev/null @@ -1,19 +0,0 @@ -DOXYFILE = 'Doxyfile' - -LINKS_NAVBAR1 = [ - (None, 'pages', [(None, 'about')]), - (None, 'namespaces', []), -] - -# Add your own navbar links using the code below. -# To find the valid link names, you can inspect the URL of a generated documentation site. - -# LINKS_NAVBAR1 = [ -# (None, 'pages', [(None, 'about')]), -# (None, 'namespaces', [(None, 'namespacegreeter')]), -# ] -# -# LINKS_NAVBAR2 = [ -# (None, 'annotated', [(None, 'classgreeter_1_1_greeter')]), -# (None, 'files', [(None, 'greeter_8h')]), -# ] diff --git a/documentation/pages/about.dox b/documentation/pages/about.dox deleted file mode 100644 index 59fc18f..0000000 --- a/documentation/pages/about.dox +++ /dev/null @@ -1,5 +0,0 @@ -/** @page about About - @section doc ModernCppStarter Documentation - This is the auto-generated documentation for the initial project of the ModernCppStarter. - It shows how we can use Doxygen to automatically build a browsable documentation for your projects. -*/ diff --git a/include/greeter/greeter.h b/include/greeter/greeter.h deleted file mode 100644 index 77dfe3b..0000000 --- a/include/greeter/greeter.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -namespace greeter { - - /** Language codes to be used with the Greeter class */ - enum class LanguageCode { EN, DE, ES, FR }; - - /** - * @brief A class for saying hello in multiple languages - */ - class Greeter { - std::string name; - - public: - /** - * @brief Creates a new greeter - * @param name the name to greet - */ - Greeter(std::string name); - - /** - * @brief Creates a localized string containing the greeting - * @param lang the language to greet in - * @return a string containing the greeting - */ - std::string greet(LanguageCode lang = LanguageCode::EN) const; - }; - -} // namespace greeter diff --git a/ref/component-model b/ref/component-model new file mode 160000 index 0000000..72219d8 --- /dev/null +++ b/ref/component-model @@ -0,0 +1 @@ +Subproject commit 72219d873419302b5e192b422d1af0a1daba7242 diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..0cddcbc --- /dev/null +++ b/run_tests.py @@ -0,0 +1,469 @@ +import definitions +from definitions import * + +def equal_modulo_string_encoding(s, t): + if s is None and t is None: + return True + if isinstance(s, (bool,int,float,str)) and isinstance(t, (bool,int,float,str)): + return s == t + if isinstance(s, tuple) and isinstance(t, tuple): + assert(isinstance(s[0], str)) + assert(isinstance(t[0], str)) + return s[0] == t[0] + if isinstance(s, dict) and isinstance(t, dict): + return all(equal_modulo_string_encoding(sv,tv) for sv,tv in zip(s.values(), t.values(), strict=True)) + if isinstance(s, list) and isinstance(t, list): + return all(equal_modulo_string_encoding(sv,tv) for sv,tv in zip(s, t, strict=True)) + assert(False) + +class Heap: + def __init__(self, arg): + self.memory = bytearray(arg) + self.last_alloc = 0 + + def realloc(self, original_ptr, original_size, alignment, new_size): + if original_ptr != 0 and new_size < original_size: + return align_to(original_ptr, alignment) + ret = align_to(self.last_alloc, alignment) + self.last_alloc = ret + new_size + if self.last_alloc > len(self.memory): + print('oom: have {} need {}'.format(len(self.memory), self.last_alloc)) + trap() + self.memory[ret : ret + original_size] = self.memory[original_ptr : original_ptr + original_size] + return ret + +def mk_opts(memory = bytearray(), encoding = 'utf8', realloc = None, post_return = None): + opts = CanonicalOptions() + opts.memory = memory + opts.string_encoding = encoding + opts.realloc = realloc + opts.post_return = post_return + return opts + +def mk_cx(memory = bytearray(), encoding = 'utf8', realloc = None, post_return = None): + opts = mk_opts(memory, encoding, realloc, post_return) + return CallContext(opts, ComponentInstance()) + +def mk_str(s): + return (s, 'utf8', len(s.encode('utf-8'))) + +def mk_tup(*a): + def mk_tup_rec(x): + if isinstance(x, list): + return { str(i):mk_tup_rec(v) for i,v in enumerate(x) } + return x + return { str(i):mk_tup_rec(v) for i,v in enumerate(a) } + +def fail(msg): + raise BaseException(msg) + +def test(t, vals_to_lift, v, + cx = mk_cx(), + dst_encoding = None, + lower_t = None, + lower_v = None): + def test_name(): + return "test({},{},{}):".format(t, vals_to_lift, v) + + vi = ValueIter([Value(ft, v) for ft,v in zip(flatten_type(t), vals_to_lift, strict=True)]) + + if v is None: + try: + got = lift_flat(cx, vi, t) + fail("{} expected trap, but got {}".format(test_name(), got)) + except Trap: + return + + got = lift_flat(cx, vi, t) + assert(vi.i == len(vi.values)) + if got != v: + fail("{} initial lift_flat() expected {} but got {}".format(test_name(), v, got)) + + if lower_t is None: + lower_t = t + if lower_v is None: + lower_v = v + + heap = Heap(5*len(cx.opts.memory)) + if dst_encoding is None: + dst_encoding = cx.opts.string_encoding + cx = mk_cx(heap.memory, dst_encoding, heap.realloc) + lowered_vals = lower_flat(cx, v, lower_t) + assert(flatten_type(lower_t) == list(map(lambda v: v.t, lowered_vals))) + + vi = ValueIter(lowered_vals) + got = lift_flat(cx, vi, lower_t) + if not equal_modulo_string_encoding(got, lower_v): + fail("{} re-lift expected {} but got {}".format(test_name(), lower_v, got)) + +# Empty record types are not permitted yet. +#test(Record([]), [], {}) +test(Record([Field('x',U8()), Field('y',U16()), Field('z',U32())]), [1,2,3], {'x':1,'y':2,'z':3}) +test(Tuple([Tuple([U8(),U8()]),U8()]), [1,2,3], {'0':{'0':1,'1':2},'1':3}) +# Empty flags types are not permitted yet. +#t = Flags([]) +#test(t, [], {}) +t = Flags(['a','b']) +test(t, [0], {'a':False,'b':False}) +test(t, [2], {'a':False,'b':True}) +test(t, [3], {'a':True,'b':True}) +test(t, [4], {'a':False,'b':False}) +test(Flags([str(i) for i in range(33)]), [0xffffffff,0x1], { str(i):True for i in range(33) }) +t = Variant([Case('x',U8()),Case('y',Float32()),Case('z',None)]) +test(t, [0,42], {'x': 42}) +test(t, [0,256], {'x': 0}) +test(t, [1,0x4048f5c3], {'y': 3.140000104904175}) +test(t, [2,0xffffffff], {'z': None}) +t = Option(Float32()) +test(t, [0,3.14], {'none':None}) +test(t, [1,3.14], {'some':3.14}) +t = Result(U8(),U32()) +test(t, [0, 42], {'ok':42}) +test(t, [1, 1000], {'error':1000}) +t = Variant([Case('w',U8()), Case('x',U8(),'w'), Case('y',U8()), Case('z',U8(),'x')]) +test(t, [0, 42], {'w':42}) +test(t, [1, 42], {'x|w':42}) +test(t, [2, 42], {'y':42}) +test(t, [3, 42], {'z|x|w':42}) +t2 = Variant([Case('w',U8())]) +test(t, [0, 42], {'w':42}, lower_t=t2, lower_v={'w':42}) +test(t, [1, 42], {'x|w':42}, lower_t=t2, lower_v={'w':42}) +test(t, [3, 42], {'z|x|w':42}, lower_t=t2, lower_v={'w':42}) + +def test_pairs(t, pairs): + for arg,expect in pairs: + test(t, [arg], expect) + +test_pairs(Bool(), [(0,False),(1,True),(2,True),(4294967295,True)]) +test_pairs(U8(), [(127,127),(128,128),(255,255),(256,0), + (4294967295,255),(4294967168,128),(4294967167,127)]) +test_pairs(S8(), [(127,127),(128,-128),(255,-1),(256,0), + (4294967295,-1),(4294967168,-128),(4294967167,127)]) +test_pairs(U16(), [(32767,32767),(32768,32768),(65535,65535),(65536,0), + ((1<<32)-1,65535),((1<<32)-32768,32768),((1<<32)-32769,32767)]) +test_pairs(S16(), [(32767,32767),(32768,-32768),(65535,-1),(65536,0), + ((1<<32)-1,-1),((1<<32)-32768,-32768),((1<<32)-32769,32767)]) +test_pairs(U32(), [((1<<31)-1,(1<<31)-1),(1<<31,1<<31),(((1<<32)-1),(1<<32)-1)]) +test_pairs(S32(), [((1<<31)-1,(1<<31)-1),(1<<31,-(1<<31)),((1<<32)-1,-1)]) +test_pairs(U64(), [((1<<63)-1,(1<<63)-1), (1<<63,1<<63), ((1<<64)-1,(1<<64)-1)]) +test_pairs(S64(), [((1<<63)-1,(1<<63)-1), (1<<63,-(1<<63)), ((1<<64)-1,-1)]) +test_pairs(Float32(), [(3.14,3.14)]) +test_pairs(Float64(), [(3.14,3.14)]) +test_pairs(Char(), [(0,'\x00'), (65,'A'), (0xD7FF,'\uD7FF'), (0xD800,None), (0xDFFF,None)]) +test_pairs(Char(), [(0xE000,'\uE000'), (0x10FFFF,'\U0010FFFF'), (0x110000,None), (0xFFFFFFFF,None)]) +test_pairs(Enum(['a','b']), [(0,{'a':None}), (1,{'b':None}), (2,None)]) + +def test_nan32(inbits, outbits): + origf = decode_i32_as_float(inbits) + f = lift_flat(mk_cx(), ValueIter([Value('f32', origf)]), Float32()) + if DETERMINISTIC_PROFILE: + assert(encode_float_as_i32(f) == outbits) + else: + assert(not math.isnan(origf) or math.isnan(f)) + cx = mk_cx(int.to_bytes(inbits, 4, 'little')) + f = load(cx, 0, Float32()) + if DETERMINISTIC_PROFILE: + assert(encode_float_as_i32(f) == outbits) + else: + assert(not math.isnan(origf) or math.isnan(f)) + +def test_nan64(inbits, outbits): + origf = decode_i64_as_float(inbits) + f = lift_flat(mk_cx(), ValueIter([Value('f64', origf)]), Float64()) + if DETERMINISTIC_PROFILE: + assert(encode_float_as_i64(f) == outbits) + else: + assert(not math.isnan(origf) or math.isnan(f)) + cx = mk_cx(int.to_bytes(inbits, 8, 'little')) + f = load(cx, 0, Float64()) + if DETERMINISTIC_PROFILE: + assert(encode_float_as_i64(f) == outbits) + else: + assert(not math.isnan(origf) or math.isnan(f)) + +test_nan32(0x7fc00000, CANONICAL_FLOAT32_NAN) +test_nan32(0x7fc00001, CANONICAL_FLOAT32_NAN) +test_nan32(0x7fe00000, CANONICAL_FLOAT32_NAN) +test_nan32(0x7fffffff, CANONICAL_FLOAT32_NAN) +test_nan32(0xffffffff, CANONICAL_FLOAT32_NAN) +test_nan32(0x7f800000, 0x7f800000) +test_nan32(0x3fc00000, 0x3fc00000) +test_nan64(0x7ff8000000000000, CANONICAL_FLOAT64_NAN) +test_nan64(0x7ff8000000000001, CANONICAL_FLOAT64_NAN) +test_nan64(0x7ffc000000000000, CANONICAL_FLOAT64_NAN) +test_nan64(0x7fffffffffffffff, CANONICAL_FLOAT64_NAN) +test_nan64(0xffffffffffffffff, CANONICAL_FLOAT64_NAN) +test_nan64(0x7ff0000000000000, 0x7ff0000000000000) +test_nan64(0x3ff0000000000000, 0x3ff0000000000000) + +def test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units): + heap = Heap(len(encoded)) + heap.memory[:] = encoded[:] + cx = mk_cx(heap.memory, src_encoding) + v = (s, src_encoding, tagged_code_units) + test(String(), [0, tagged_code_units], v, cx, dst_encoding) + +def test_string(src_encoding, dst_encoding, s): + if src_encoding == 'utf8': + encoded = s.encode('utf-8') + tagged_code_units = len(encoded) + test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units) + elif src_encoding == 'utf16': + encoded = s.encode('utf-16-le') + tagged_code_units = int(len(encoded) / 2) + test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units) + elif src_encoding == 'latin1+utf16': + try: + encoded = s.encode('latin-1') + tagged_code_units = len(encoded) + test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units) + except UnicodeEncodeError: + pass + encoded = s.encode('utf-16-le') + tagged_code_units = int(len(encoded) / 2) | UTF16_TAG + test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units) + +encodings = ['utf8', 'utf16', 'latin1+utf16'] + +fun_strings = ['', 'a', 'hi', '\x00', 'a\x00b', '\x80', '\x80b', 'ab\xefc', + '\u01ffy', 'xy\u01ff', 'a\ud7ffb', 'a\u02ff\u03ff\u04ffbc', + '\uf123', '\uf123\uf123abc', 'abcdef\uf123'] + +for src_encoding in encodings: + for dst_encoding in encodings: + for s in fun_strings: + test_string(src_encoding, dst_encoding, s) + +def test_heap(t, expect, args, byte_array): + heap = Heap(byte_array) + cx = mk_cx(heap.memory) + test(t, args, expect, cx) + +# Empty record types are not permitted yet. +#test_heap(List(Record([])), [{},{},{}], [0,3], []) +test_heap(List(Bool()), [True,False,True], [0,3], [1,0,1]) +test_heap(List(Bool()), [True,False,True], [0,3], [1,0,2]) +test_heap(List(Bool()), [True,False,True], [3,3], [0xff,0xff,0xff, 1,0,1]) +test_heap(List(U8()), [1,2,3], [0,3], [1,2,3]) +test_heap(List(U16()), [1,2,3], [0,3], [1,0, 2,0, 3,0 ]) +test_heap(List(U16()), None, [1,3], [0, 1,0, 2,0, 3,0 ]) +test_heap(List(U32()), [1,2,3], [0,3], [1,0,0,0, 2,0,0,0, 3,0,0,0]) +test_heap(List(U64()), [1,2], [0,2], [1,0,0,0,0,0,0,0, 2,0,0,0,0,0,0,0]) +test_heap(List(S8()), [-1,-2,-3], [0,3], [0xff,0xfe,0xfd]) +test_heap(List(S16()), [-1,-2,-3], [0,3], [0xff,0xff, 0xfe,0xff, 0xfd,0xff]) +test_heap(List(S32()), [-1,-2,-3], [0,3], [0xff,0xff,0xff,0xff, 0xfe,0xff,0xff,0xff, 0xfd,0xff,0xff,0xff]) +test_heap(List(S64()), [-1,-2], [0,2], [0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff]) +test_heap(List(Char()), ['A','B','c'], [0,3], [65,00,00,00, 66,00,00,00, 99,00,00,00]) +test_heap(List(String()), [mk_str("hi"),mk_str("wat")], [0,2], + [16,0,0,0, 2,0,0,0, 21,0,0,0, 3,0,0,0, + ord('h'), ord('i'), 0xf,0xf,0xf, ord('w'), ord('a'), ord('t')]) +test_heap(List(List(U8())), [[3,4,5],[],[6,7]], [0,3], + [24,0,0,0, 3,0,0,0, 0,0,0,0, 0,0,0,0, 27,0,0,0, 2,0,0,0, + 3,4,5, 6,7]) +test_heap(List(List(U16())), [[5,6]], [0,1], + [8,0,0,0, 2,0,0,0, + 5,0, 6,0]) +test_heap(List(List(U16())), None, [0,1], + [9,0,0,0, 2,0,0,0, + 0, 5,0, 6,0]) +test_heap(List(Tuple([U8(),U8(),U16(),U32()])), [mk_tup(6,7,8,9),mk_tup(4,5,6,7)], [0,2], + [6, 7, 8,0, 9,0,0,0, 4, 5, 6,0, 7,0,0,0]) +test_heap(List(Tuple([U8(),U16(),U8(),U32()])), [mk_tup(6,7,8,9),mk_tup(4,5,6,7)], [0,2], + [6,0xff, 7,0, 8,0xff,0xff,0xff, 9,0,0,0, 4,0xff, 5,0, 6,0xff,0xff,0xff, 7,0,0,0]) +test_heap(List(Tuple([U16(),U8()])), [mk_tup(6,7),mk_tup(8,9)], [0,2], + [6,0, 7, 0x0ff, 8,0, 9, 0xff]) +test_heap(List(Tuple([Tuple([U16(),U8()]),U8()])), [mk_tup([4,5],6),mk_tup([7,8],9)], [0,2], + [4,0, 5,0xff, 6,0xff, 7,0, 8,0xff, 9,0xff]) +# Empty flags types are not permitted yet. +#t = List(Flags([])) +#test_heap(t, [{},{},{}], [0,3], +# []) +#t = List(Tuple([Flags([]), U8()])) +#test_heap(t, [mk_tup({}, 42), mk_tup({}, 43), mk_tup({}, 44)], [0,3], +# [42,43,44]) +t = List(Flags(['a','b'])) +test_heap(t, [{'a':False,'b':False},{'a':False,'b':True},{'a':True,'b':True}], [0,3], + [0,2,3]) +test_heap(t, [{'a':False,'b':False},{'a':False,'b':True},{'a':False,'b':False}], [0,3], + [0,2,4]) +t = List(Flags([str(i) for i in range(9)])) +v = [{ str(i):b for i in range(9) } for b in [True,False]] +test_heap(t, v, [0,2], + [0xff,0x1, 0,0]) +test_heap(t, v, [0,2], + [0xff,0x3, 0,0]) +t = List(Flags([str(i) for i in range(17)])) +v = [{ str(i):b for i in range(17) } for b in [True,False]] +test_heap(t, v, [0,2], + [0xff,0xff,0x1,0, 0,0,0,0]) +test_heap(t, v, [0,2], + [0xff,0xff,0x3,0, 0,0,0,0]) +t = List(Flags([str(i) for i in range(33)])) +v = [{ str(i):b for i in range(33) } for b in [True,False]] +test_heap(t, v, [0,2], + [0xff,0xff,0xff,0xff,0x1,0,0,0, 0,0,0,0,0,0,0,0]) +test_heap(t, v, [0,2], + [0xff,0xff,0xff,0xff,0x3,0,0,0, 0,0,0,0,0,0,0,0]) + +def test_flatten(t, params, results): + expect = CoreFuncType(params, results) + + if len(params) > definitions.MAX_FLAT_PARAMS: + expect.params = ['i32'] + + if len(results) > definitions.MAX_FLAT_RESULTS: + expect.results = ['i32'] + got = flatten_functype(t, 'lift') + assert(got == expect) + + if len(results) > definitions.MAX_FLAT_RESULTS: + expect.params += ['i32'] + expect.results = [] + got = flatten_functype(t, 'lower') + assert(got == expect) + +test_flatten(FuncType([U8(),Float32(),Float64()],[]), ['i32','f32','f64'], []) +test_flatten(FuncType([U8(),Float32(),Float64()],[Float32()]), ['i32','f32','f64'], ['f32']) +test_flatten(FuncType([U8(),Float32(),Float64()],[U8()]), ['i32','f32','f64'], ['i32']) +test_flatten(FuncType([U8(),Float32(),Float64()],[Tuple([Float32()])]), ['i32','f32','f64'], ['f32']) +test_flatten(FuncType([U8(),Float32(),Float64()],[Tuple([Float32(),Float32()])]), ['i32','f32','f64'], ['f32','f32']) +test_flatten(FuncType([U8(),Float32(),Float64()],[Float32(),Float32()]), ['i32','f32','f64'], ['f32','f32']) +test_flatten(FuncType([U8() for _ in range(17)],[]), ['i32' for _ in range(17)], []) +test_flatten(FuncType([U8() for _ in range(17)],[Tuple([U8(),U8()])]), ['i32' for _ in range(17)], ['i32','i32']) + +def test_roundtrip(t, v): + before = definitions.MAX_FLAT_RESULTS + definitions.MAX_FLAT_RESULTS = 16 + + ft = FuncType([t],[t]) + callee = lambda x: x + + callee_heap = Heap(1000) + callee_opts = mk_opts(callee_heap.memory, 'utf8', callee_heap.realloc, lambda x: () ) + callee_inst = ComponentInstance() + lifted_callee = lambda args: canon_lift(callee_opts, callee_inst, callee, ft, args) + + caller_heap = Heap(1000) + caller_opts = mk_opts(caller_heap.memory, 'utf8', caller_heap.realloc) + caller_inst = ComponentInstance() + caller_cx = CallContext(caller_opts, caller_inst) + + flat_args = lower_flat(caller_cx, v, t) + flat_results = canon_lower(caller_opts, caller_inst, lifted_callee, True, ft, flat_args) + got = lift_flat(caller_cx, ValueIter(flat_results), t) + + if got != v: + fail("test_roundtrip({},{},{}) got {}".format(t, v, caller_args, got)) + + assert(caller_inst.may_leave and caller_inst.may_enter) + assert(callee_inst.may_leave and callee_inst.may_enter) + definitions.MAX_FLAT_RESULTS = before + +test_roundtrip(S8(), -1) +test_roundtrip(Tuple([U16(),U16()]), mk_tup(3,4)) +test_roundtrip(List(String()), [mk_str("hello there")]) +test_roundtrip(List(List(String())), [[mk_str("one"),mk_str("two")],[mk_str("three")]]) +test_roundtrip(List(Option(Tuple([String(),U16()]))), [{'some':mk_tup(mk_str("answer"),42)}]) + +def test_handles(): + before = definitions.MAX_FLAT_RESULTS + definitions.MAX_FLAT_RESULTS = 16 + + dtor_value = None + def dtor(x): + nonlocal dtor_value + dtor_value = x + + rt = ResourceType(ComponentInstance(), dtor) # usable in imports and exports + + inst = ComponentInstance() + rt2 = ResourceType(inst, dtor) # only usable in exports + opts = mk_opts() + + def host_import(args): + assert(len(args) == 2) + assert(args[0] == 42) + assert(args[1] == 44) + return ([45], lambda:()) + + def core_wasm(args): + nonlocal dtor_value + + assert(len(args) == 4) + assert(len(inst.handles.table(rt).array) == 4) + assert(inst.handles.table(rt).array[0] is None) + assert(args[0].t == 'i32' and args[0].v == 1) + assert(args[1].t == 'i32' and args[1].v == 2) + assert(args[2].t == 'i32' and args[2].v == 3) + assert(args[3].t == 'i32' and args[3].v == 13) + assert(canon_resource_rep(inst, rt, 1) == 42) + assert(canon_resource_rep(inst, rt, 2) == 43) + assert(canon_resource_rep(inst, rt, 3) == 44) + + host_ft = FuncType([ + Borrow(rt), + Borrow(rt) + ],[ + Own(rt) + ]) + args = [ + Value('i32',1), + Value('i32',3) + ] + results = canon_lower(opts, inst, host_import, True, host_ft, args) + assert(len(results) == 1) + assert(results[0].t == 'i32' and results[0].v == 4) + assert(canon_resource_rep(inst, rt, 4) == 45) + + dtor_value = None + canon_resource_drop(inst, rt, 1) + assert(dtor_value == 42) + assert(len(inst.handles.table(rt).array) == 5) + assert(inst.handles.table(rt).array[1] is None) + assert(len(inst.handles.table(rt).free) == 1) + + h = canon_resource_new(inst, rt, 46) + assert(h == 1) + assert(len(inst.handles.table(rt).array) == 5) + assert(inst.handles.table(rt).array[1] is not None) + assert(len(inst.handles.table(rt).free) == 0) + + dtor_value = None + canon_resource_drop(inst, rt, 3) + assert(dtor_value is None) + assert(len(inst.handles.table(rt).array) == 5) + assert(inst.handles.table(rt).array[3] is None) + assert(len(inst.handles.table(rt).free) == 1) + + return [Value('i32', 1), Value('i32', 2), Value('i32', 4)] + + ft = FuncType([ + Own(rt), + Own(rt), + Borrow(rt), + Borrow(rt2) + ],[ + Own(rt), + Own(rt), + Own(rt) + ]) + args = [ + 42, + 43, + 44, + 13 + ] + got,post_return = canon_lift(opts, inst, core_wasm, ft, args) + + assert(len(got) == 3) + assert(got[0] == 46) + assert(got[1] == 43) + assert(got[2] == 45) + assert(len(inst.handles.table(rt).array) == 5) + assert(all(inst.handles.table(rt).array[i] is None for i in range(4))) + assert(len(inst.handles.table(rt).free) == 4) + definitions.MAX_FLAT_RESULTS = before + +test_handles() + +print("All tests passed") diff --git a/source/greeter.cpp b/source/greeter.cpp deleted file mode 100644 index 11fb416..0000000 --- a/source/greeter.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include - -using namespace greeter; - -Greeter::Greeter(std::string _name) : name(std::move(_name)) {} - -std::string Greeter::greet(LanguageCode lang) const { - switch (lang) { - default: - case LanguageCode::EN: - return fmt::format("Hello, {}!", name); - case LanguageCode::DE: - return fmt::format("Hallo {}!", name); - case LanguageCode::ES: - return fmt::format("¡Hola {}!", name); - case LanguageCode::FR: - return fmt::format("Bonjour {}!", name); - } -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..5fee60d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,40 @@ +project(component-model-cpp) + +# ---- Add source files ---- +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(fmt CONFIG REQUIRED) + +set(SRC + traits.hpp + traits.cpp + context.hpp + context.cpp + flatten.hpp + flatten.cpp + lift.hpp + lift.cpp + load.hpp + load.cpp + lower.hpp + lower.cpp + store.hpp + store.cpp + util.hpp + util.cpp + val.hpp + val.cpp +) + +add_library(${PROJECT_NAME} SHARED ${SRC}) +target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt) + +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) +target_compile_options(${PROJECT_NAME} PUBLIC "$<$:/permissive->") + +target_include_directories( + ${PROJECT_NAME} PUBLIC $ + $ +) + diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 0000000..b5629b4 --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,66 @@ +#include "context.hpp" + +#include +#include // Include the necessary header file + +namespace cmcpp +{ + + class CanonicalOptionsImpl : public CanonicalOptions + { + private: + const GuestRealloc &_realloc; + const GuestPostReturn &_post_return; + const HostEncodeTo &_encodeTo; + + public: + CanonicalOptionsImpl(const GuestMemory &memory, HostEncoding string_encoding, + const GuestRealloc &realloc, const HostEncodeTo &encodeTo, const GuestPostReturn &post_return) + : _realloc(realloc), _encodeTo(encodeTo), _post_return(post_return) + { + this->memory = memory; + this->string_encoding = string_encoding; + } + + virtual int realloc(int ptr, int old_size, int align, int new_size) + { + return _realloc(ptr, old_size, align, new_size); + } + + virtual size_t encodeTo(void *dest, const char8_t *src, uint32_t byte_len, GuestEncoding encoding) + { + return _encodeTo(dest, src, byte_len, encoding); + } + + void post_return() + { + // Optional + if (_post_return) + { + _post_return(); + } + } + }; + + CanonicalOptionsPtr createCanonicalOptions(const GuestMemory &memory, const GuestRealloc &realloc, + const HostEncodeTo &encodeTo, + HostEncoding encoding, + const GuestPostReturn &post_return) + { + return std::make_shared(memory, encoding, realloc, encodeTo, post_return); + } + + class CallContextImpl : public CallContext + { + public: + CallContextImpl(CanonicalOptionsPtr options) { opts = options; } + }; + + CallContextPtr createCallContext(const GuestMemory &memory, const GuestRealloc &realloc, const HostEncodeTo &encodeTo, + HostEncoding encoding, const GuestPostReturn &post_return) + { + return std::make_shared( + createCanonicalOptions(memory, realloc, encodeTo, encoding, post_return)); + } + +} \ No newline at end of file diff --git a/src/context.hpp b/src/context.hpp new file mode 100644 index 0000000..c4c408f --- /dev/null +++ b/src/context.hpp @@ -0,0 +1,64 @@ +#ifndef CMCPP_HPP +#define CMCPP_HPP + +#if __has_include() +#include +#else +#include +#include +#endif + +#include +#include +#include + +namespace cmcpp +{ + + enum class HostEncoding + { + Utf8, + Utf16, + Latin1, + Latin1_Utf16 + }; + + enum class GuestEncoding + { + Utf8, + Utf16le, + Latin1 + }; + + using GuestMemory = std::span; + using GuestRealloc = std::function; + using GuestPostReturn = std::function; + using HostEncodeTo = std::function; + + class CanonicalOptions + { + public: + virtual ~CanonicalOptions() = default; + + GuestMemory memory; + HostEncoding string_encoding; + + virtual int realloc(int ptr, int old_size, int align, int new_size) = 0; + virtual size_t encodeTo(void *dest, const char8_t *src, uint32_t byte_len, GuestEncoding encoding) = 0; + virtual void post_return() = 0; + }; + using CanonicalOptionsPtr = std::shared_ptr; + CanonicalOptionsPtr createCanonicalOptions(const GuestMemory &memory, const GuestRealloc &realloc, const HostEncodeTo &encodeTo, HostEncoding encoding, const GuestPostReturn &post_return); + + class CallContext + { + public: + virtual ~CallContext() = default; + + CanonicalOptionsPtr opts; + }; + using CallContextPtr = std::shared_ptr; + CallContextPtr createCallContext(const GuestMemory &memory, const GuestRealloc &realloc, const HostEncodeTo &encodeTo, HostEncoding encoding = HostEncoding::Utf8, const GuestPostReturn &post_return = nullptr); +} + +#endif \ No newline at end of file diff --git a/src/flatten.cpp b/src/flatten.cpp new file mode 100644 index 0000000..4b5a3b7 --- /dev/null +++ b/src/flatten.cpp @@ -0,0 +1,135 @@ +#include "flatten.hpp" +#include "util.hpp" +#include "val.hpp" + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +#include + +namespace cmcpp +{ + + std::vector flatten_types(const std::vector &vs) + { + std::vector result; + for (auto v : vs) + { + std::vector flattened = flatten_type(v); + result.insert(result.end(), flattened.begin(), flattened.end()); + } + return result; + } + + // std::vector flatten_types(const std::vector &ts) + // { + // std::vector result; + // for (ValType t : ts) + // { + // std::vector flattened = flatten_type(t); + // result.insert(result.end(), flattened.begin(), flattened.end()); + // } + // return result; + // } + + std::vector flatten_record(const std::vector &fields) + { + std::vector flat; + for (auto f : fields) + { + auto flattened = flatten_type(f->v); + flat.insert(flat.end(), flattened.begin(), flattened.end()); + } + return flat; + } + std::vector flatten_variant(const std::vector &cases) + { + std::vector flat; + for (auto &c : cases) + { + if (c->v.has_value()) + { + auto fts = flatten_type(c->v.value()); + for (size_t i = 0; i < fts.size(); ++i) + { + auto ft = fts[i]; + if (i < flat.size()) + { + flat[i] = join(flat[i], ft); + } + else + { + flat.push_back(ft); + } + } + } + } + std::vector discriminantFlattened = flatten_type(discriminant_type(cases)); + flat.insert(flat.begin(), discriminantFlattened.begin(), discriminantFlattened.end()); + return flat; + } + + std::vector flatten_type(ValType kind) + { + switch (kind) + { + case ValType::Bool: + return {"i32"}; + case ValType::U8: + case ValType::U16: + case ValType::U32: + return {"i32"}; + case ValType::U64: + return {"i64"}; + case ValType::S8: + case ValType::S16: + case ValType::S32: + return {"i32"}; + case ValType::S64: + return {"i64"}; + case ValType::F32: + return {"f32"}; + case ValType::F64: + return {"f64"}; + case ValType::Char: + return {"i32"}; + case ValType::String: + return {"i32", "i32"}; + case ValType::List: + return {"i32", "i32"}; + default: + throw std::runtime_error(fmt::format("Invalid type: {}", valTypeName(kind))); + } + } + + std::vector flatten_type(const Val &_t) + { + auto t = despecialize(_t); + switch (valType(t)) + { + case ValType::Record: + return flatten_record(std::get(t)->fields); + case ValType::Variant: + return flatten_variant(std::get(t)->cases); + case ValType::Flags: + { + std::vector retVal = {}; + for (int i = 0; i < num_i32_flags(std::get(t)->labels); ++i) + { + retVal.push_back("i32"); + } + return retVal; + } + // case ValType::Own: + // case ValType::Borrow: + // return {"i32"}; + default: + return flatten_type(valType(t)); + } + } + +} diff --git a/src/flatten.hpp b/src/flatten.hpp new file mode 100644 index 0000000..2bf1201 --- /dev/null +++ b/src/flatten.hpp @@ -0,0 +1,16 @@ +#ifndef FLATTEN_HPP +#define FLATTEN_HPP + +#include "context.hpp" +#include "val.hpp" + +namespace cmcpp +{ + const int MAX_FLAT_PARAMS = 16; + const int MAX_FLAT_RESULTS = 1; + std::vector flatten_variant(const std::vector &cases); + std::vector flatten_type(ValType kind); + std::vector flatten_type(const Val &v); +} + +#endif diff --git a/src/lift.cpp b/src/lift.cpp new file mode 100644 index 0000000..b7c0ce3 --- /dev/null +++ b/src/lift.cpp @@ -0,0 +1,233 @@ +#include "lift.hpp" +#include "flatten.hpp" +#include "load.hpp" +#include "util.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace cmcpp +{ + + template + R lift_flat_unsigned(const CoreValueIter &vi, int core_width, int t_width) + { + using SignedT = std::make_signed_t; + auto xxx = wasmValType(SignedT()); + T i = vi.next(SignedT()); + if (t_width == 64) + { + return (R)i; + } + assert(0 <= i && i < (1ULL << core_width)); + return R(i % (1ULL << t_width)); + } + + template + R lift_flat_signed(const CoreValueIter &vi, int core_width, int t_width) + { + using UnsignedT = std::make_unsigned_t; + T i = vi.next(T()); + if (t_width == 64) + { + return (R)i; + } + assert(0 <= (UnsignedT)i && (UnsignedT)i < (1LL << 32)); + i %= (1LL << t_width); + if (i >= (1LL << (t_width - 1))) + { + return i - (1LL << t_width); + } + return i; + } + + string_ptr lift_flat_string(const CallContext &cx, const CoreValueIter &vi) + { + auto ptr = vi.next(int32_t()); + auto packed_length = vi.next(int32_t()); + return load_string_from_range(cx, ptr, packed_length); + } + + list_ptr lift_flat_list(const CallContext &cx, const CoreValueIter &vi, const Val &elem_type) + { + auto ptr = vi.next(int32_t()); + auto length = vi.next(int32_t()); + return load_list_from_range(cx, ptr, length, elem_type); + } + + record_ptr lift_flat_record(const CallContext &cx, const CoreValueIter &vi, const std::vector fields) + { + auto r = std::make_shared(); + for (auto f : fields) + { + r->fields.push_back(std::make_shared(f->label, lift_flat(cx, vi, f->v))); + } + return r; + } + + flags_ptr unpack_flags_from_int(uint64_t i, const std::vector &labels) + { + std::vector flags; + for (const auto &l : labels) + { + if (i & 1) + { + flags.push_back(l); + } + i >>= 1; + } + return std::make_shared(flags); + } + + int32_t wrap_i64_to_i32(int64_t i) + { + return i % (1LL << 32); + } + + class CoerceValueIter : public CoreValueIter + { + public: + std::vector &flat_types; + + CoerceValueIter(const CoreValueIter &vi, std::vector &flat_types) : CoreValueIter(vi.values, vi.i), flat_types(flat_types) + { + } + + template + T _next() const + { + flat_types.erase(flat_types.begin()); + auto have = wasmValType(values[i]); + auto want = wasmValType(T()); + if (have == ValType::S32 && want == ValType::F32) + { + return decode_i32_as_float(CoreValueIter::next(int32_t())); + } + else if (have == ValType::S64 && want == ValType::S32) + { + return wrap_i64_to_i32(CoreValueIter::next(int64_t())); + } + else if (have == ValType::S64 && want == ValType::F32) + { + return decode_i32_as_float(wrap_i64_to_i32(CoreValueIter::next(int64_t()))); + } + else if (have == ValType::S64 && want == ValType::F64) + { + return decode_i64_as_float(CoreValueIter::next(int64_t())); + } + else + { + assert(want == have); + return CoreValueIter::next(T()); + } + } + + virtual int32_t next(int32_t _) const + { + return _next(); + } + + virtual int64_t next(int64_t _) const + { + return _next(); + } + + virtual float32_t next(float32_t _) const + { + return _next(); + } + + virtual float64_t next(float64_t _) const + { + return _next(); + } + }; + + variant_ptr lift_flat_variant(const CallContext &cx, const CoreValueIter &vi, const std::vector &cases) + { + std::vector flat_types = flatten_variant(cases); + assert(flat_types[0] == "i32"); + flat_types.erase(flat_types.begin()); + int32_t case_index = vi.next(int32_t()); + assert(case_index < cases.size()); + auto c = cases[case_index]; + auto v = std::make_shared(); + if (!c->v.has_value()) + { + v->cases.push_back(std::make_shared(c->label, std::nullopt)); + } + else + { + CoerceValueIter cvi(vi, flat_types); + auto val = lift_flat(cx, cvi, c->v.value()); + v->cases.push_back(std::make_shared(c->label, val)); + } + for (auto have : flat_types) + { + vi.skip(); + } + return v; + } + + flags_ptr lift_flat_flags(const CoreValueIter &vi, const std::vector &labels) + { + uint64_t i = 0; + uint32_t shift = 0; + size_t numFlags = num_i32_flags(labels); + for (size_t _ = 0; _ < numFlags; ++_) + { + i |= (vi.next(int32_t()) << shift); + shift += 32; + } + return unpack_flags_from_int(i, labels); + } + + Val lift_flat(const CallContext &cx, const CoreValueIter &vi, const Val &_t) + { + auto t = despecialize(_t); + switch (valType(t)) + { + case ValType::Bool: + return convert_int_to_bool(vi.next(int32_t())); + case ValType::U8: + return lift_flat_unsigned(vi, 32, 8); + case ValType::U16: + return lift_flat_unsigned(vi, 32, 16); + case ValType::U32: + return lift_flat_unsigned(vi, 32, 32); + case ValType::U64: + return lift_flat_unsigned(vi, 64, 64); + case ValType::S8: + return lift_flat_signed(vi, 32, 8); + case ValType::S16: + return lift_flat_signed(vi, 32, 16); + case ValType::S32: + return lift_flat_signed(vi, 32, 32); + case ValType::S64: + return lift_flat_signed(vi, 64, 64); + case ValType::F32: + return canonicalize_nan32(vi.next(float32_t())); + case ValType::F64: + return canonicalize_nan64(vi.next(float64_t())); + case ValType::Char: + return convert_i32_to_char(cx, vi.next(int32_t())); + case ValType::String: + return lift_flat_string(cx, vi); + case ValType::List: + return lift_flat_list(cx, vi, std::get(t)->lt); + case ValType::Record: + return lift_flat_record(cx, vi, std::get(t)->fields); + case ValType::Variant: + return lift_flat_variant(cx, vi, std::get(t)->cases); + case ValType::Flags: + return lift_flat_flags(vi, std::get(t)->labels); + default: + throw std::runtime_error(fmt::format("Invalid type: {}", valTypeName(valType(t)))); + } + } +} \ No newline at end of file diff --git a/src/lift.hpp b/src/lift.hpp new file mode 100644 index 0000000..54a232c --- /dev/null +++ b/src/lift.hpp @@ -0,0 +1,13 @@ +#ifndef LIFT_HPP +#define LIFT_HPP + +#include "context.hpp" +#include "util.hpp" + +namespace cmcpp +{ + flags_ptr unpack_flags_from_int(uint64_t i, const std::vector &labels); + Val lift_flat(const CallContext &cx, const CoreValueIter &vi, const Val &t); +} + +#endif \ No newline at end of file diff --git a/src/load.cpp b/src/load.cpp new file mode 100644 index 0000000..b84d8a5 --- /dev/null +++ b/src/load.cpp @@ -0,0 +1,224 @@ +#include "load.hpp" +#include "util.hpp" +#include "lift.hpp" + +#include +#include + +namespace cmcpp +{ + + template + T load_int(const CallContext &cx, uint32_t ptr, uint8_t) + { + T retVal = 0; + for (size_t i = 0; i < sizeof(T); ++i) + { + retVal |= static_cast(cx.opts->memory[ptr + i]) << (8 * i); + } + return retVal; + } + + std::shared_ptr load_string_from_range(const CallContext &cx, uint32_t ptr, uint32_t tagged_code_units) + { + HostEncoding encoding; + uint32_t byte_length = tagged_code_units; + uint32_t alignment = 1; + if (cx.opts->string_encoding == HostEncoding::Utf8) + { + alignment = 1; + byte_length = tagged_code_units; + encoding = HostEncoding::Utf8; + } + else if (cx.opts->string_encoding == HostEncoding::Utf16) + { + alignment = 2; + byte_length = 2 * tagged_code_units; + encoding = HostEncoding::Latin1_Utf16; + } + else if (cx.opts->string_encoding == HostEncoding::Latin1_Utf16) + { + alignment = 2; + if (tagged_code_units & UTF16_TAG) + { + byte_length = 2 * (tagged_code_units ^ UTF16_TAG); + encoding = HostEncoding::Latin1_Utf16; + } + else + { + byte_length = tagged_code_units; + encoding = HostEncoding::Latin1; + } + } + assert(isAligned(ptr, alignment)); + assert(ptr + byte_length <= cx.opts->memory.size()); + auto [dec_str, dec_len] = decode(&cx.opts->memory[ptr], byte_length, encoding); + return std::make_shared(dec_str, dec_len); + } + + std::shared_ptr load_string(const CallContext &cx, uint32_t ptr) + { + uint32_t begin = load_int(cx, ptr, 4); + uint32_t tagged_code_units = load_int(cx, ptr + 4, 4); + return load_string_from_range(cx, begin, tagged_code_units); + } + + std::shared_ptr load_list_from_range(const CallContext &cx, uint32_t ptr, uint32_t length, const Val &t) + { + assert(ptr == align_to(ptr, alignment(t))); + assert(ptr + length * elem_size(t) <= cx.opts->memory.size()); + auto list = std::make_shared(t); + for (uint32_t i = 0; i < length; ++i) + { + list->vs.push_back(load(cx, ptr + i * elem_size(t), t)); + } + return list; + } + + std::shared_ptr load_list(const CallContext &cx, uint32_t ptr, const Val &t) + { + uint32_t begin = load_int(cx, ptr, 4); + uint32_t length = load_int(cx, ptr + 4, 4); + return load_list_from_range(cx, begin, length, t); + } + + /* + def load_record(cx, ptr, fields): + record = {} + for field in fields: + ptr = align_to(ptr, alignment(field.t)) + record[field.label] = load(cx, ptr, field.t) + ptr += elem_size(field.t) + return record + */ + + std::shared_ptr load_record(const CallContext &cx, uint32_t ptr, const std::vector &fields) + { + auto record = std::make_shared(); + for (auto field : fields) + { + ptr = align_to(ptr, alignment(field->v)); + record->fields.push_back(std::make_shared(field->label, load(cx, ptr, field->v))); + ptr += elem_size(field->v); + } + return record; + } + + /* + def case_label_with_refinements(c, cases): + label = c.label + while c.refines is not None: + c = cases[find_case(c.refines, cases)] + label += '|' + c.label + return label + */ + + std::string case_label_with_refinements(case_ptr c, const std::vector &cases) + { + std::string label = c->label; + while (c->refines.has_value()) + { + c = cases[find_case(c->refines.value(), cases)]; + label += '|' + c->label; + } + return label; + } + + /* + def load_variant(cx, ptr, cases): + disc_size = elem_size(discriminant_type(cases)) + case_index = load_int(cx, ptr, disc_size) + ptr += disc_size + trap_if(case_index >= len(cases)) + c = cases[case_index] + ptr = align_to(ptr, max_case_alignment(cases)) + case_label = case_label_with_refinements(c, cases) + if c.t is None: + return { case_label: None } + return { case_label: load(cx, ptr, c.t) } + */ + + std::shared_ptr load_variant(const CallContext &cx, uint32_t ptr, const std::vector &cases) + { + uint32_t disc_size = elem_size(discriminant_type(cases)); + uint32_t case_index = load_int(cx, ptr, disc_size); + ptr += disc_size; + assert(case_index < cases.size()); + auto c = cases[case_index]; + ptr = align_to(ptr, max_case_alignment(cases)); + auto case_label = case_label_with_refinements(c, cases); + if (!c->v.has_value()) + { + return std::make_shared(std::vector{std::make_shared(case_label)}); + } + return std::make_shared(std::vector{std::make_shared(case_label, load(cx, ptr, c->v.value()))}); + } + + /* + def load_flags(cx, ptr, labels): + i = load_int(cx, ptr, elem_size_flags(labels)) + return unpack_flags_from_int(i, labels) + */ + + flags_ptr load_flags(const CallContext &cx, uint32_t ptr, const std::vector &labels) + { + uint32_t i = load_int(cx, ptr, elem_size_flags(labels)); + return unpack_flags_from_int(i, labels); + } + + Val load(const CallContext &cx, uint32_t ptr, ValType t) + { + switch (t) + { + case ValType::Bool: + return convert_int_to_bool(load_int(cx, ptr, 1)); + case ValType::U8: + return load_int(cx, ptr, 1); + case ValType::U16: + return load_int(cx, ptr, 2); + case ValType::U32: + return load_int(cx, ptr, 4); + case ValType::U64: + return load_int(cx, ptr, 8); + case ValType::S8: + return load_int(cx, ptr, 1); + case ValType::S16: + return load_int(cx, ptr, 2); + case ValType::S32: + return load_int(cx, ptr, 4); + case ValType::S64: + return load_int(cx, ptr, 8); + case ValType::F32: + return decode_i32_as_float(load_int(cx, ptr, 4)); + case ValType::F64: + return decode_i64_as_float(load_int(cx, ptr, 8)); + case ValType::Char: + return convert_i32_to_char(cx, load_int(cx, ptr, 4)); + case ValType::String: + return load_string(cx, ptr); + default: + throw std::runtime_error("Invalid type"); + } + } + + Val load(const CallContext &cx, uint32_t ptr, const Val &v) + { + switch (valType(v)) + { + case ValType::List: + return load_list(cx, ptr, std::get(v)->lt); + case ValType::Record: + return load_record(cx, ptr, std::get(v)->fields); + case ValType::Variant: + return load_variant(cx, ptr, std::get(v)->cases); + case ValType::Flags: + return load_flags(cx, ptr, std::get(v)->labels); + // case ValType::Own: + // return lift_own(cx, load_int(cx, ptr, 4), static_cast(t)); + // case ValType::Borrow: + // return lift_borrow(cx, load_int(cx, ptr, 4), static_cast(t)); + default: + return load(cx, ptr, valType(v)); + } + } +} \ No newline at end of file diff --git a/src/load.hpp b/src/load.hpp new file mode 100644 index 0000000..67e20d0 --- /dev/null +++ b/src/load.hpp @@ -0,0 +1,15 @@ +#ifndef LOAD_HPP +#define LOAD_HPP + +#include "context.hpp" +#include "val.hpp" + +namespace cmcpp +{ + std::shared_ptr load_string(const CallContext &cx, uint32_t ptr); + std::shared_ptr load_string_from_range(const CallContext &cx, uint32_t ptr, uint32_t tagged_code_units); + std::shared_ptr load_list_from_range(const CallContext &cx, uint32_t ptr, uint32_t length, const Val &t); + Val load(const CallContext &cx, uint32_t ptr, ValType t); + Val load(const CallContext &cx, uint32_t ptr, const Val &v); +} +#endif \ No newline at end of file diff --git a/src/lower.cpp b/src/lower.cpp new file mode 100644 index 0000000..00d2d09 --- /dev/null +++ b/src/lower.cpp @@ -0,0 +1,178 @@ +#include "lower.hpp" +#include "util.hpp" +#include "store.hpp" +#include "flatten.hpp" + +#include +#include + +namespace cmcpp +{ + + std::vector lower_flat_string(const CallContext &cx, const string_ptr &string) + { + auto [ptr, packed_length] = store_string_into_range(cx, string); + return {(int32_t)ptr, (int32_t)packed_length}; + } + + std::vector lower_flat_list(const CallContext &cx, const list_ptr &v, const Val &elem_type) + { + auto [ptr, length] = store_list_into_range(cx, v, elem_type); + return {(int32_t)ptr, (int32_t)length}; + } + + std::vector lower_flat_record(const CallContext &cx, const record_ptr &v, const std::vector &fields) + { + std::vector flat; + for (auto f : fields) + { + auto flat_field = lower_flat(cx, v->find(f->label), f->v); + flat.insert(flat.end(), flat_field.begin(), flat_field.end()); + } + return flat; + } + + uint32_t pack_flags_into_int(const flags_ptr &v, const std::vector &labels) + { + uint32_t i = 0; + uint32_t shift = 0; + for (auto l : labels) + { + i |= (static_cast(std::find(v->labels.begin(), v->labels.end(), l) != v->labels.end()) << shift); + shift += 1; + } + return i; + } + + std::vector lower_flat_flags(const flags_ptr &v, const std::vector &labels) + { + int32_t i = pack_flags_into_int(v, labels); + std::vector flat; + for (size_t _ = 0; _ < num_i32_flags(labels); ++_) + { + flat.push_back((int32_t)(i & 0xffffffff)); + i >>= 32; + } + return flat; + } + + /* + def lower_flat_variant(cx, v, cases): + case_index, case_value = match_case(v, cases) + flat_types = flatten_variant(cases) + assert(flat_types.pop(0) == 'i32') + c = cases[case_index] + if c.t is None: + payload = [] + else: + payload = lower_flat(cx, case_value, c.t) + for i,(fv,have) in enumerate(zip(payload, flatten_type(c.t))): + want = flat_types.pop(0) + match (have, want): + case ('f32', 'i32') : payload[i] = encode_float_as_i32(fv) + case ('i32', 'i64') : payload[i] = fv + case ('f32', 'i64') : payload[i] = encode_float_as_i32(fv) + case ('f64', 'i64') : payload[i] = encode_float_as_i64(fv) + case _ : assert(have == want) + for _ in flat_types: + payload.append(0) + return [case_index] + payload + */ + + std::vector lower_flat_variant(const CallContext &cx, const variant_ptr &v, const std::vector &cases) + { + auto [case_index, case_value] = match_case(v, cases); + std::vector flat_types = flatten_variant(cases); + assert(flat_types[0] == "i32"); + flat_types.erase(flat_types.begin()); + auto c = cases[case_index]; + std::vector payload; + if (c->v.has_value()) + { + payload = lower_flat(cx, case_value.value(), c->v.value()); + auto vTypes = flatten_type(c->v.value()); + for (size_t i = 0; i < payload.size(); ++i) + { + auto fv = payload[i]; + auto have = vTypes[i]; + auto want = flat_types[0]; + flat_types.erase(flat_types.begin()); + if (have == "f32" && want == "i32") + { + payload[i] = encode_float_as_i32(std::get(fv)); + } + else if (have == "i32" && want == "i64") + { + payload[i] = fv; + } + else if (have == "f32" && want == "i64") + { + payload[i] = encode_float_as_i32(std::get(fv)); + } + else if (have == "f64" && want == "i64") + { + payload[i] = static_cast(encode_float_as_i64(std::get(fv))); + } + else + { + assert(have == want); + } + } + } + for (auto _ : flat_types) + { + payload.push_back(0); + } + payload.insert(payload.begin(), case_index); + return payload; + } + + std::vector lower_flat(const CallContext &cx, const Val &v, const Val &_t) + { + auto t = despecialize(_t); + switch (valType(t)) + { + case ValType::Bool: + return {static_cast(std::get(v))}; + case ValType::U8: + return {static_cast(std::get(v))}; + case ValType::U16: + return {static_cast(std::get(v))}; + case ValType::U32: + return {static_cast(std::get(v))}; + case ValType::U64: + return {static_cast(std::get(v))}; + case ValType::S8: + return {static_cast(std::get(v))}; + case ValType::S16: + return {static_cast(std::get(v))}; + case ValType::S32: + return {static_cast(std::get(v))}; + case ValType::S64: + return {static_cast(std::get(v))}; + case ValType::F32: + return {static_cast(maybe_scramble_nan32(std::get(v)))}; + case ValType::F64: + return {static_cast(maybe_scramble_nan64(std::get(v)))}; + case ValType::Char: + return {char_to_i32(std::get(v))}; + case ValType::String: + return lower_flat_string(cx, std::get(v)); + case ValType::List: + return lower_flat_list(cx, std::get(v), std::get(t)->lt); + case ValType::Record: + return lower_flat_record(cx, std::get(v), std::get(t)->fields); + case ValType::Variant: + return lower_flat_variant(cx, std::get(v), std::get(t)->cases); + case ValType::Flags: + return lower_flat_flags(std::get(v), std::get(t)->labels); + // case ValType::Own: + // return lower_own(cx, std::get(v)); + // case ValType::Borrow: + // return lower_borrow(cx, std::get(v)); + default: + throw std::runtime_error(fmt::format("Invalid type: {}", valTypeName(valType(t)))); + } + } + +} diff --git a/src/lower.hpp b/src/lower.hpp new file mode 100644 index 0000000..3816fd1 --- /dev/null +++ b/src/lower.hpp @@ -0,0 +1,13 @@ +#ifndef LOWER_HPP +#define LOWER_HPP + +#include "context.hpp" +#include "val.hpp" +#include "util.hpp" + +namespace cmcpp +{ + std::vector lower_flat(const CallContext &cx, const Val &v, const Val &t); +} + +#endif \ No newline at end of file diff --git a/src/store.cpp b/src/store.cpp new file mode 100644 index 0000000..0e008f5 --- /dev/null +++ b/src/store.cpp @@ -0,0 +1,363 @@ +#include "store.hpp" +#include "util.hpp" + +#include +#include +#include +// #include + +namespace cmcpp +{ + const uint32_t MAX_STRING_BYTE_LENGTH = (1U << 31) - 1; + + std::pair store_string_copy(const CallContext &cx, const char8_t *src, uint32_t src_code_units, uint32_t dst_code_unit_size, uint32_t dst_alignment, GuestEncoding dst_encoding) + { + uint32_t dst_byte_length = dst_code_unit_size * src_code_units; + assert(dst_byte_length <= MAX_STRING_BYTE_LENGTH); + uint32_t ptr = cx.opts->realloc(0, 0, dst_alignment, dst_byte_length); + assert(ptr == align_to(ptr, dst_alignment)); + assert(ptr + dst_byte_length <= cx.opts->memory.size()); + auto enc_len = encodeTo(&cx.opts->memory[ptr], src, src_code_units, dst_encoding); + assert(dst_byte_length == enc_len); + return std::make_pair(ptr, src_code_units); + } + + std::pair store_string_to_utf8(const CallContext &cx, const char8_t *src, uint32_t src_code_units, uint32_t worst_case_size) + { + assert(worst_case_size <= MAX_STRING_BYTE_LENGTH); + uint32_t ptr = cx.opts->realloc(0, 0, 1, worst_case_size); + assert(ptr + src_code_units <= cx.opts->memory.size()); + auto enc_len = encodeTo(&cx.opts->memory[ptr], src, worst_case_size, GuestEncoding::Utf8); + if (enc_len < worst_case_size) + { + assert(enc_len <= MAX_STRING_BYTE_LENGTH); + ptr = cx.opts->realloc(ptr, src_code_units, 1, enc_len); + } + return std::make_pair(ptr, enc_len); + } + + std::pair store_utf16_to_utf8(const CallContext &cx, const char8_t *src, uint32_t src_code_units) + { + uint32_t worst_case_size = src_code_units * 3; + return store_string_to_utf8(cx, src, src_code_units, worst_case_size); + } + + std::pair store_latin1_to_utf8(const CallContext &cx, const char8_t *src, uint32_t src_code_units) + { + uint32_t worst_case_size = src_code_units * 2; + return store_string_to_utf8(cx, src, src_code_units, worst_case_size); + } + + std::pair store_utf8_to_utf16(const CallContext &cx, const char8_t *src, uint32_t src_code_units) + { + uint32_t worst_case_size = 2 * src_code_units; + if (worst_case_size > MAX_STRING_BYTE_LENGTH) + throw std::runtime_error("Worst case size exceeds maximum string byte length"); + uint32_t ptr = cx.opts->realloc(0, 0, 2, worst_case_size); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("Pointer misaligned"); + if (ptr + worst_case_size > cx.opts->memory.size()) + throw std::runtime_error("Out of bounds access"); + auto enc_len = encodeTo(&cx.opts->memory[ptr], src, src_code_units, GuestEncoding::Utf16le); + if (enc_len < worst_case_size) + { + uint32_t cleanup_ptr = ptr; + ptr = cx.opts->realloc(ptr, worst_case_size, 2, enc_len); + std::memcpy(&cx.opts->memory[ptr], &cx.opts->memory[ptr], enc_len); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("Pointer misaligned"); + if (ptr + enc_len > cx.opts->memory.size()) + throw std::runtime_error("Out of bounds access"); + } + uint32_t code_units = static_cast(enc_len / 2); + return std::make_pair(ptr, code_units); + } + + std::pair store_string_to_latin1_or_utf16(const CallContext &cx, const char8_t *src, uint32_t src_code_units) + { + assert(src_code_units <= MAX_STRING_BYTE_LENGTH); + uint32_t ptr = cx.opts->realloc(0, 0, 2, src_code_units); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("Pointer misaligned"); + if (ptr + src_code_units > cx.opts->memory.size()) + throw std::runtime_error("Out of bounds access"); + uint32_t dst_byte_length = 0; + for (size_t i = 0; i < src_code_units; ++i) + { + char8_t usv = *src; + if (static_cast(usv) < (1 << 8)) + { + cx.opts->memory[ptr + dst_byte_length] = static_cast(usv); + dst_byte_length += 1; + } + else + { + uint32_t worst_case_size = 2 * src_code_units; + if (worst_case_size > MAX_STRING_BYTE_LENGTH) + throw std::runtime_error("Worst case size exceeds maximum string byte length"); + ptr = cx.opts->realloc(ptr, src_code_units, 2, worst_case_size); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("Pointer misaligned"); + if (ptr + worst_case_size > cx.opts->memory.size()) + throw std::runtime_error("Out of bounds access"); + for (int j = dst_byte_length - 1; j >= 0; --j) + { + cx.opts->memory[ptr + 2 * j] = cx.opts->memory[ptr + j]; + cx.opts->memory[ptr + 2 * j + 1] = 0; + } + auto enc_len = encodeTo(&cx.opts->memory[ptr + 2 * dst_byte_length], src, src_code_units, GuestEncoding::Utf16le); + if (worst_case_size > enc_len) + { + // TODO - skipping the truncation for now... + // ptr = cx.opts->realloc(ptr, worst_case_size, 2, enc_len); + // if (ptr != align_to(ptr, 2)) + // throw std::runtime_error("Pointer misaligned"); + // if (ptr + enc_len > cx.opts->memory.size()) + // throw std::runtime_error("Out of bounds access"); + } + uint32_t tagged_code_units = static_cast(enc_len / 2) | UTF16_TAG; + return std::make_pair(ptr, tagged_code_units); + } + } + if (dst_byte_length < src_code_units) + { + ptr = cx.opts->realloc(ptr, src_code_units, 2, dst_byte_length); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("Pointer misaligned"); + if (ptr + dst_byte_length > cx.opts->memory.size()) + throw std::runtime_error("Out of bounds access"); + } + return std::make_pair(ptr, dst_byte_length); + } + + std::pair store_probably_utf16_to_latin1_or_utf16(const CallContext &cx, const char8_t *src, uint32_t src_code_units) + { + uint32_t src_byte_length = 2 * src_code_units; + if (src_byte_length > MAX_STRING_BYTE_LENGTH) + throw std::runtime_error("src_byte_length exceeds MAX_STRING_BYTE_LENGTH"); + + uint32_t ptr = cx.opts->realloc(0, 0, 2, src_byte_length); + if (ptr != align_to(ptr, 2)) + throw std::runtime_error("ptr is not aligned"); + + if (ptr + src_byte_length > cx.opts->memory.size()) + throw std::runtime_error("Not enough memory"); + + auto enc_len = encodeTo(&cx.opts->memory[ptr], src, src_code_units, GuestEncoding::Utf16le); + const uint8_t *enc_src_ptr = &cx.opts->memory[ptr]; + if (std::any_of(enc_src_ptr, enc_src_ptr + enc_len, [](uint8_t c) + { return static_cast(c) >= (1 << 8); })) + { + uint32_t tagged_code_units = static_cast(enc_len / 2) | UTF16_TAG; + return std::make_pair(ptr, tagged_code_units); + } + + uint32_t latin1_size = static_cast(enc_len / 2); + for (uint32_t i = 0; i < latin1_size; ++i) + cx.opts->memory[ptr + i] = cx.opts->memory[ptr + 2 * i]; + + ptr = cx.opts->realloc(ptr, src_byte_length, 1, latin1_size); + if (ptr + latin1_size > cx.opts->memory.size()) + throw std::runtime_error("Not enough memory"); + + return std::make_pair(ptr, latin1_size); + } + + std::pair store_string_into_range(const CallContext &cx, const string_ptr &v, HostEncoding src_encoding) + { + const char8_t *src = v->ptr; + const size_t src_tagged_code_units = v->len; + HostEncoding src_simple_encoding; + uint32_t src_code_units; + + if (src_encoding == HostEncoding::Latin1_Utf16) + { + if (src_tagged_code_units & UTF16_TAG) + { + src_simple_encoding = HostEncoding::Utf16; + src_code_units = src_tagged_code_units ^ UTF16_TAG; + } + else + { + src_simple_encoding = HostEncoding::Latin1; + src_code_units = src_tagged_code_units; + } + } + else + { + src_simple_encoding = src_encoding; + src_code_units = src_tagged_code_units; + } + + if (cx.opts->string_encoding == HostEncoding::Utf8) + { + if (src_simple_encoding == HostEncoding::Utf8) + return store_string_copy(cx, src, src_code_units, 1, 1, GuestEncoding::Utf8); + else if (src_simple_encoding == HostEncoding::Utf16) + return store_utf16_to_utf8(cx, src, src_code_units); + else if (src_simple_encoding == HostEncoding::Latin1) + return store_latin1_to_utf8(cx, src, src_code_units); + } + else if (cx.opts->string_encoding == HostEncoding::Utf16) + { + if (src_simple_encoding == HostEncoding::Utf8) + return store_utf8_to_utf16(cx, src, src_code_units); + else if (src_simple_encoding == HostEncoding::Utf16 || src_simple_encoding == HostEncoding::Latin1) + return store_string_copy(cx, src, src_code_units, 2, 2, GuestEncoding::Utf16le); + } + else if (cx.opts->string_encoding == HostEncoding::Latin1_Utf16) + { + if (src_encoding == HostEncoding::Utf8 || src_encoding == HostEncoding::Utf16) + return store_string_to_latin1_or_utf16(cx, src, src_code_units); + else if (src_encoding == HostEncoding::Latin1_Utf16) + { + if (src_simple_encoding == HostEncoding::Latin1) + return store_string_copy(cx, src, src_code_units, 1, 2, GuestEncoding::Latin1); + else if (src_simple_encoding == HostEncoding::Utf16) + return store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units); + } + } + + assert(false); + return std::make_pair(0, 0); + } + + template + void store_int(const CallContext &cx, const T &v, uint32_t ptr, uint8_t nbytes) + { + for (size_t i = 0; i < nbytes; ++i) + { + cx.opts->memory[ptr + i] = static_cast(v >> (8 * i)); + } + } + + void store_string(const CallContext &cx, const string_ptr &v, uint32_t ptr) + { + auto [begin, tagged_code_units] = store_string_into_range(cx, v); + store_int(cx, begin, ptr, 4); + store_int(cx, tagged_code_units, ptr + 4, 4); + } + + std::pair store_list_into_range(const CallContext &cx, const list_ptr &v, const Val &elem_type) + { + size_t nbytes = elem_size(elem_type); + auto byte_length = v->vs.size() * nbytes; + if (byte_length >= std::numeric_limits::max()) + { + throw std::runtime_error("byte_length exceeds limit"); + } + uint32_t ptr = cx.opts->realloc(0, 0, alignment(elem_type), byte_length); + if (ptr != align_to(ptr, alignment(elem_type))) + { + throw std::runtime_error("ptr not aligned"); + } + if (ptr + byte_length > cx.opts->memory.size()) + { + throw std::runtime_error("memory overflow"); + } + for (size_t i = 0; i < v->vs.size(); ++i) + { + store(cx, v->vs[i], elem_type, ptr + i * nbytes); + } + return {ptr, v->vs.size()}; + } + + void store_list(const CallContext &cx, const list_ptr &list, uint32_t ptr, const Val &elem_type) + { + auto [begin, length] = store_list_into_range(cx, list, elem_type); + store_int(cx, begin, ptr, 4); + store_int(cx, length, ptr + 4, 4); + } + + // void store_record(const CallContext &cx, const record_ptr &record, uint32_t ptr) + // { + // for (auto f : record->fields) + // { + // ptr = align_to(ptr, alignment(f->ft)); + // store(cx, f->v, ptr); + // ptr += elem_size(f->ft); + // } + // } + + // void store_variant(const CallContext &cx, const variant_ptr &v, uint32_t ptr) + // { + // auto [case_index, case_value] = match_case(v); + // auto disc_size = elem_size(discriminant_type(v->cases)); + // store_int(cx, case_index, ptr, disc_size); + // ptr += disc_size; + // ptr = align_to(ptr, max_case_alignment(v->cases)); + // const auto &c = v->cases[case_index]; + // if (c->v) + // { + // store(cx, c->v, ptr); + // } + // } + + void store(const CallContext &cx, const Val &v, const Val &t, uint32_t ptr) + { + assert(ptr == align_to(ptr, alignment(valType(v)))); + assert(ptr + elem_size(v) <= cx.opts->memory.size()); + switch (valType(v)) + { + case ValType::Bool: + store_int(cx, std::get(v), ptr, 1); + break; + case ValType::U8: + store_int(cx, std::get(v), ptr, 1); + break; + case ValType::U16: + store_int(cx, std::get(v), ptr, 2); + break; + case ValType::U32: + store_int(cx, std::get(v), ptr, 4); + break; + case ValType::U64: + store_int(cx, std::get(v), ptr, 8); + break; + case ValType::S8: + store_int(cx, std::get(v), ptr, 1); + break; + case ValType::S16: + store_int(cx, std::get(v), ptr, 2); + break; + case ValType::S32: + store_int(cx, std::get(v), ptr, 4); + break; + case ValType::S64: + store_int(cx, std::get(v), ptr, 8); + break; + case ValType::F32: + store_int(cx, encode_float_as_i32(std::get(v)), ptr, 4); + break; + case ValType::F64: + store_int(cx, encode_float_as_i64(std::get(v)), ptr, 8); + break; + case ValType::Char: + store_int(cx, char_to_i32(std::get(v)), ptr, 4); + break; + case ValType::String: + store_string(cx, std::get(v), ptr); + break; + case ValType::List: + store_list(cx, std::get(v), ptr, std::get(t)->lt); + break; + // case ValType::Record: + // store_record(cx, std::get(v), ptr); + // break; + // case ValType::Variant: + // store_variant(cx, std::get(v), ptr); + // break; + // case ValType::Flags: + // store_flags(cx, v.flags(), ptr); + // break; + // case ValType::Own: + // store_int(cx, lower_own(cx.opts, v, t), ptr, 4); + // break; + // case ValType::Borrow: + // store_int(cx, lower_borrow(cx.opts, v, t), ptr, 4); + // break; + default: + throw std::runtime_error("Unknown type"); + } + } +} \ No newline at end of file diff --git a/src/store.hpp b/src/store.hpp new file mode 100644 index 0000000..77ebda8 --- /dev/null +++ b/src/store.hpp @@ -0,0 +1,13 @@ +#ifndef STORE_HPP +#define STORE_HPP + +#include "context.hpp" +#include "val.hpp" + +namespace cmcpp +{ + void store(const CallContext &cx, const Val &v, const Val &t, uint32_t ptr); + std::pair store_list_into_range(const CallContext &cx, const list_ptr &v, const Val &elem_type); + std::pair store_string_into_range(const CallContext &cx, const string_ptr &v, HostEncoding src_encoding = HostEncoding::Utf8); +} +#endif \ No newline at end of file diff --git a/src/traits.cpp b/src/traits.cpp new file mode 100644 index 0000000..4f65e92 --- /dev/null +++ b/src/traits.cpp @@ -0,0 +1 @@ +#include "traits.hpp" diff --git a/src/traits.hpp b/src/traits.hpp new file mode 100644 index 0000000..2578ddb --- /dev/null +++ b/src/traits.hpp @@ -0,0 +1,269 @@ +#ifndef TRAITS_HPP +#define TRAITS_HPP + +#include "context.hpp" + +#include +#include + +namespace cmcpp +{ + + using float32_t = float; + using float64_t = double; + + enum class ValType : uint8_t + { + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + F32, + F64, + Char, + String, + List, + Field, + Record, + Tuple, + Case, + Variant, + Enum, + Option, + Result, + Flags, + Own, + Borrow + }; + + template + struct ValTrait + { + static ValType type() + { + throw std::runtime_error(typeid(T).name()); + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::Bool; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::S8; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::U8; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::S16; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::U16; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::S32; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::U32; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::S64; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::U64; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::F32; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::F64; + } + }; + + template <> + struct ValTrait + { + static ValType type() + { + return ValType::Char; + } + }; + + class string_t; + using string_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::String; } + }; + + class list_t; + using list_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::List; } + }; + + class field_t; + using field_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Field; } + }; + + class record_t; + using record_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Record; } + }; + + class tuple_t; + using tuple_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Tuple; } + }; + + class case_t; + using case_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Case; } + }; + + class variant_t; + using variant_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Variant; } + }; + + class enum_t; + using enum_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Enum; } + }; + + class option_t; + using option_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Option; } + }; + + class result_t; + using result_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Result; } + }; + + class flags_t; + using flags_ptr = std::shared_ptr; + template <> + struct ValTrait + { + static ValType type() { return ValType::Flags; } + }; + + // -------------------------------------------------------------------- + template + ValType type(const T &v) + { + return ValTrait::type(); + } + + // -------------------------------------------------------------------- + + template + struct WasmValTrait + { + static const char *type() + { + throw std::runtime_error(typeid(T).name()); + } + }; + +} + +#endif diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..b9140ba --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,505 @@ +#include "util.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace cmcpp +{ + + ValType despecialize(const ValType t) + { + switch (t) + { + case ValType::Tuple: + return ValType::Record; + case ValType::Enum: + return ValType::Variant; + case ValType::Option: + return ValType::Variant; + case ValType::Result: + return ValType::Variant; + } + return t; + } + + Val despecialize(const Val &v) + { + switch (valType(v)) + { + case ValType::Tuple: + { + auto r = std::make_shared(); + auto vars = std::get(v)->vs; + for (size_t i = 0; i < vars.size(); ++i) + { + auto var = vars[i]; + auto f = std::make_shared(fmt::format("{}", i), despecialize(var)); + r->fields.push_back(f); + } + return r; + } + case ValType::Enum: + { + std::vector cases; + for (auto label : std::get(v)->labels) + { + auto c = std::make_shared(label); + cases.push_back(c); + } + return std::make_shared(cases); + } + case ValType::Option: + return std::make_shared(std::vector({std::make_shared("None"), std::make_shared("Some", std::get(v)->v)})); + case ValType::Result: + return std::make_shared(std::vector({std::make_shared("Ok", std::get(v)->ok), std::make_shared("Error", std::get(v)->error)})); + default: + return v; + } + } + + bool convert_int_to_bool(int32_t i) + { + return i != 0; + } + + wchar_t convert_i32_to_char(const CallContext &cx, int32_t i) + { + // assert(i >= 0); + // assert(i < 0x110000); + // assert(!(0xD800 <= i && i <= 0xDFFF)); + return static_cast(i); + } + + ValType discriminant_type(const std::vector &cases) + { + size_t n = cases.size(); + + assert(0 < n && n < std::numeric_limits::max()); + int match = std::ceil(std::log2(n) / 8); + switch (match) + { + case 0: + return ValType::U8; + case 1: + return ValType::U8; + case 2: + return ValType::U16; + case 3: + return ValType::U32; + default: + throw std::runtime_error("Invalid match value"); + } + } + + uint32_t align_to(uint32_t ptr, uint32_t alignment) + { + return (ptr + alignment - 1) & ~(alignment - 1); + } + + bool isAligned(uint32_t ptr, uint32_t alignment) { return (ptr & (alignment - 1)) == 0; } + + bool convert_int_to_bool(uint8_t i) + { + return i > 0; + } + + int alignment_record(const std::vector &fields) + { + int a = 1; + for (auto f : fields) + { + a = std::max(a, alignment(f->v)); + } + return a; + } + + int max_case_alignment(const std::vector &cases) + { + int a = 1; + for (auto c : cases) + { + if (c->v.has_value()) + { + a = std::max(a, alignment(c->v.value())); + } + } + return a; + } + + int alignment_variant(const std::vector &cases) + { + return std::max(alignment(discriminant_type(cases)), max_case_alignment(cases)); + } + + int alignment_flags(const std::vector &labels) + { + int n = labels.size(); + if (n <= 8) + return 1; + if (n <= 16) + return 2; + return 4; + } + + int alignment(ValType t) + { + switch (t) + { + case ValType::Bool: + case ValType::S8: + case ValType::U8: + return 1; + case ValType::S16: + case ValType::U16: + return 2; + case ValType::S32: + case ValType::U32: + case ValType::F32: + case ValType::Char: + return 4; + case ValType::S64: + case ValType::U64: + case ValType::F64: + return 8; + case ValType::String: + case ValType::List: + return 4; + // case ValType::Own: + // case ValType::Borrow: + // return 4; + default: + throw std::runtime_error("Invalid type"); + } + } + + int alignment(const Val &_t) + { + auto t = despecialize(_t); + switch (valType(t)) + { + case ValType::Record: + return alignment_record(std::get(t)->fields); + case ValType::Variant: + return alignment_variant(std::get(t)->cases); + case ValType::Flags: + return alignment_flags(std::get(t)->labels); + default: + return alignment(valType(t)); + } + } + + int elem_size(ValType t) + { + switch (despecialize(t)) + { + case ValType::Bool: + case ValType::S8: + case ValType::U8: + return 1; + case ValType::S16: + case ValType::U16: + return 2; + case ValType::S32: + case ValType::U32: + case ValType::F32: + case ValType::Char: + return 4; + case ValType::S64: + case ValType::U64: + case ValType::F64: + return 8; + case ValType::String: + case ValType::List: + return 8; + default: + throw std::runtime_error("Invalid type"); + } + } + + int elem_size_record(const std::vector &fields) + { + int s = 0; + for (auto f : fields) + { + s = align_to(s, alignment(f->v)); + s += elem_size(f->v); + } + assert(s > 0); + return align_to(s, alignment_record(fields)); + } + + int elem_size_variant(const std::vector &cases) + { + int s = elem_size(discriminant_type(cases)); + s = align_to(s, max_case_alignment(cases)); + int cs = 0; + for (auto c : cases) + { + if (c->v.has_value()) + { + cs = std::max(cs, elem_size(c->v.value())); + } + } + s += cs; + return align_to(s, alignment_variant(cases)); + } + + int num_i32_flags(const std::vector &labels) + { + return std::ceil(static_cast(labels.size()) / 32); + } + + int elem_size_flags(const std::vector &labels) + { + int n = labels.size(); + assert(n > 0); + if (n <= 8) + return 1; + if (n <= 16) + return 2; + return 4 * num_i32_flags(labels); + } + + int elem_size(const Val &_t) + { + auto t = despecialize(_t); + ValType kind = valType(t); + switch (kind) + { + case ValType::Record: + return elem_size_record(std::get(t)->fields); + case ValType::Variant: + return elem_size_variant(std::get(t)->cases); + case ValType::Flags: + return elem_size_flags(std::get(t)->labels); + default: + return elem_size(kind); + } + throw std::runtime_error("Invalid type"); + } + + float32_t canonicalize_nan32(float32_t f) + { + if (std::isnan(f)) + { + f = CANONICAL_FLOAT32_NAN; + assert(std::isnan(f)); + } + return f; + } + + float64_t canonicalize_nan64(float64_t f) + { + if (std::isnan(f)) + { + f = CANONICAL_FLOAT64_NAN; + assert(std::isnan(f)); + } + return f; + } + + float32_t core_f32_reinterpret_i32(int32_t i); + float32_t decode_i32_as_float(int32_t i) + { + return canonicalize_nan32(core_f32_reinterpret_i32(i)); + } + + float64_t core_f64_reinterpret_i64(int64_t i); + float64_t decode_i64_as_float(int64_t i) + { + return canonicalize_nan64(core_f64_reinterpret_i64(i)); + } + + float32_t core_f32_reinterpret_i32(int32_t i) + { + float f; + std::memcpy(&f, &i, sizeof f); + return f; + } + + float64_t core_f64_reinterpret_i64(int64_t i) + { + double d; + std::memcpy(&d, &i, sizeof d); + return d; + } + + int find_case(const std::string &label, const std::vector &cases) + { + auto it = std::find_if(cases.begin(), cases.end(), + [&label](auto c) + { return c->label == label; }); + + if (it != cases.end()) + { + return std::distance(cases.begin(), it); + } + + return -1; + } + + int32_t char_to_i32(wchar_t c) { return static_cast(c); } + + template + T random_nan_bits(int bits, int quiet_bits) + { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1 << quiet_bits, (1 << bits) - 1); + return distrib(gen); + } + + float32_t maybe_scramble_nan32(float32_t f) + { + if (std::isnan(f)) + { + if (DETERMINISTIC_PROFILE) + { + f = CANONICAL_FLOAT32_NAN; + } + else + { + f = core_f32_reinterpret_i32(random_nan_bits(32, 8)); + } + assert(std::isnan(f)); + } + return f; + } + + float64_t maybe_scramble_nan64(float64_t f) + { + if (std::isnan(f)) + { + if (DETERMINISTIC_PROFILE) + { + f = CANONICAL_FLOAT32_NAN; + } + else + { + f = core_f64_reinterpret_i64(random_nan_bits(64, 11)); + } + assert(std::isnan(f)); + } + return f; + } + + uint32_t encode_float_as_i32(float32_t f) + { + return std::bit_cast(maybe_scramble_nan32(f)); + } + + uint64_t encode_float_as_i64(float64_t f) + { + return std::bit_cast(maybe_scramble_nan64(f)); + } + + std::vector split(const std::string &input, char delimiter) + { + std::vector result; + std::string token; + std::istringstream stream(input); + + while (std::getline(stream, token, delimiter)) + { + result.push_back(token); + } + + return result; + } + + std::pair> match_case(const variant_ptr &v, const std::vector &cases) + { + assert(v->cases.size() == 1); + auto key = v->cases[0]->label; + auto value = v->cases[0]->v; + for (auto label : split(key, '|')) + { + auto case_index = find_case(label, cases); + if (case_index != -1) + { + return {case_index, value}; + } + } + throw std::runtime_error("Case not found"); + } + + std::string join(const std::string &a, const std::string &b) + { + if (a == b) + return a; + if ((a == "i32" && b == "f32") || (a == "f32" && b == "i32")) + return "i32"; + return "i64"; + } + + std::pair decode(void *src, uint32_t byte_len, HostEncoding encoding) + { + switch (encoding) + { + case HostEncoding::Utf8: + case HostEncoding::Latin1: + return {reinterpret_cast(src), byte_len}; + case HostEncoding::Latin1_Utf16: + assert(false); + break; + default: + throw std::runtime_error("Invalid encoding"); + } + } + + size_t encodeTo(void *dest, const char8_t *src, uint32_t byte_len, GuestEncoding encoding) + { + switch (encoding) + { + case GuestEncoding::Utf8: + case GuestEncoding::Latin1: + std::memcpy(dest, src, byte_len); + return byte_len; + case GuestEncoding::Utf16le: + assert(false); + break; + default: + throw std::runtime_error("Invalid encoding"); + } + } + + CoreValueIter::CoreValueIter(const std::vector &values) : values(values), i(_i) + { + } + + CoreValueIter::CoreValueIter(const std::vector &values, std::size_t &i) : values(values), i(i) + { + } + + int32_t CoreValueIter::next(int32_t _) const + { + return std::get(values[i++]); + } + + int64_t CoreValueIter::next(int64_t _) const + { + return std::get(values[i++]); + } + + float32_t CoreValueIter::next(float32_t _) const + { + return std::get(values[i++]); + } + + float64_t CoreValueIter::next(float64_t _) const + { + return std::get(values[i++]); + } + + void CoreValueIter::skip() const + { + ++i; + } +} diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..c761cda --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,76 @@ +#ifndef UTIL_HPP +#define UTIL_HPP + +#include "context.hpp" +#include "val.hpp" + +#include + +namespace cmcpp +{ + const uint32_t UTF16_TAG = 1U << 31; + const bool DETERMINISTIC_PROFILE = false; + const float32_t CANONICAL_FLOAT32_NAN = 0x7fc00000; + const float64_t CANONICAL_FLOAT64_NAN = 0x7ff8000000000000; + + uint32_t align_to(uint32_t ptr, uint32_t alignment); + int alignment(const Val &v); + int alignment(ValType t); + + float32_t canonicalize_nan32(float32_t f); + float64_t canonicalize_nan64(float64_t f); + + int32_t char_to_i32(wchar_t c); + wchar_t convert_i32_to_char(const CallContext &cx, int32_t i); + bool convert_int_to_bool(int32_t i); + + std::pair decode(void *src, uint32_t byte_len, HostEncoding encoding); + float32_t decode_i32_as_float(int32_t i); + float64_t decode_i64_as_float(int64_t i); + + ValType despecialize(const ValType t); + Val despecialize(const Val &v); + ValType discriminant_type(const std::vector &cases); + + size_t encodeTo(void *, const char8_t *src, uint32_t byte_len, GuestEncoding encoding); + uint32_t encode_float_as_i32(float32_t f); + uint64_t encode_float_as_i64(float64_t f); + + bool isAligned(uint32_t ptr, uint32_t alignment); + int find_case(const std::string &label, const std::vector &cases); + std::pair> match_case(const variant_ptr &v, const std::vector &cases); + float32_t maybe_scramble_nan32(float32_t f); + float64_t maybe_scramble_nan64(float64_t f); + int max_case_alignment(const std::vector &cases); + + int elem_size(ValType t); + int elem_size(const Val &v); + + int elem_size_flags(const std::vector &labels); + int num_i32_flags(const std::vector &labels); + + std::string join(const std::string &a, const std::string &b); + + class CoreValueIter + { + + private: + std::size_t _i = 0; + + public: + const std::vector values; + size_t &i; + + CoreValueIter(const std::vector &values); + CoreValueIter(const std::vector &values, std::size_t &i); + + virtual int32_t next(int32_t _) const; + virtual int64_t next(int64_t _) const; + virtual float32_t next(float32_t _) const; + virtual float64_t next(float64_t _) const; + + void skip() const; + }; + +} +#endif diff --git a/src/val.cpp b/src/val.cpp new file mode 100644 index 0000000..bd0bd28 --- /dev/null +++ b/src/val.cpp @@ -0,0 +1,313 @@ +#include "val.hpp" + +#include + +const char *ValTypeNames[] = { + "bool", + "int8_t", + "uint8_t", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "float32_t", + "float64_t", + "wchar_t", + "string_ptr", + "list_ptr", + "field_ptr", + "record_ptr", + "tuple_ptr", + "case_ptr", + "variant_ptr", + "enum_ptr", + "option_ptr", + "result_ptr", + "flags_ptr", + "unknown1", "unknown2", "unknown3", "unknown4", "unknown5", "unknown6"}; + +namespace cmcpp +{ + // Vals ---------------------------------------------------------------- + + ValType valType(const Val &v) + { + return std::visit([](auto &&arg) -> ValType + { + using T = std::decay_t; + return ValTrait::type(); }, + v); + } + + const char *valTypeName(ValType type) + { + return ValTypeNames[static_cast(type)]; + } + + bool operator==(const Val &lhs, const Val &rhs) + { + return lhs.index() == rhs.index() && std::visit([rhs](auto &&arg1, auto &&arg2) -> bool + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + return arg1 == arg2; + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else if constexpr (std::is_same_v) + { + return *arg1 ==(*std::get(rhs)); + } + else + { + return arg1 == arg2; + } }, lhs, rhs); + } + + // ---------------------------------------------------------------------- + + string_t::string_t() {} + string_t::string_t(const char8_t *ptr, size_t len) : ptr(ptr), len(len) {} + string_t::string_t(const std::string &_str) + { + str = _str; + ptr = (const char8_t *)str.c_str(); + len = str.size(); + } + std::string string_t::to_string() const + { + return std::string((const char *)ptr, len); + } + + bool string_t::operator==(const string_t &rhs) const + { + if (this == nullptr) + { + return true; + } + return std::string_view((const char *)ptr, len).compare(std::string_view((const char *)rhs.ptr, rhs.len)) == 0; + } + + list_t::list_t() {} + list_t::list_t(const Val <) : lt(lt) {} + list_t::list_t(const Val <, const std::vector &vs) : lt(lt), vs(vs) {} + bool list_t::operator==(const list_t &rhs) const + { + if (lt != rhs.lt) + return false; + + if (vs.size() != rhs.vs.size()) + return false; + + for (size_t i = 0; i < vs.size(); i++) + { + if (vs[i] != rhs.vs[i]) + return false; + } + + return true; + } + + field_t::field_t() {} + field_t::field_t(const std::string &label, const Val &v) : label(label), v(v) {} + bool field_t::operator==(const field_t &rhs) const + { + return label == rhs.label && v == rhs.v; + } + + record_t::record_t() {} + record_t::record_t(const std::vector &fields) : fields(fields) {} + bool record_t::operator==(const record_t &rhs) const + { + if (fields.size() != rhs.fields.size()) + return false; + + for (size_t i = 0; i < fields.size(); i++) + { + if (*fields[i] != *rhs.fields[i]) + return false; + } + + return true; + } + Val record_t::find(const std::string &label) const + { + for (auto f : fields) + { + if (f->label == label) + return f->v; + } + return Val(); + } + + tuple_t::tuple_t() {} + tuple_t::tuple_t(const std::vector &vs) : vs(vs) {} + bool tuple_t::operator==(const tuple_t &rhs) const + { + if (vs.size() != rhs.vs.size()) + return false; + + for (size_t i = 0; i < vs.size(); i++) + { + if (vs[i] != rhs.vs[i]) + return false; + } + + return true; + } + + case_t::case_t() {} + case_t::case_t(const std::string &label, const std::optional &v, const std::optional &refines) : label(label), v(v), refines(refines) {} + bool case_t::operator==(const case_t &rhs) const + { + return label == rhs.label && v == rhs.v; + } + + variant_t::variant_t() {} + variant_t::variant_t(const std::vector &cases) : cases(cases) {} + bool variant_t::operator==(const variant_t &rhs) const + { + for (auto c : cases) + { + for (auto c2 : cases) + { + if (c->label == c2->label) + { + if (!c->v.has_value() && !c2->v.has_value()) + { + return true; + } + else if (c->v.has_value() && c2->v.has_value() && c->v.value() == c2->v.value()) + { + return true; + } + } + } + } + + return false; + } + + enum_t::enum_t() {} + enum_t::enum_t(const std::vector &labels) : labels(labels) {} + bool enum_t::operator==(const enum_t &rhs) const + { + if (labels.size() != rhs.labels.size()) + return false; + + for (size_t i = 0; i < labels.size(); i++) + { + if (labels[i] != rhs.labels[i]) + return false; + } + + return true; + } + + option_t::option_t() {} + option_t::option_t(const Val &v) : v(v) {} + bool option_t::operator==(const option_t &rhs) const + { + return v == rhs.v; + } + + result_t::result_t() {} + result_t::result_t(const Val &ok, const Val &error) : ok(ok), error(error) {} + bool result_t::operator==(const result_t &rhs) const + { + return ok == rhs.ok && error == rhs.error; + } + + flags_t::flags_t() {} + flags_t::flags_t(const std::vector &labels) : labels(labels) {} + bool flags_t::operator==(const flags_t &rhs) const + { + if (labels.size() != rhs.labels.size()) + return false; + + for (size_t i = 0; i < labels.size(); i++) + { + if (labels[i] != rhs.labels[i]) + return false; + } + + return true; + } + + // ---------------------------------------------------------------- + + ValType wasmValType(const WasmVal &v) + { + return std::visit([](auto &&arg) -> ValType + { + using T = std::decay_t; + return ValTrait::type(); }, + v); + } + + const char *ValTypeNames[] = { + "i32", + "i64", + "f32", + "f64", + "unknown1", "unknown2", "unknown3", "unknown4", "unknown5", "unknown6"}; + + const char *wasmValTypeName(ValType type) + { + switch (type) + { + case ValType::S32: + return ValTypeNames[0]; + case ValType::S64: + return ValTypeNames[1]; + case ValType::F32: + return ValTypeNames[2]; + case ValType::F64: + return ValTypeNames[3]; + default: + assert(false); + return ValTypeNames[4]; + } + } +} \ No newline at end of file diff --git a/src/val.hpp b/src/val.hpp new file mode 100644 index 0000000..02bcdd0 --- /dev/null +++ b/src/val.hpp @@ -0,0 +1,163 @@ +#ifndef VAL_HPP +#define VAL_HPP + +#include "traits.hpp" + +namespace cmcpp +{ + + // Vals ---------------------------------------------------------------- + + using Val = std::variant; + + ValType valType(const Val &v); + const char *valTypeName(ValType type); + bool operator==(const Val &lhs, const Val &rhs); + + // ---------------------------------------------------------------------- + + class string_t + { + private: + std::string str; + + public: + const char8_t *ptr = nullptr; + size_t len = 0; + + string_t(); + string_t(const char8_t *ptr, size_t len); + string_t(const std::string &_str); + ~string_t() = default; + bool operator==(const string_t &rhs) const; + + std::string to_string() const; + }; + + class list_t + { + public: + Val lt; + std::vector vs; + + list_t(); + list_t(const Val <); + list_t(const Val <, const std::vector &vs); + ~list_t() = default; + bool operator==(const list_t &rhs) const; + }; + + class field_t + { + public: + std::string label; + Val v; + + field_t(); + field_t(const std::string &label, const Val &v); + ~field_t() = default; + bool operator==(const field_t &rhs) const; + }; + + class record_t + { + public: + std::vector fields; + + record_t(); + record_t(const std::vector &fields); + ~record_t() = default; + bool operator==(const record_t &rhs) const; + + Val find(const std::string &label) const; + }; + + class tuple_t + { + public: + std::vector vs; + + tuple_t(); + tuple_t(const std::vector &vs); + ~tuple_t() = default; + bool operator==(const tuple_t &rhs) const; + }; + + class case_t + { + public: + std::string label; + std::optional v; + std::optional refines = std::nullopt; + + case_t(); + case_t(const std::string &label, const std::optional &v = std::nullopt, const std::optional &refines = std::nullopt); + ~case_t() = default; + bool operator==(const case_t &rhs) const; + }; + + class variant_t + { + public: + std::vector cases; + + variant_t(); + variant_t(const std::vector &cases); + ~variant_t() = default; + bool operator==(const variant_t &rhs) const; + }; + + class enum_t + { + public: + std::vector labels; + + enum_t(); + enum_t(const std::vector &labels); + ~enum_t() = default; + bool operator==(const enum_t &rhs) const; + }; + + class option_t + { + public: + Val v; + + option_t(); + option_t(const Val &v); + ~option_t() = default; + bool operator==(const option_t &rhs) const; + }; + + class result_t + { + public: + Val ok; + Val error; + + result_t(); + result_t(const Val &ok, const Val &error); + ~result_t() = default; + bool operator==(const result_t &rhs) const; + }; + + class flags_t + { + public: + std::vector labels; + + flags_t(); + flags_t(const std::vector &labels); + ~flags_t() = default; + bool operator==(const flags_t &rhs) const; + }; + + // WasmVals ------------------------------------------------------------ + + using WasmVal = std::variant; + ValType wasmValType(const WasmVal &v); + + const char *wasmValTypeName(ValType type); +} + +#endif diff --git a/standalone/CMakeLists.txt b/standalone/CMakeLists.txt deleted file mode 100644 index a932149..0000000 --- a/standalone/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -cmake_minimum_required(VERSION 3.14...3.22) - -project(GreeterStandalone LANGUAGES CXX) - -# --- Import tools ---- - -include(../cmake/tools.cmake) - -# ---- Dependencies ---- - -include(../cmake/CPM.cmake) - -CPMAddPackage( - GITHUB_REPOSITORY jarro2783/cxxopts - VERSION 3.0.0 - OPTIONS "CXXOPTS_BUILD_EXAMPLES NO" "CXXOPTS_BUILD_TESTS NO" "CXXOPTS_ENABLE_INSTALL YES" -) - -CPMAddPackage(NAME Greeter SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) - -# ---- Create standalone executable ---- - -file(GLOB sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) - -add_executable(${PROJECT_NAME} ${sources}) - -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17 OUTPUT_NAME "Greeter") - -target_link_libraries(${PROJECT_NAME} Greeter::Greeter cxxopts) diff --git a/standalone/source/main.cpp b/standalone/source/main.cpp deleted file mode 100644 index 938a386..0000000 --- a/standalone/source/main.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -auto main(int argc, char** argv) -> int { - const std::unordered_map languages{ - {"en", greeter::LanguageCode::EN}, - {"de", greeter::LanguageCode::DE}, - {"es", greeter::LanguageCode::ES}, - {"fr", greeter::LanguageCode::FR}, - }; - - cxxopts::Options options(*argv, "A program to welcome the world!"); - - std::string language; - std::string name; - - // clang-format off - options.add_options() - ("h,help", "Show help") - ("v,version", "Print the current version number") - ("n,name", "Name to greet", cxxopts::value(name)->default_value("World")) - ("l,lang", "Language code to use", cxxopts::value(language)->default_value("en")) - ; - // clang-format on - - auto result = options.parse(argc, argv); - - if (result["help"].as()) { - std::cout << options.help() << std::endl; - return 0; - } - - if (result["version"].as()) { - std::cout << "Greeter, version " << GREETER_VERSION << std::endl; - return 0; - } - - auto langIt = languages.find(language); - if (langIt == languages.end()) { - std::cerr << "unknown language code: " << language << std::endl; - return 1; - } - - greeter::Greeter greeter(name); - std::cout << greeter.greet(langIt->second) << std::endl; - - return 0; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 74cd08d..8d4df10 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,60 +1,64 @@ -cmake_minimum_required(VERSION 3.14...3.22) - -project(GreeterTests LANGUAGES CXX) - -# ---- Options ---- +project(component-model-cpp-tests) option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) option(TEST_INSTALLED_VERSION "Test the version found by find_package" OFF) -# --- Import tools ---- - -include(../cmake/tools.cmake) - -# ---- Dependencies ---- - -include(../cmake/CPM.cmake) - -CPMAddPackage("gh:doctest/doctest@2.4.9") -CPMAddPackage("gh:TheLartians/Format.cmake@1.7.3") - -if(TEST_INSTALLED_VERSION) - find_package(Greeter REQUIRED) +find_package(doctest CONFIG REQUIRED) +find_path(WASMTIME_CPP_API_INCLUDE_DIRS "wasmtime-cpp-api/wasmtime.hh" + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} +) +if (WIN32) + find_library(WASMTIME_LIB NAMES wasmtime.dll + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} + ) else() - CPMAddPackage(NAME Greeter SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) + find_library(WASMTIME_LIB NAMES wasmtime + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} + ) endif() +find_package(fmt CONFIG REQUIRED) + +include_directories( + ../src + ${WASMTIME_CPP_API_INCLUDE_DIRS}/wasmtime-c-api + ${WASMTIME_CPP_API_INCLUDE_DIRS}/wasmtime-cpp-api +) + +add_executable(${PROJECT_NAME} + main.cpp + val.cpp + # wasmtime.cpp + # val3.cpp +) + +target_link_libraries(${PROJECT_NAME} + ${WASMTIME_LIB} + doctest::doctest + component-model-cpp + fmt::fmt +) +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) +add_test( + NAME ${PROJECT_NAME} + COMMAND ${PROJECT_NAME} + WORKING_DIRECTORY ${ROOT_DIR} +) -# ---- Create binary ---- - -file(GLOB sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) -add_executable(${PROJECT_NAME} ${sources}) -target_link_libraries(${PROJECT_NAME} doctest::doctest Greeter::Greeter) -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) - -# enable compiler warnings if(NOT TEST_INSTALLED_VERSION) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(Greeter PUBLIC -Wall -Wpedantic -Wextra -Werror) + # target_compile_options(component-model-cpp PUBLIC -Wall -Wpedantic -Wextra -Werror -Wno-error=unused-parameter -Wno-unused-parameter) elseif(MSVC) - target_compile_options(Greeter PUBLIC /W4 /WX) + target_compile_options(component-model-cpp PUBLIC /W4 /WX) target_compile_definitions(${PROJECT_NAME} PUBLIC DOCTEST_CONFIG_USE_STD_HEADERS) endif() endif() -# ---- Add GreeterTests ---- - enable_testing() -# Note: doctest and similar testing frameworks can automatically configure CMake tests. For other -# testing frameworks add the tests target instead: add_test(NAME ${PROJECT_NAME} COMMAND -# ${PROJECT_NAME}) - -include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) +include(${doctest_DIR}/doctest.cmake) doctest_discover_tests(${PROJECT_NAME}) -# ---- code coverage ---- - if(ENABLE_TEST_COVERAGE) - target_compile_options(Greeter PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) - target_link_options(Greeter PUBLIC -fprofile-arcs -ftest-coverage) + target_compile_options(component-model-cpp PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) + target_link_options(component-model-cpp PUBLIC -fprofile-arcs -ftest-coverage) endif() diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..8caaabe --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,324 @@ +#include "traits.hpp" +#include "lift.hpp" +#include "lower.hpp" +#include "util.hpp" + +using namespace cmcpp; + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include + +#include +#include +#include +#include + +using namespace cmcpp; + +class Heap +{ +private: + mutable std::size_t last_alloc; + +public: + std::vector memory; + + Heap(size_t arg) : memory(arg), last_alloc(0) + { + CHECK(true); + } + + void *realloc(void *original_ptr, size_t original_size, size_t alignment, size_t new_size) const + { + if (original_ptr != nullptr && new_size < original_size) + { + return align_to(original_ptr, alignment); + } + void *ret = align_to(reinterpret_cast(last_alloc), alignment); + last_alloc = reinterpret_cast(ret) + new_size; + if (last_alloc > memory.size()) + { + std::cout << "oom: have " << memory.size() << " need " << last_alloc << std::endl; + CHECK(false); + // trap(); // You need to implement this function + } + std::copy_n(static_cast(original_ptr), original_size, static_cast(ret)); + return ret; + } + +private: + void *align_to(void *ptr, size_t alignment) const + { + return reinterpret_cast((reinterpret_cast(ptr) + alignment - 1) & ~(alignment - 1)); + } +}; + +static std::vector g_memory; +std::vector &globalMemory() +{ + g_memory.resize(1024 * 1024); + return g_memory; +} +CallContextPtr mk_cx(std::vector &memory = globalMemory(), + HostEncoding encoding = HostEncoding::Utf8, + const GuestRealloc &realloc = nullptr, + const GuestPostReturn &post_return = nullptr) +{ + return createCallContext(memory, realloc, encodeTo, encoding, post_return); +} +void test(const Val &t, std::vector vals_to_lift, std::optional v = std::nullopt, std::optional lower_t = std::nullopt, std::optional lower_v = std::nullopt, const CallContextPtr &cx = mk_cx()) +{ + auto vi = CoreValueIter(vals_to_lift); + if (!v.has_value()) + { + CHECK_THROWS(lift_flat(*cx, vi, t)); + } + + Val got = lift_flat(*cx, vi, t); + const char *got_type = valTypeName(valType(got)); + const char *expected_type = valTypeName(valType(v.value())); + + CHECK(vi.i == vi.values.size()); + CHECK(valType(got) == valType(v.value())); + + // fmt.print("got: {}, v: {}", valTypeName(valType(got)), valTypeName(valType(v.value()))); + auto got32 = valType(got) == ValType::U32 ? std::get(got) : 0; + auto expected32 = valType(got) == ValType::U32 ? std::get(v.value()) : 0; + auto gotChar = valType(got) == ValType::Char ? std::get(got) : 0; + auto expectedChar = valType(got) == ValType::Char ? std::get(v.value()) : 0; + auto gotString = valType(got) == ValType::String ? std::get(got)->to_string() : ""; + auto expectedString = valType(got) == ValType::String ? std::get(v.value())->to_string() : ""; + + CHECK(got == v.value()); + + if (!lower_t.has_value()) + { + lower_t = t; + CHECK(lower_t.value() == t); + } + if (!lower_v.has_value()) + { + lower_v = v; + CHECK(lower_v.value() == v.value()); + } + Heap heap(5 * cx->opts->memory.size()); + std::function realloc = [heap](int original_ptr, int original_size, int alignment, int new_size) -> int + { + void *retVal = heap.realloc(reinterpret_cast(original_ptr), original_size, alignment, new_size); + return reinterpret_cast(retVal); + }; + CallContextPtr cx2 = createCallContext(heap.memory, realloc, encodeTo, cx->opts->string_encoding); + auto lowered_vals = lower_flat(*cx2, v.value(), t); + auto vi2 = CoreValueIter(lowered_vals); + got = lift_flat(*cx2, vi2, lower_t.value()); + CHECK(valType(got) == valType(lower_v.value())); + CHECK(got == lower_v.value()); +} + +TEST_CASE("Record") +{ + auto rt = std::make_shared(std::vector{std::make_shared("x", uint8_t()), std::make_shared("y", uint16_t()), std::make_shared("z", uint32_t())}); + auto r = std::make_shared(std::vector{std::make_shared("x", (uint8_t)1), std::make_shared("y", (uint16_t)2), std::make_shared("z", (uint32_t)3)}); + test(rt, {1, 2, 3}, r); +} + +TEST_CASE("Tuple") +{ + auto tt = std::make_shared(std::vector{std::make_shared(std::vector{uint8_t(), uint8_t()}), + uint8_t()}); + + auto r = std::make_shared(std::vector{std::make_shared("0", std::make_shared(std::vector{ + std::make_shared("0", (uint8_t)1), + std::make_shared("1", (uint8_t)2)})), + std::make_shared("1", (uint8_t)3)}); + test(tt, {1, 2, 3}, r); +} + +TEST_CASE("Flags") +{ + auto f = std::make_shared(std::vector{"a", "b"}); + test(f, {0}, std::make_shared(std::vector{})); + test(f, {1}, std::make_shared(std::vector{"a"})); + test(f, {2}, std::make_shared(std::vector{"b"})); + test(f, {3}, std::make_shared(std::vector{"a", "b"})); + test(f, {4}, std::make_shared(std::vector{})); + + std::vector strVec(33); + std::vector strVec2(33); + for (int i = 0; i < 33; i++) + { + strVec[i] = std::to_string(i); + strVec2[i] = std::to_string(i); + } + test(std::make_shared(strVec), {(int32_t)0xffffffff, (int32_t)0x1}, std::make_shared(strVec2)); +} + +TEST_CASE("Variant") +{ + auto t = std::make_shared(std::vector{std::make_shared("x", uint8_t()), std::make_shared("y", float64_t()), std::make_shared("z", bool())}); + std::vector vals_to_lift = {0, 42}; + auto zero = vals_to_lift[0].index(); + auto one = vals_to_lift[1].index(); + test(t, {0, 42}, std::make_shared(std::vector{std::make_shared("x", (uint8_t)42)})); + test(t, {0, 256}, std::make_shared(std::vector{std::make_shared("x", (uint8_t)0)})); + test(t, {1, (int64_t)0x4048f5c3}, std::make_shared(std::vector{std::make_shared("y", (float64_t)3.140000104904175)})); + test(t, {2, false}, std::make_shared(std::vector{std::make_shared("z", false)})); + + t = std::make_shared(std::vector{std::make_shared("w", uint8_t()), std::make_shared("x", uint8_t(), "w"), std::make_shared("y", uint8_t()), std::make_shared("z", uint8_t(), "x")}); + test(t, {0, 42}, std::make_shared(std::vector{std::make_shared("w", (uint8_t)42)})); + test(t, {1, 42}, std::make_shared(std::vector{std::make_shared("x|w", (uint8_t)42)})); + test(t, {2, 42}, std::make_shared(std::vector{std::make_shared("y", (uint8_t)42)})); + test(t, {3, 42}, std::make_shared(std::vector{std::make_shared("z|x|w", (uint8_t)42)})); + + /* +t2 = Variant([Case('w',U8())]) +test(t, [0, 42], {'w':42}, lower_t=t2, lower_v={'w':42}) +test(t, [1, 42], {'x|w':42}, lower_t=t2, lower_v={'w':42}) +test(t, [3, 42], {'z|x|w':42}, lower_t=t2, lower_v={'w':42}) + */ + + auto t2 = std::make_shared(std::vector{std::make_shared("w", uint8_t())}); + test(t, {0, 42}, std::make_shared(std::vector{std::make_shared("w", (uint8_t)42)}), t2, std::make_shared(std::vector{std::make_shared("w", (uint8_t)42)})); + // test(t, {1, 42}, std::make_shared(std::vector{std::make_shared("x|w", (uint8_t)42)}), t2, std::make_shared(std::vector{std::make_shared("w", (uint8_t)42)})); + // test(t, {3, 42}, std::make_shared(std::vector{std::make_shared("z|x|w", (uint8_t)42)}), t2, std::make_shared(std::vector{std::make_shared("w", (uint8_t)42)})); +} + +TEST_CASE("Option") +{ + auto t = std::make_shared(float32_t()); + test(t, {0, (float32_t)3.14}, std::make_shared(std::vector{std::make_shared("None")})); + test(t, {1, (float32_t)3.14}, std::make_shared(std::vector{std::make_shared("Some", (float32_t)3.14)})); +} + +TEST_CASE("Result") +{ + auto t = std::make_shared(uint8_t(), uint32_t()); + test(t, {0, 42}, std::make_shared(std::vector{std::make_shared("Ok", (uint8_t)42)})); + test(t, {1, 1000}, std::make_shared(std::vector{std::make_shared("Error", (uint32_t)1000)})); +} + +using WasmValValPair = std::pair>; +void test_pairs(Val t, std::vector pairs) +{ + for (auto pair : pairs) + { + const char *wasm_type = valTypeName(wasmValType(pair.first)); + const char *expected_type = pair.second.has_value() ? valTypeName(valType(pair.second.value())) : ""; + uint32_t wasm32 = wasmValType(pair.first) == ValType::S32 ? std::get(pair.first) : 0; + uint32_t val32 = pair.second.has_value() ? valType(pair.second.value()) == ValType::U32 ? std::get(pair.second.value()) : 0 : 0; + uint64_t wasm64 = wasmValType(pair.first) == ValType::S64 ? std::get(pair.first) : 0; + uint64_t val64 = pair.second.has_value() ? valType(pair.second.value()) == ValType::U64 ? std::get(pair.second.value()) : 0 : 0; + int64_t valS64 = pair.second.has_value() ? valType(pair.second.value()) == ValType::S64 ? std::get(pair.second.value()) : 0 : 0; + test(t, {pair.first}, pair.second); + } +} + +template +void testValPair(const std::pair &pair) +{ + CHECK(std::get(pair.first) == pair.second); +} + +template +void testValVector(const std::vector> &pairs) +{ + for (auto pair : pairs) + { + testValPair(pair); + } +} + +void testWasmValVal(const std::vector &pairs) +{ + for (const WasmValValPair &pair : pairs) + { + const char *wasm_type = valTypeName(wasmValType(pair.first)); + const char *expected_type = valTypeName(valType(pair.second.value())); + CHECK(true); + } +} + +template +void testVal(const Val &v, T expected) +{ + CHECK(std::get(v) == expected); + testValPair({v, expected}); + testValVector({{v, expected}}); + testWasmValVal({{expected, v}}); +} + +TEST_CASE("pairs") +{ + test_pairs(bool(), {{0, false}, {1, true}, {2, true}, {(int32_t)4294967295, true}}); + test_pairs(uint8_t(), {{127, (uint8_t)127}, {128, (uint8_t)128}, {255, (uint8_t)255}, {256, (uint8_t)0}, {(int32_t)4294967295, (uint8_t)255}, {(int32_t)4294967168, (uint8_t)128}, {(int32_t)4294967167, (uint8_t)127}}); + test_pairs(int8_t(), {{127, (int8_t)127}, {128, (int8_t)-128}, {255, (int8_t)-1}, {256, (int8_t)0}, {(int32_t)4294967295, (int8_t)-1}, {(int32_t)4294967168, (int8_t)-128}, {(int32_t)4294967167, (int8_t)127}}); + test_pairs(uint16_t(), {{32767, (uint16_t)32767}, {32768, (uint16_t)32768}, {65535, (uint16_t)65535}, {65536, (uint16_t)0}, {(1 << 32) - 1, (uint16_t)65535}, {(1 << 32) - 32768, (uint16_t)32768}, {(1 << 32) - 32769, (uint16_t)32767}}); + test_pairs(int16_t(), {{32767, (int16_t)32767}, {32768, (int16_t)-32768}, {65535, (int16_t)-1}, {65536, (int16_t)0}, {(1 << 32) - 1, (int16_t)-1}, {(1 << 32) - 32768, (int16_t)-32768}, {(1 << 32) - 32769, (int16_t)32767}}); + test_pairs(uint32_t(), {{(1 << 31) - 1, (uint32_t)((1 << 31) - 1)}, {1 << 31, (uint32_t)(1 << 31)}, {((1 << 32) - 1), (uint32_t)((1 << 32) - 1)}}); + test_pairs(int32_t(), {{(1 << 31) - 1, (1 << 31) - 1}, {1 << 31, -(1 << 31)}, {(1 << 32) - 1, -1}}); + test_pairs(uint64_t(), {{(int64_t)((1ULL << 63) - 1), (uint64_t)((1ULL << 63) - 1)}, {(int64_t)(1ULL << 63), (uint64_t)(1ULL << 63)}, {(int64_t)((1ULL << 64) - 1), (uint64_t)((1ULL << 64) - 1)}}); + test_pairs(int64_t(), {{(int64_t)((1 << 63) - 1), (int64_t)((1 << 63) - 1)}, {(int64_t)(1 << 63), (int64_t) - (1 << 63)}, {(int64_t)((1 << 64) - 1), (int64_t)-1}}); + test_pairs(float32_t(), {{(float32_t)3.14, (float32_t)3.14}}); + test_pairs(float64_t(), {{3.14, 3.14}}); + test_pairs(wchar_t(), {{0, L'\x00'}, {65, L'A'}, {0xD7FF, L'\uD7FF'}}); + test_pairs(wchar_t(), {{0xE000, L'\uE000'}, {0x10FFFF, L'\U0010FFFF'}}); + + // test_pairs(Enum([ 'a', 'b' ]), {{0, {'a' : None}}, {1, {'b' : None}}, {2, None}}); +} + +void test_string_internal(HostEncoding host_encoding, GuestEncoding guest_encoding, const std::string &guestS, const std::string &expected) +{ + Heap heap(guestS.length() * 2); + memcpy(heap.memory.data(), guestS.data(), guestS.length()); + + auto cx = mk_cx(heap.memory, host_encoding); + + test(string_ptr(), {0, (int32_t)guestS.length()}, std::make_shared(expected), std::nullopt, std::nullopt, cx); +} + +TEST_CASE("String") +{ + std::vector host_encodings = {HostEncoding::Utf8}; //, HostEncoding::Utf16, HostEncoding::Latin1_Utf16}; + std::vector fun_strings = {"", "a", "hi", "\x00", "a\x00b", "\x80", "\x80b", "ab\xefc", "\u01ffy", "xy\u01ff", "a\ud7ffb", "a\u02ff\u03ff\u04ffbc", "\uf123", "\uf123\uf123abc", "abcdef\uf123"}; + for (auto s : fun_strings) + { + for (auto h_enc : host_encodings) + { + test_string_internal(h_enc, GuestEncoding::Utf8, s, s); + } + } +} + +void test_heap(Val t, Val expected, std::vector vals_to_lift, const std::vector byte_array) +{ + Heap heap(byte_array.size()); + memcpy(heap.memory.data(), byte_array.data(), byte_array.size()); + + auto cx = mk_cx(heap.memory); + test(t, vals_to_lift, expected, std::nullopt, std::nullopt, cx); +} + +TEST_CASE("List") +{ + test_heap(std::make_shared(bool()), std::make_shared(bool(), std::vector{true, false, true}), {0, 3}, {1, 0, 1}); + test_heap(std::make_shared(bool()), std::make_shared(bool(), std::vector{true, false, true}), {0, 3}, {1, 0, 2}); + test_heap(std::make_shared(bool()), std::make_shared(bool(), std::vector{true, false, true}), {3, 3}, {0xff, 0xff, 0xff, 1, 0, 1}); + test_heap(std::make_shared(uint8_t()), std::make_shared(uint8_t(), std::vector{(uint8_t)1, (uint8_t)2, (uint8_t)3}), {0, 3}, {1, 2, 3}); + test_heap(std::make_shared(uint16_t()), std::make_shared(uint16_t(), std::vector{(uint16_t)1, (uint16_t)2, (uint16_t)3}), {0, 3}, {1, 0, 2, 0, 3, 0}); + test_heap(std::make_shared(uint32_t()), std::make_shared(uint32_t(), std::vector{(uint32_t)1, (uint32_t)2, (uint32_t)3}), {0, 3}, {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0}); + test_heap(std::make_shared(uint64_t()), std::make_shared(uint64_t(), std::vector{(uint64_t)1, (uint64_t)2}), {0, 2}, {1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0}); + test_heap(std::make_shared(int8_t()), std::make_shared(int8_t(), std::vector{(int8_t)-1, (int8_t)-2, (int8_t)-3}), {0, 3}, {0xff, 0xfe, 0xfd}); + test_heap(std::make_shared(int16_t()), std::make_shared(int16_t(), std::vector{(int16_t)-1, (int16_t)-2, (int16_t)-3}), {0, 3}, {0xff, 0xff, 0xfe, 0xff, 0xfd, 0xff}); + test_heap(std::make_shared(int32_t()), std::make_shared(int32_t(), std::vector{(int32_t)-1, (int32_t)-2, (int32_t)-3}), {0, 3}, {0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff}); + test_heap(std::make_shared(int64_t()), std::make_shared(int64_t(), std::vector{(int64_t)-1, (int64_t)-2}), {0, 2}, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + test_heap(std::make_shared(wchar_t()), std::make_shared(wchar_t(), std::vector{L'A', L'B', L'c'}), {0, 3}, {65, 0, 0, 0, 66, 0, 0, 0, 99, 0, 0, 0}); + test_heap(std::make_shared(string_ptr()), std::make_shared(string_ptr(), std::vector{std::make_shared("hi"), std::make_shared("wat")}), {0, 2}, {16, 0, 0, 0, 2, 0, 0, 0, 21, 0, 0, 0, 3, 0, 0, 0, 'h', 'i', 0xf, 0xf, 0xf, 'w', 'a', 't'}); + // test_heap(List(List(U8())), [[3,4,5],[],[6,7]], [0,3], [24,0,0,0, 3,0,0,0, 0,0,0,0, 0,0,0,0, 27,0,0,0, 2,0,0,0, 3,4,5, 6,7]) + // test_heap(List(List(U16())), [[5,6]], [0,1], [8,0,0,0, 2,0,0,0, 5,0, 6,0]) + // test_heap(List(List(U16())), None, [0,1], [9,0,0,0, 2,0,0,0, 0, 5,0, 6,0]) + // test_heap(List(Tuple([U8(),U8(),U16(),U32()])), [mk_tup(6,7,8,9),mk_tup(4,5,6,7)], [0,2], [6, 7, 8,0, 9,0,0,0, 4, 5, 6,0, 7,0,0,0]) + // test_heap(List(Tuple([U8(),U16(),U8(),U32()])), [mk_tup(6,7,8,9),mk_tup(4,5,6,7)], [0,2], [6,0xff, 7,0, 8,0xff,0xff,0xff, 9,0,0,0, 4,0xff, 5,0, 6,0xff,0xff,0xff, 7,0,0,0]) + // test_heap(List(Tuple([U16(),U8()])), [mk_tup(6,7),mk_tup(8,9)], [0,2], [6,0, 7, 0x0ff, 8,0, 9, 0xff]) + // test_heap(List(Tuple([Tuple([U16(),U8()]),U8()])), [mk_tup([4,5],6),mk_tup([7,8],9)], [0,2], [4,0, 5,0xff, 6,0xff, 7,0, 8,0xff, 9,0xff]) +} diff --git a/test/pytest.cpp b/test/pytest.cpp new file mode 100644 index 0000000..a736935 --- /dev/null +++ b/test/pytest.cpp @@ -0,0 +1,196 @@ +#include "traits.hpp" +// #include "val.hpp" +// #include "context.hpp" +// #include "lower.hpp" +// #include "lift.hpp" +// #include "util.hpp" + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +bool equal_modulo_string_encoding(const auto &s, const auto &t) +{ + if (s == nullptr && t == nullptr) + { + return true; + } + if (std::is_same_v && std::is_same_v || + std::is_same_v && std::is_same_v || + std::is_same_v && std::is_same_v || + std::is_same_v && std::is_same_v) + { + return s == t; + } + if (std::is_same_v> && std::is_same_v>) + { + assert(std::get<0>(s) == std::get<0>(t)); + return std::get<0>(s) == std::get<0>(t); + } + // if (std::is_same_v> && std::is_same_v>) + // { + // for (const auto &[key, value] : s) + // { + // if (!equal_modulo_string_encoding(value, t.at(key))) + // { + // return false; + // } + // } + // return true; + // } + // if (std::is_same_v> && std::is_same_v>) + // { + // for (size_t i = 0; i < s.size(); i++) + // { + // if (!equal_modulo_string_encoding(s[i], t[i])) + // { + // return false; + // } + // } + // return true; + // } + + assert(false); + return false; +} + +class Heap +{ +private: + std::vector memory; + std::size_t last_alloc; + +public: + Heap(size_t arg) : memory(arg), last_alloc(0) {} + + void *realloc(void *original_ptr, size_t original_size, size_t alignment, size_t new_size) + { + if (original_ptr != nullptr && new_size < original_size) + { + return align_to(original_ptr, alignment); + } + void *ret = align_to(reinterpret_cast(last_alloc), alignment); + last_alloc = reinterpret_cast(ret) + new_size; + if (last_alloc > memory.size()) + { + std::cout << "oom: have " << memory.size() << " need " << last_alloc << std::endl; + assert(false); + // trap(); // You need to implement this function + } + std::copy_n(static_cast(original_ptr), original_size, static_cast(ret)); + return ret; + } + +private: + void *align_to(void *ptr, size_t alignment) + { + return reinterpret_cast((reinterpret_cast(ptr) + alignment - 1) & ~(alignment - 1)); + } +}; + +cmcpp::CanonicalOptionsPtr mk_opts(std::vector memory = std::vector(), + cmcpp::HostEncoding encoding = cmcpp::HostEncoding::Utf8, + const cmcpp::GuestRealloc &realloc = nullptr, + const cmcpp::GuestPostReturn &post_return = nullptr) +{ + return cmcpp::createCanonicalOptions(memory, realloc, cmcpp::encodeTo, encoding, post_return); +} + +struct ComponentInstance +{ +}; + +cmcpp::CallContextPtr mk_cx(std::vector memory = std::vector(), + cmcpp::HostEncoding encoding = cmcpp::HostEncoding::Utf8, + const cmcpp::GuestRealloc &realloc = nullptr, + const cmcpp::GuestPostReturn &post_return = nullptr) +{ + return cmcpp::createCallContext(memory, realloc, cmcpp::encodeTo, encoding, post_return); +} + +std::tuple mk_str(const std::string &s) +{ + return std::make_tuple(s, "utf8", s.size()); +} + +void fail(const std::string &msg) +{ + throw std::runtime_error(msg); +} + +void test(const cmcpp::ValBase &t, const std::vector> &vals_to_lift, const std::string &v, + const cmcpp::CallContextPtr &cx = mk_cx() + // ,const auto &dst_encoding = std::string(), + // const auto &lower_t = std::optional(), + // const auto &lower_v = std::optional() +) +{ + auto test_name = [&]() + { + return "test():"; + }; + + cmcpp::CoreValueIter vi(vals_to_lift); + + // if (v == nullptr) + // { + // try + // { + // auto got = lift_flat(cx, vi, t); + // fail(test_name() + " expected trap, but got " + got); + // } + // catch (const Trap &) + // { + // return; + // } + // } + + // auto got = cmcpp::lift_flat(*cx, vi, t.t); + // assert(vi.i == vi.values.size()); + // if (got != v) + // { + // fail(test_name() + " initial lift_flat() expected " + v + " but got " + got); + // } + + // if (!lower_t.has_value()) + // { + // lower_t = t; + // } + // if (!lower_v.has_value()) + // { + // lower_v = v; + // } + + // Heap heap(5 * cx.opts.memory.size()); + // if (dst_encoding.empty()) + // { + // dst_encoding = cx.opts.string_encoding; + // } + // cx = mk_cx(heap.memory, dst_encoding, heap.realloc); + // auto lowered_vals = lower_flat(cx, v, lower_t); + + // vi = CoreValueIter(lowered_vals); + // got = lift_flat(cx, vi, lower_t); + // if (!equal_modulo_string_encoding(got, lower_v)) + // { + // fail(test_name() + " re-lift expected " + lower_v + " but got " + got); + // } +} + +TEST_CASE("run_tests.py") +{ + std::cout << "run_tests.py" << std::endl; + // cmcpp::Field('x', U8()), cmcpp::Field('y', U16()), cmcpp::Field('z', U32())}), cmcpp::List({1, 2, 3}), {'x' : 1, 'y' : 2, 'z' : 3} + cmcpp::Record r({cmcpp::FieldT("x"), cmcpp::Field("y", cmcpp::ValType::U16), cmcpp::Field("z", cmcpp::ValType::U32)}); + std::vector> vals_to_lift({1, 2, 3}); + test(r, {1, 2, 3}, "{'x': 1, 'y': 2, 'z': 3}"); +} diff --git a/test/source/greeter.cpp b/test/source/greeter.cpp deleted file mode 100644 index 2ff31ba..0000000 --- a/test/source/greeter.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include - -#include - -TEST_CASE("Greeter") { - using namespace greeter; - - Greeter greeter("Tests"); - - CHECK(greeter.greet(LanguageCode::EN) == "Hello, Tests!"); - CHECK(greeter.greet(LanguageCode::DE) == "Hallo Tests!"); - CHECK(greeter.greet(LanguageCode::ES) == "¡Hola Tests!"); - CHECK(greeter.greet(LanguageCode::FR) == "Bonjour Tests!"); -} - -TEST_CASE("Greeter version") { - static_assert(std::string_view(GREETER_VERSION) == std::string_view("1.0")); - CHECK(std::string(GREETER_VERSION) == std::string("1.0")); -} diff --git a/test/source/main.cpp b/test/source/main.cpp deleted file mode 100644 index af24eeb..0000000 --- a/test/source/main.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -#include diff --git a/test/val.cpp b/test/val.cpp new file mode 100644 index 0000000..0caecd4 --- /dev/null +++ b/test/val.cpp @@ -0,0 +1,103 @@ +#include "val.hpp" +using namespace cmcpp; + +#include +#include + +TEST_CASE("primatives") +{ + CHECK(type(false) == ValType::Bool); + CHECK(type((int8_t)0) == ValType::S8); + CHECK(type((uint8_t)0) == ValType::U8); + CHECK(type((int16_t)0) == ValType::S16); + CHECK(type((uint16_t)0) == ValType::U16); + CHECK(type((int32_t)0) == ValType::S32); + CHECK(type((uint32_t)0) == ValType::U32); + CHECK(type((int64_t)0) == ValType::S64); + CHECK(type((uint64_t)0) == ValType::U64); + CHECK(type((float32_t)0) == ValType::F32); + CHECK(type((float64_t)0) == ValType::F64); + CHECK(type(L'0') == ValType::Char); +} + +TEST_CASE("Val") +{ + Val b = false; + CHECK(valType(b) == ValType::Bool); + CHECK(std::get(b) == false); + + Val u8 = (uint8_t)0; + CHECK(valType(u8) == ValType::U8); + CHECK(std::get(u8) == 0); + + Val s16 = (int16_t)0; + CHECK(valType(s16) == ValType::S16); + CHECK(std::get(s16) == 0); + + Val u16 = (uint16_t)0; + CHECK(valType(u16) == ValType::U16); + CHECK(std::get(u16) == 0); + + Val s32 = (int32_t)0; + CHECK(valType(s32) == ValType::S32); + CHECK(std::get(s32) == 0); + + Val u32 = (uint32_t)0; + CHECK(valType(u32) == ValType::U32); + CHECK(std::get(u32) == 0); + + Val s64 = (int64_t)0; + CHECK(valType(s64) == ValType::S64); + CHECK(std::get(s64) == 0); + + Val u64 = (uint64_t)0; + CHECK(valType(u64) == ValType::U64); + CHECK(std::get(u64) == 0); + + Val f32 = (float32_t)0; + CHECK(valType(f32) == ValType::F32); + CHECK(std::get(f32) == 0); + + Val f64 = (float64_t)0; + CHECK(valType(f64) == ValType::F64); + CHECK(std::get(f64) == 0); + + Val c = L'0'; + CHECK(valType(c) == ValType::Char); + CHECK(std::get(c) == '0'); +} + +TEST_CASE("WasmVal") +{ + WasmVal s32 = (int32_t)0; + CHECK(wasmValType(s32) == ValType::S32); + CHECK(std::get(s32) == 0); + + WasmVal s64 = (int64_t)0; + CHECK(wasmValType(s64) == ValType::S64); + CHECK(std::get(s64) == 0); + + WasmVal f32 = (float32_t)0; + CHECK(wasmValType(f32) == ValType::F32); + CHECK(std::get(f32) == 0); + + WasmVal f64 = (float64_t)0; + CHECK(wasmValType(f64) == ValType::F64); + CHECK(std::get(f64) == 0); +} + +TEST_CASE("other") +{ + std::vector> pairs; + WasmVal x = 0; + Val y = false; + fmt::print("first: {}\n", std::get(x)); + pairs.push_back({x, y}); + for (auto pairX : pairs) + { + auto first = pairX.first; + auto second = pairX.second; + // fmt::print("first: {}\n", first); + // fmt::print("second: {}\n", second); + } +} \ No newline at end of file diff --git a/test/val3.cpp b/test/val3.cpp new file mode 100644 index 0000000..a76ce26 --- /dev/null +++ b/test/val3.cpp @@ -0,0 +1,701 @@ +#include "val3.hpp" + +#include + +#include + +using namespace cmcpp3; + +enum class ValType : uint8_t +{ + Unknown, + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + Float32, + Float64, + Char, + String, + List, + Field, + Record, + Tuple, + Case, + Variant, + Enum, + Option, + Result, + Flags, + Own, + Borrow +}; + +struct Bool +{ + bool v; +}; + +struct String +{ + std::string v; +}; + +struct Person +{ + std::string lname; + std::string rname; + int age; + std::vector children; +}; + +TEST_CASE("Val") +{ + // auto x = HostVal(true); + // static_assert(std::is_same_v>, "Error"); + + Bool b{true}; + String s{"Hello"}; + + // std::cout << HostVal(b) << std::endl; + // std::cout << HostVal(s) << std::endl; + + print(true); + std::cout << std::endl; + print((int8_t)22); + std::cout << std::endl; + print((uint8_t)22); + std::cout << std::endl; + print((int16_t)22); + std::cout << std::endl; + print((uint16_t)22); + std::cout << std::endl; + print((int32_t)22); + std::cout << std::endl; + print((uint32_t)22); + std::cout << std::endl; + + print((int64_t)22); + std::cout << std::endl; + print((uint64_t)22); + std::cout << std::endl; + print((float32_t)22); + std::cout << std::endl; + print((float64_t)22); + std::cout << std::endl; + print('q'); + std::cout << std::endl; + print("aaaabbb123"); + std::cout << std::endl; + + std::vector v1 = {true, false, true}; + print(v1); + std::cout << std::endl; + + std::vector> v2 = {{true, false, true}, {false, true, false}}; + print(v2); + std::cout << std::endl; + + std::vector> v3 = {{"aaa", "bbb", "ccc"}, {"ddd", "eee", "fff"}}; + print(v3); + std::cout << std::endl; + + Person p1{"John", "Doe", 22, {}}; + print(p1); + std::cout << std::endl; + + std::vector v4 = {}; + print(v4); + std::cout << std::endl; + + std::tuple> t1; + print(t1); + std::cout << std::endl; +} + +namespace cmcpp3 +{ + + // Val::Val() : val{} + // { + // val.kind = ValType::U32; + // val.of.u32 = 0; + // } + // Val::Val(val_t val) : val(val) {} + + // Val::Val(bool b) : val{} + // { + // val.kind = ValType::Bool; + // val.of.b = b; + // } + + // Val::Val(int8_t s8) : val{} + // { + // val.kind = ValType::S8; + // val.of.s8 = s8; + // } + + // Val::Val(uint8_t u8) : val{} + // { + // val.kind = ValType::U8; + // val.of.u8 = u8; + // } + + // Val::Val(int16_t s16) : val{} + // { + // val.kind = ValType::S16; + // val.of.s16 = s16; + // } + + // Val::Val(uint16_t u16) : val{} + // { + // val.kind = ValType::U16; + // val.of.u16 = u16; + // } + + // Val::Val(int32_t s32) : val{} + // { + // val.kind = ValType::S32; + // val.of.s32 = s32; + // } + + // Val::Val(uint32_t u32) : val{} + // { + // val.kind = ValType::U32; + // val.of.s32 = u32; + // } + + // Val::Val(int64_t s64) : val{} + // { + // val.kind = ValType::S64; + // val.of.s64 = s64; + // } + + // Val::Val(uint64_t u64) : val{} + // { + // val.kind = ValType::U64; + // val.of.u64 = u64; + // } + + // Val::Val(float32_t f32) : val{} + // { + // val.kind = ValType::Float32; + // val.of.f32 = f32; + // } + + // Val::Val(float64_t f64) : val{} + // { + // val.kind = ValType::Float64; + // val.of.f64 = f64; + // } + + // Val::Val(char c) : val{} + // { + // val.kind = ValType::Char; + // val.of.c = c; + // } + + // Val::Val(const char *s) : val{} + // { + // val.kind = ValType::String; + // val.of.s.ptr = (const char8_t *)s; + // val.of.s.len = strlen(s); + // } + + // Val::Val(const char8_t *s, size_t len) : val{} + // { + // val.kind = ValType::String; + // val.of.s.ptr = s; + // val.of.s.len = len; + // } + + // Val::Val(ListPtr list) : val{} + // { + // val.kind = ValType::List; + // val.of.list = list.get(); + // shared_ptr = list; + // } + + // Val::Val(FieldPtr field) : val{} + // { + // val.kind = ValType::Field; + // val.of.field = field.get(); + // shared_ptr = field; + // } + + // Val::Val(RecordPtr record) : val{} + // { + // val.kind = ValType::Record; + // val.of.record = record.get(); + // shared_ptr = record; + // } + + // Val::Val(TuplePtr tuple) : val{} + // { + // val.kind = ValType::Tuple; + // val.of.tuple = tuple.get(); + // shared_ptr = tuple; + // } + + // Val::Val(CasePtr case_) : val{} + // { + // val.kind = ValType::Case; + // val.of.case_ = case_.get(); + // shared_ptr = case_; + // } + + // Val::Val(VariantPtr variant) : val{} + // { + // val.kind = ValType::Variant; + // val.of.variant = variant.get(); + // shared_ptr = variant; + // } + + // Val::Val(EnumPtr enum_) : val{} + // { + // val.kind = ValType::Enum; + // val.of.enum_ = enum_.get(); + // shared_ptr = enum_; + // } + + // Val::Val(OptionPtr option) : val{} + // { + // val.kind = ValType::Option; + // val.of.option = option.get(); + // shared_ptr = option; + // } + + // Val::Val(ResultPtr result) : val{} + // { + // val.kind = ValType::Result; + // val.of.result = result.get(); + // shared_ptr = result; + // } + + // // Val::Val(FlagsPtr flags) : val{} + // // { + // // val.kind = ValType::Flags; + // // val.of.flags = flags.get(); + // // shared_ptr = flags; + // // } + + // // Val::Val(OwnPtr own) : val{} + // // { + // // val.kind = ValType::Own; + // // val.of.own = own.get(); + // // shared_ptr = own; + // // } + + // // Val::Val(BorrowPtr borrow) : val{} + // // { + // // val.kind = ValType::Borrow; + // // val.of.borrow = borrow.get(); + // // shared_ptr = borrow; + // // } + + // // Val::Val(FuncTypePtr func) : val{} + // // { + // // val.kind = ValType::Func; + // // val.of.func = func.get(); + // // shared_ptr = func; + // // } + + // Val::Val(const Val &other) : val{} + // { + // val = other.val; + // shared_ptr = other.shared_ptr; + // } + + // Val::Val(Val &&other) noexcept : val{} + // { + // val.kind = ValType::U32; + // val.of.u32 = 0; + // std::swap(val, other.val); + // std::swap(shared_ptr, other.shared_ptr); + // } + + // Val::~Val() {} + + // Val &Val::operator=(const Val &other) noexcept + // { + // val = other.val; + // shared_ptr = other.shared_ptr; + // return *this; + // } + // Val &Val::operator=(Val &&other) noexcept + // { + // std::swap(val, other.val); + // std::swap(shared_ptr, other.shared_ptr); + // return *this; + // } + + // ValType Val::kind() const { return val.kind; } + + // bool Val::b() const + // { + // if (val.kind != ValType::Bool) + // std::abort(); + // return val.of.b; + // } + + // int8_t Val::s8() const + // { + // if (val.kind != ValType::S8) + // std::abort(); + // return val.of.s8; + // } + + // uint8_t Val::u8() const + // { + // if (val.kind != ValType::U8) + // std::abort(); + // return val.of.u8; + // } + + // int16_t Val::s16() const + // { + // if (val.kind != ValType::S16) + // std::abort(); + // return val.of.s16; + // } + + // uint16_t Val::u16() const + // { + // if (val.kind != ValType::U16) + // std::abort(); + // return val.of.u16; + // } + + // int32_t Val::s32() const + // { + // if (val.kind != ValType::S32) + // std::abort(); + // return val.of.s32; + // } + + // uint32_t Val::u32() const + // { + // if (val.kind != ValType::U32) + // std::abort(); + // return val.of.u32; + // } + + // int64_t Val::s64() const + // { + // if (val.kind != ValType::S64) + // std::abort(); + // return val.of.s64; + // } + + // uint64_t Val::u64() const + // { + // if (val.kind != ValType::U64) + // std::abort(); + // return val.of.u64; + // } + + // float32_t Val::f32() const + // { + // if (val.kind != ValType::Float32) + // std::abort(); + // return val.of.f32; + // } + + // float64_t Val::f64() const + // { + // if (val.kind != ValType::Float64) + // std::abort(); + // return val.of.f64; + // } + + // char Val::c() const + // { + // if (val.kind != ValType::Char) + // std::abort(); + // return val.of.c; + // } + + // utf8_t Val::s() const + // { + // if (val.kind != ValType::String) + // std::abort(); + // return val.of.s; + // } + + // std::string Val::string() const + // { + // if (val.kind != ValType::String) + // std::abort(); + // return std::string((const char *)val.of.s.ptr, val.of.s.len); + // } + + // ListPtr Val::list() const + // { + // if (val.kind != ValType::List) + // std::abort(); + // return std::get(shared_ptr); + // } + + // FieldPtr Val::field() const + // { + // if (val.kind != ValType::Field) + // std::abort(); + // return std::get(shared_ptr); + // } + + // RecordPtr Val::record() const + // { + // if (val.kind != ValType::Record) + // std::abort(); + // return std::get(shared_ptr); + // } + + // TuplePtr Val::tuple() const + // { + // if (val.kind != ValType::Tuple) + // std::abort(); + // return std::get(shared_ptr); + // } + + // CasePtr Val::case_() const + // { + // if (val.kind != ValType::Case) + // std::abort(); + // return std::get(shared_ptr); + // } + + // VariantPtr Val::variant() const + // { + // if (val.kind != ValType::Variant) + // std::abort(); + // return std::get(shared_ptr); + // } + + // EnumPtr Val::enum_() const + // { + // if (val.kind != ValType::Enum) + // std::abort(); + // return std::get(shared_ptr); + // } + + // OptionPtr Val::option() const + // { + // if (val.kind != ValType::Option) + // std::abort(); + // return std::get(shared_ptr); + // } + + // ResultPtr Val::result() const + // { + // if (val.kind != ValType::Result) + // std::abort(); + // return std::get(shared_ptr); + // } + + // FlagsPtr Val::flags() const + // { + // if (val.kind != ValType::Flags) + // std::abort(); + // return std::get(shared_ptr); + // } + + // OwnPtr Val::own() const + // { + // if (val.kind != ValType::Own) + // std::abort(); + // return std::get(shared_ptr); + // } + + // BorrowPtr Val::borrow() const + // { + // if (val.kind != ValType::Borrow) + // std::abort(); + // return std::get(shared_ptr); + // } + + // FuncType *Val::func() const + // { + // if (val.kind != ValType::Borrow) + // std::abort(); + // return val.of.func; + // } + + // WasmVal::WasmVal() {} + + // WasmVal::WasmVal(val_t val) : val(val) {} + + // WasmVal::WasmVal(int32_t i32) : val{} + // { + // val.kind = ValType::S32; + // val.of.s32 = i32; + // } + + // WasmVal::WasmVal(uint32_t i32) : val{} + // { + // val.kind = ValType::S32; + // val.of.s32 = i32; + // } + + // WasmVal::WasmVal(int64_t i64) : val{} + // { + // val.kind = ValType::S64; + // val.of.s64 = i64; + // } + + // WasmVal::WasmVal(uint64_t i64) : val{} + // { + // val.kind = ValType::S64; + // val.of.s64 = i64; + // } + + // WasmVal::WasmVal(float32_t f32) : val{} + // { + // val.kind = ValType::Float32; + // val.of.f32 = f32; + // } + + // WasmVal::WasmVal(float64_t f64) : val{} + // { + // val.kind = ValType::Float64; + // val.of.f64 = f64; + // } + + // WasmVal::WasmVal(const WasmVal &other) : val{} { val = other.val; } + + // WasmVal::WasmVal(WasmVal &&other) noexcept : val{} + // { + // val.kind = ValType::S32; + // val.of.s32 = 0; + // std::swap(val, other.val); + // } + + // WasmVal::~WasmVal() + // { + // // if (val.kind == WASMTIME_EXTERNREF && val.of.externref != nullptr) + // // { + // // TODO: wasmtime_externref_delete(val.of.externref); + // // } + // } + + // /// Copies the contents of another value into this one. + // WasmVal &WasmVal::operator=(const WasmVal &other) noexcept + // { + // val = other.val; + // return *this; + // } + + // WasmVal &WasmVal::operator=(WasmVal &&other) noexcept + // { + // std::swap(val, other.val); + // return *this; + // } + + // WasmValType WasmVal::kind() const + // { + // switch (val.kind) + // { + // case ValType::S32: + // return WasmValType::I32; + // case ValType::S64: + // return WasmValType::I64; + // case ValType::Float32: + // return WasmValType::F32; + // case ValType::Float64: + // return WasmValType::F64; + // default: + // throw std::runtime_error("Invalid WasmValType"); + // } + // } + + // int32_t WasmVal::i32() const + // { + // if (val.kind != ValType::S32) + // std::abort(); + // return val.of.s32; + // } + + // int64_t WasmVal::i64() const + // { + // if (val.kind != ValType::S64) + // std::abort(); + // return val.of.s64; + // } + + // float32_t WasmVal::f32() const + // { + // if (val.kind != ValType::Float32) + // std::abort(); + // return val.of.f32; + // } + + // float64_t WasmVal::f64() const + // { + // if (val.kind != ValType::Float64) + // std::abort(); + // return val.of.f64; + // } + + // List::List(const ValType &t) : t(t) {} + + // Field::Field(const std::string &label, const ValType &t) : label(label), t(t) {} + + // Record::Record() {} + + // Tuple::Tuple() {} + + // Case::Case(const std::string &label, const std::optional &v, + // const std::optional &refines) + // : label(label), v(v), refines(refines) {} + + // Variant::Variant(const std::vector &cases) : cases(cases) {} + + // Enum::Enum(const std::vector &labels) : labels(labels) {} + + // Option::Option(const Val &v) : v(v) {} + + // Result::Result(const std::optional &ok, const std::optional &error) + // : ok(ok), error(error) {} + + // Flags::Flags(const std::vector &labels) : labels(labels) {} + + // using Param = std::pair; + // class FuncTypeImpl : public FuncType + // { + // protected: + // std::vector params; + // std::vector results; + + // public: + // FuncTypeImpl() + // { + // } + // std::vector param_types() + // { + // std::vector retVal; + // if (!params.empty()) + // { + // for (const auto &pair : params) + // { + // retVal.push_back(pair.second); + // } + // } + // return retVal; + // } + + // std::vector result_types() + // { + // return results; + // } + // }; + + // FuncTypePtr createFuncType() + // { + // return std::make_shared(); + // } + +} \ No newline at end of file diff --git a/test/val3.hpp b/test/val3.hpp new file mode 100644 index 0000000..8314d88 --- /dev/null +++ b/test/val3.hpp @@ -0,0 +1,165 @@ +#ifndef VAL3_HPP +#define VAL3_HPP + +#include "val.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +constexpr bool IsTuple = false; + +template +constexpr bool IsTuple> = true; + +namespace cmcpp3 +{ + using float32_t = float; + using float64_t = double; + + template + concept Numeric = std::is_arithmetic_v; + + // template + // std::vector print() + // { + // std::cout << boost::typeindex::type_id().pretty_name() << std::endl; + // return {}; + // } + + template + std::vector print(const T &x) + { + std::cout << boost::typeindex::type_id().pretty_name(); + return {}; + } + + std::vector print(const char *x) + { + std::cout << boost::typeindex::type_id().pretty_name(); + return {}; + } + + std::vector print(const std::string &x) + { + std::cout << "string"; + return {}; + } + + // template + // concept VectorType = requires(T t) { + // { + // t.size() + // } -> std::same_as; // Requires a size() member function + // { + // t.push_back(std::declval()) + // }; + // }; + + template + std::vector print(const std::tuple &x); + + template + std::vector print(const std::string_view &label, const T &x); + + template + std::vector print(const T &x); + + template + std::vector print(const std::tuple &x); + + template + std::vector print(const std::vector &x) + { + std::cout << "List<"; + print(T{}); + std::cout << ">"; + // for (const auto &v : x) + // { + // print(v); + // } + return {}; + } + + template + std::vector print(const std::string_view &label, const T &x) + { + std::cout << "Field<" << label << ": "; + print(x); + std::cout << ">"; + return {}; + } + + template + struct TuplePrinter + { + static void tprint(const Tuple &t) + { + TuplePrinter::tprint(t); + std::cout << ", "; + print(std::get(t)); + } + }; + + template + struct TuplePrinter + { + static void tprint(const Tuple &t) + { + print(std::get<0>(t)); + } + }; + + template + std::vector print(const std::tuple &x) + { + std::cout << "Tuple<"; + TuplePrinter::tprint(x); + std::cout << ">"; + return {}; + } + + std::map dedup; + + template + std::vector print(const T &x) + { + const std::string label = boost::typeindex::type_id().pretty_name(); + std::cout << "Record<" << label << ">"; + + if (!dedup[label]) + { + dedup[label] = true; + constexpr auto names = boost::pfr::names_as_array(); + boost::pfr::for_each_field(x, [&names](const auto &field, std::size_t idx) + { + std::cout << std::endl; + print(names[idx], field); + return; }); + } + return {}; + } + + // template + // inline typename std::enable_if::type + // printTuple(std::tuple &t) + // { + // // Base case: no more elements to print + // } + + // template + // inline typename std::enable_if < I::type + // printTuple(std::tuple &t) + // { + // std::cout << "Tuple" << std::endl; // Print the current element + // printTuple(t); // Recurse to the next element + // } +} +#endif diff --git a/test/wasm/.devcontainer/Dockerfile b/test/wasm/.devcontainer/Dockerfile new file mode 100644 index 0000000..f67e87f --- /dev/null +++ b/test/wasm/.devcontainer/Dockerfile @@ -0,0 +1,49 @@ +# See: https://github.com/devcontainers/images/blob/main/src/cpp/.devcontainer/Dockerfile +ARG VARIANT=ubuntu-22.04 +FROM mcr.microsoft.com/devcontainers/base:${VARIANT} +USER root + +# Install needed packages. Use a separate RUN statement to add your own dependencies. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install cmake curl git tar xz-utils ninja-build \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +ARG USERNAME=vscode + +# Install wasi-sdk https://github.com/WebAssembly/wasi-sdk +ARG WASI_VERSION="21" +ENV WASI_VERSION_FULL="${WASI_VERSION}.0" +ENV WASI_LOCATION="${LOCATION:-"/usr/local"}/lib" +ENV WASI_FILE="wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz" +ENV WASI_SDK_PREFIX="/usr/local/lib/wasi-sdk-${WASI_VERSION_FULL}" +RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/${WASI_FILE} --output ${WASI_FILE} +RUN tar -C ${WASI_LOCATION} -xvf ${WASI_FILE} +RUN rm ${WASI_FILE} + +# Install wit-bindgen https://github.com/bytecodealliance/wit-bindgen +ARG WIT_BINDGEN_VERSION="0.22.0" +RUN curl -L https://github.com/bytecodealliance/wit-bindgen/releases/download/v${WIT_BINDGEN_VERSION}/wit-bindgen-${WIT_BINDGEN_VERSION}-x86_64-linux.tar.gz --output wit-bindgen-${WIT_BINDGEN_VERSION}-x86_64-linux.tar.gz +RUN tar -xvzf wit-bindgen-${WIT_BINDGEN_VERSION}-x86_64-linux.tar.gz +RUN cp wit-bindgen-${WIT_BINDGEN_VERSION}-x86_64-linux/wit-bindgen ${LOCATION}/bin/wit-bindgen +RUN rm -r wit-bindgen-${WIT_BINDGEN_VERSION}-x86_64-linux* + +# Install wasm-tools https://github.com/bytecodealliance/wasm-tools +ARG WASM_TOOLS_VERSION="1.201.0" +RUN curl -L https://github.com/bytecodealliance/wasm-tools/releases/download/v${WASM_TOOLS_VERSION}/wasm-tools-${WASM_TOOLS_VERSION}-x86_64-linux.tar.gz --output wasm-tools-${WASM_TOOLS_VERSION}-x86_64-linux.tar.gz +RUN tar -xvzf wasm-tools-${WASM_TOOLS_VERSION}-x86_64-linux.tar.gz +RUN cp wasm-tools-${WASM_TOOLS_VERSION}-x86_64-linux/wasm-tools ${LOCATION}/bin/wasm-tools +RUN rm -r wasm-tools-${WASM_TOOLS_VERSION}-x86_64-linux* + +# Install wasi adapter https://github.com/bytecodealliance/wasmtime +ARG WASMTIME_VERSION="19.0.0" +RUN curl -L -O https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasi_snapshot_preview1.reactor.wasm +RUN mv wasi_snapshot_preview1.reactor.wasm ${LOCATION}/lib/wasi_snapshot_preview1.reactor.wasm + +# # Install wasmtime +# ENV WASMTIME_VERSION="${WASMTIME_VERSION:-"v9.0.4"}" +# ENV INSTALLER=./wasmtime-install.sh +# RUN curl https://wasmtime.dev/install.sh -L --output ${INSTALLER} +# RUN chmod a+x ${INSTALLER} +# RUN ${INSTALLER} --version ${WASMTIME_VERSION} +# RUN cp ${HOME}/.wasmtime/bin/wasmtime /usr/bin/wasmtime + diff --git a/test/wasm/.devcontainer/devcontainer.json b/test/wasm/.devcontainer/devcontainer.json new file mode 100644 index 0000000..035500a --- /dev/null +++ b/test/wasm/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "name": "C/C++ WASM", + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": "true", + "username": "vscode", + "userUid": "1000", + "userGid": "1000", + "upgradePackages": "true" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools-extension-pack", + "dtsvet.vscode-wasm" + ] + } + }, + "remoteUser": "vscode", + "remoteEnv": { + "PATH": "${containerEnv:PATH}:${WASI_SDK_PREFIX}/bin" + }, + "postCreateCommand": "echo 'Done'" +} \ No newline at end of file diff --git a/test/wasm/.dockerignore b/test/wasm/.dockerignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/test/wasm/.dockerignore @@ -0,0 +1 @@ +/build diff --git a/test/wasm/.gitignore b/test/wasm/.gitignore new file mode 100644 index 0000000..42ffc56 --- /dev/null +++ b/test/wasm/.gitignore @@ -0,0 +1,3 @@ +/.vscode/settings.json +/build +/wasi_snapshot_preview1.reactor.wasm \ No newline at end of file diff --git a/test/wasm/.vscode/launch.json b/test/wasm/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/test/wasm/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/test/wasm/CMakeLists.txt b/test/wasm/CMakeLists.txt new file mode 100644 index 0000000..f52038f --- /dev/null +++ b/test/wasm/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.22) + +project(wasmtests) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(WASM_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin/${PROJECT_NAME}.wasm") + +add_custom_command( + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/test.wit + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/tests.c ${CMAKE_CURRENT_BINARY_DIR}/tests.h ${CMAKE_CURRENT_BINARY_DIR}/tests_component_type.o + COMMAND wit-bindgen c --out-dir ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test.wit +) +add_custom_target(wit-generate ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/tests.c) + +set(CMAKE_EXECUTABLE_SUFFIX ".wasm") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions --sysroot=/${WASI_SDK_PREFIX}/share/wasi-sysroot") + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_executable(wasmtests + bool.cpp + int.cpp + float.cpp + string.cpp + uint.cpp + util.cpp + ${CMAKE_CURRENT_BINARY_DIR}/tests.c + ${CMAKE_CURRENT_BINARY_DIR}/tests.h +) + +target_link_options(wasmtests PRIVATE "LINKER:--no-entry") + + +add_custom_command( + TARGET wasmtests POST_BUILD + # OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/wasmtests-component.wasm + COMMAND echo "wasm-tools component new ${CMAKE_CURRENT_BINARY_DIR}/wasmtests.wasm -o ${CMAKE_CURRENT_BINARY_DIR}/wasmtests-component.wasm --adapt wasi_snapshot_preview1=${WASI_SDK_PREFIX}/wasi_snapshot_preview1.reactor.wasm" +) + +target_link_libraries(wasmtests + ${CMAKE_CURRENT_BINARY_DIR}/tests_component_type.o +) + +install(TARGETS wasmtests + RUNTIME DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../install-target +) diff --git a/test/wasm/CMakePresets.json b/test/wasm/CMakePresets.json new file mode 100644 index 0000000..2854e5b --- /dev/null +++ b/test/wasm/CMakePresets.json @@ -0,0 +1,21 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 22, + "patch": 1 + }, + "configurePresets": [ + { + "name": "wasi-sdk", + "displayName": "wasi-sdk", + "toolchainFile": "$env{WASI_SDK_PREFIX}/share/cmake/wasi-sdk.cmake", + "binaryDir": "${sourceDir}/build", + "installDir": "${sourceDir}/build/stage", + "cacheVariables": { + "WASI_SDK_PREFIX": "$env{WASI_SDK_PREFIX}" + } + } + ], + "buildPresets": [] +} \ No newline at end of file diff --git a/test/wasm/bool.cpp b/test/wasm/bool.cpp new file mode 100644 index 0000000..5544b49 --- /dev/null +++ b/test/wasm/bool.cpp @@ -0,0 +1,19 @@ +#include "util.hpp" + +bool tests_bool_and(bool a, bool b) +{ + dbglog("tests_bool_and(" + std::to_string(a) + ", " + std::to_string(b) + ")"); + return a && b; +} + +bool tests_bool_or(bool a, bool b) +{ + dbglog("tests_bool_and(" + std::to_string(a) + ", " + std::to_string(b) + ")"); + return a || b; +} + +bool tests_bool_xor(bool a, bool b) +{ + dbglog("tests_bool_and(" + std::to_string(a) + ", " + std::to_string(b) + ")"); + return a ^ b; +} diff --git a/test/wasm/build.sh b/test/wasm/build.sh new file mode 100755 index 0000000..bb739d5 --- /dev/null +++ b/test/wasm/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )" +ROOT_DIR="$(pwd)" + +echo "SCRIPT_DIR: ${SCRIPT_DIR}" +echo "ROOT_DIR: $ROOT_DIR" + +function cleanup() +{ + CONTAINER_ID=$(jq -r .containerId ${SCRIPT_DIR}/up.json) + docker rm -f "${CONTAINER_ID}" + rm ${SCRIPT_DIR}/up.json +} + +trap cleanup EXIT + +npx -y @devcontainers/cli up --workspace-folder ${SCRIPT_DIR} | jq . > "${SCRIPT_DIR}/up.json" + +CMAKE_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=\${WASI_SDK_PREFIX}/share/cmake/wasi-sdk.cmake -DWASI_SDK_PREFIX=\${WASI_SDK_PREFIX}" + +npx -y @devcontainers/cli exec --workspace-folder ${SCRIPT_DIR} mkdir -p ./build +npx -y @devcontainers/cli exec --workspace-folder ${SCRIPT_DIR} sh -c 'cmake -S . -B ./build -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PREFIX}/share/cmake/wasi-sdk.cmake -DWASI_SDK_PREFIX=${WASI_SDK_PREFIX}' +npx -y @devcontainers/cli exec --workspace-folder ${SCRIPT_DIR} cmake --build ./build --parallel diff --git a/test/wasm/float.cpp b/test/wasm/float.cpp new file mode 100644 index 0000000..dc1eadd --- /dev/null +++ b/test/wasm/float.cpp @@ -0,0 +1,21 @@ +#include "util.hpp" + +float tests_float32_add(float a, float b) +{ + return a + b; +} + +float tests_float32_sub(float a, float b) +{ + return a - b; +} + +double tests_float64_add(double a, double b) +{ + return a + b; +} + +double tests_float64_sub(double a, double b) +{ + return a - b; +} diff --git a/test/wasm/int.cpp b/test/wasm/int.cpp new file mode 100644 index 0000000..e69ba07 --- /dev/null +++ b/test/wasm/int.cpp @@ -0,0 +1,35 @@ +#include "util.hpp" + +int8_t tests_s8_add(int8_t a, int8_t b) +{ + return a + b; +} +int16_t tests_s16_add(int16_t a, int16_t b) +{ + return a + b; +} +int32_t tests_s32_add(int32_t a, int32_t b) +{ + return a + b; +} +int64_t tests_s64_add(int64_t a, int64_t b) +{ + return a + b; +} + +int8_t tests_s8_sub(int8_t a, int8_t b) +{ + return a - b; +} +int16_t tests_s16_sub(int16_t a, int16_t b) +{ + return a - b; +} +int32_t tests_s32_sub(int32_t a, int32_t b) +{ + return a - b; +} +int64_t tests_s64_sub(int64_t a, int64_t b) +{ + return a - b; +} diff --git a/test/wasm/string.cpp b/test/wasm/string.cpp new file mode 100644 index 0000000..b3d8af2 --- /dev/null +++ b/test/wasm/string.cpp @@ -0,0 +1,71 @@ +#include "util.hpp" +#include +#include + +static uint32_t tally = 0; + +void tests_list_char_append(tests_list_char32_t *a, tests_list_char32_t *b, tests_list_char32_t *ret) +{ + std::vector v(a->ptr, a->ptr + a->len); + std::vector v2(b->ptr, b->ptr + b->len); + v.insert(v.end(), v2.begin(), v2.end()); + ret->ptr = (uint32_t *)malloc(v.size() * sizeof(uint32_t)); + ret->len = v.size(); + std::memcpy(ret->ptr, v.data(), v.size() * sizeof(uint32_t)); +} + +void tests_string_append(tests_string_t *a, tests_string_t *b, tests_string_t *ret) +{ + std::string s1((const char *)a->ptr, a->len); + dbglog(std::string("s1: ") + s1); + tests_string_free(a); + std::string s2((const char *)b->ptr, b->len); + dbglog(std::string("s2: ") + s2); + tests_string_free(b); + std::string r = s1 + s2; + dbglog(std::to_string(++tally) + ": " + r); + tests_string_dup(ret, r.c_str()); +} + +void tests_list_list_string_append(tests_list_list_string_t *a, tests_list_list_string_t *b, tests_string_t *ret) +{ + dbglog(std::string("a len: ") + std::to_string(a->len)); + dbglog(std::string("b len: ") + std::to_string(b->len)); + + std::vector> v; + for (size_t i = 0; i < a->len; i++) + { + std::vector v2; + for (size_t j = 0; j < a->ptr[i].len; j++) + { + std::string s((const char *)a->ptr[i].ptr[j].ptr, a->ptr[i].ptr[j].len); + dbglog(std::string("a-s: ") + s); + v2.push_back(s); + } + v.push_back(v2); + } + + for (size_t i = 0; i < b->len; i++) + { + std::vector v2; + for (size_t j = 0; j < b->ptr[i].len; j++) + { + std::string s((const char *)b->ptr[i].ptr[j].ptr, b->ptr[i].ptr[j].len); + dbglog(std::string("b-s: ") + s); + v2.push_back(s); + } + v.push_back(v2); + } + + std::string r; + for (size_t i = 0; i < v.size(); i++) + { + for (size_t j = 0; j < v[i].size(); j++) + { + r += v[i][j]; + } + } + + dbglog(std::to_string(++tally) + ": " + r); + tests_string_dup(ret, r.c_str()); +} diff --git a/test/wasm/test.wit b/test/wasm/test.wit new file mode 100644 index 0000000..ee7c847 --- /dev/null +++ b/test/wasm/test.wit @@ -0,0 +1,43 @@ +package component-model-cpp:test-wasm; + +world tests { +/* imports --- + + guests dispose all params as needed + guests should dispose "results" as needed +*/ + + import dbglog: func(msg: string); + +/* exports --- + + guests dispose all params as needed + hosts call cabi_post_XXX to dispose "results" as needed +*/ + export bool-and: func(a: bool, b: bool) -> bool; + export bool-or: func(a: bool, b: bool) -> bool; + export bool-xor: func(a: bool, b: bool) -> bool; + export float32-add: func(a: float32, b: float32) -> float32; + export float32-sub: func(a: float32, b: float32) -> float32; + export float64-add: func(a: float64, b: float64) -> float64; + export float64-sub: func(a: float64, b: float64) -> float64; + export u8-add: func(a: u8, b: u8) -> u8; + export u8-sub: func(a: u8, b: u8) -> u8; + export u16-add: func(a: u16, b: u16) -> u16; + export u16-sub: func(a: u16, b: u16) -> u16; + export u32-add: func(a: u32, b: u32) -> u32; + export u32-sub: func(a: u32, b: u32) -> u32; + export u64-add: func(a: u64, b: u64) -> u64; + export u64-sub: func(a: u64, b: u64) -> u64; + export s8-add: func(a: s8, b: s8) -> s8; + export s8-sub: func(a: s8, b: s8) -> s8; + export s16-add: func(a: s16, b: s16) -> s16; + export s16-sub: func(a: s16, b: s16) -> s16; + export s32-add: func(a: s32, b: s32) -> s32; + export s32-sub: func(a: s32, b: s32) -> s32; + export s64-add: func(a: s64, b: s64) -> s64; + export s64-sub: func(a: s64, b: s64) -> s64; + export list-char-append: func(a: list, b: list) -> list; + export list-list-string-append: func(a: list>, b: list>) -> string; + export string-append: func(a: string, b: string) -> string; +} diff --git a/test/wasm/uint.cpp b/test/wasm/uint.cpp new file mode 100644 index 0000000..6920e0c --- /dev/null +++ b/test/wasm/uint.cpp @@ -0,0 +1,35 @@ +#include "util.hpp" + +uint8_t tests_u8_add(uint8_t a, uint8_t b) +{ + return a + b; +} +uint16_t tests_u16_add(uint16_t a, uint16_t b) +{ + return a + b; +} +uint32_t tests_u32_add(uint32_t a, uint32_t b) +{ + return a + b; +} +uint64_t tests_u64_add(uint64_t a, uint64_t b) +{ + return a + b; +} + +uint8_t tests_u8_sub(uint8_t a, uint8_t b) +{ + return a - b; +} +uint16_t tests_u16_sub(uint16_t a, uint16_t b) +{ + return a - b; +} +uint32_t tests_u32_sub(uint32_t a, uint32_t b) +{ + return a - b; +} +uint64_t tests_u64_sub(uint64_t a, uint64_t b) +{ + return a - b; +} diff --git a/test/wasm/util.cpp b/test/wasm/util.cpp new file mode 100644 index 0000000..81be52f --- /dev/null +++ b/test/wasm/util.cpp @@ -0,0 +1,8 @@ +#include "util.hpp" + +void dbglog(const std::string str) +{ + tests_string_t msg; + tests_string_dup(&msg, str.c_str()); + tests_dbglog(&msg); +} diff --git a/test/wasm/util.hpp b/test/wasm/util.hpp new file mode 100644 index 0000000..d24ca72 --- /dev/null +++ b/test/wasm/util.hpp @@ -0,0 +1,4 @@ +#include "tests.h" +#include + +void dbglog(const std::string str); \ No newline at end of file diff --git a/test/wasmtime.cpp b/test/wasmtime.cpp new file mode 100644 index 0000000..b8bd40b --- /dev/null +++ b/test/wasmtime.cpp @@ -0,0 +1,214 @@ +#include "context.hpp" +#include "lower.hpp" +#include "lift.hpp" +#include "val.hpp" +#include "util.hpp" +#include "flatten.hpp" + +#include + +#include +#include +#include +#include + +#include + +std::vector readWasmBinaryToBuffer(const char *filename) +{ + std::ifstream file(filename, std::ios::binary); + std::vector buffer(std::istreambuf_iterator(file), {}); + return buffer; +} + +wasmtime::Val val2WasmtimeVal(const cmcpp::WasmVal &val) +{ + if (std::holds_alternative(val)) + return wasmtime::Val(std::get(val)); + if (std::holds_alternative(val)) + return wasmtime::Val(std::get(val)); + if (std::holds_alternative(val)) + return wasmtime::Val(std::get(val)); + if (std::holds_alternative(val)) + return wasmtime::Val(std::get(val)); + throw std::runtime_error("Invalid WasmValType"); +} + +std::vector vals2WasmtimeVals(const std::vector &vals) +{ + std::vector retVal; + for (const auto &val : vals) + retVal.push_back(val2WasmtimeVal(val)); + return retVal; +} + +cmcpp::WasmVal wasmtimeVal2Val(const wasmtime::Val &val) +{ + switch (val.kind()) + { + case wasmtime::ValKind::I32: + return val.i32(); + case wasmtime::ValKind::I64: + return val.i64(); + case wasmtime::ValKind::F32: + return val.f32(); + case wasmtime::ValKind::F64: + return val.f64(); + default: + throw std::runtime_error("Invalid WasmValType"); + } +} + +std::vector wasmtimeVals2WasmVals(const std::vector &vals) +{ + std::vector retVal; + for (const auto &val : vals) + retVal.push_back(wasmtimeVal2Val(val)); + return retVal; +} + +std::ostream &operator<<(std::ostream &os, const cmcpp::ValType &valType) +{ + os << static_cast(valType); + return os; +} + +TEST_CASE("generic Val") +{ + // auto a = cmcpp::HostVal(std::vector{1, 2, 3}); + // auto b = cmcpp::HostVal(std::map>{std::make_pair("a", std::vector{1, 2, 3})}); + // // auto b = std::vector{1, 2, 3}; + // std::cout << b.type() << std::endl; +} + +cmcpp::ListPtr createList() +{ + auto list = std::make_shared(cmcpp::ValType::Char); + list->vs.push_back('a'); + list->vs.push_back('b'); + list->vs.push_back('c'); + return list; +} + +TEST_CASE("wasmtime") +{ + std::cout << "wasmtime" << std::endl; + + wasmtime::Engine engine; + + wasmtime::Store store(engine); + std::vector _contents = readWasmBinaryToBuffer("test/wasm/build/wasmtests.wasm"); + const wasmtime::Span &contents = _contents; + auto module = wasmtime::Module::compile(engine, contents).unwrap(); + + wasmtime::WasiConfig wasi; + wasi.inherit_argv(); + wasi.inherit_env(); + wasi.inherit_stdin(); + wasi.inherit_stdout(); + wasi.inherit_stderr(); + store.context().set_wasi(std::move(wasi)).unwrap(); + + wasmtime::Linker linker(engine); + linker.define_wasi().unwrap(); + + auto dbglog = [&store](wasmtime::Caller caller, uint32_t msg, uint32_t msg_len) + { + auto memory = std::get(*caller.get_export("memory")); + auto data = memory.data(store.context()); + const char *msg_ptr = reinterpret_cast(&data[msg]); + std::string str(msg_ptr, msg_len); + std::cout << str << std::endl; + }; + + linker.func_wrap("$root", "dbglog", dbglog).unwrap(); + auto instance = linker.instantiate(store, module).unwrap(); + linker.define_instance(store, "linking2", instance).unwrap(); + + auto cabi_realloc = std::get(*instance.get(store, "cabi_realloc")); + std::function realloc = [&store, cabi_realloc](int a, int b, int c, int d) -> int + { + return cabi_realloc.call(store, {a, b, c, d}).unwrap()[0].i32(); + }; + auto memory = std::get(*instance.get(store, "memory")); + store.context().set_data(memory); + wasmtime::Span data = memory.data(store.context()); + auto bool_and = std::get(*instance.get(store, "bool-and")); + auto list_char_append = std::get(*instance.get(store, "list-char-append")); + auto list_list_string_append = std::get(*instance.get(store, "list-list-string-append")); + auto string_append = std::get(*instance.get(store, "string-append")); + + // Actual ABI Test Code -------------------------------------------- + cmcpp::ListPtr list = createList(); + CHECK(list->vs.size() == 3); + cmcpp::Val v = list; + auto list3 = std::get(v); + CHECK(list3->vs.size() == 3); + + cmcpp::CallContextPtr cx = cmcpp::createCallContext(data, realloc, cmcpp::encodeTo); + std::vector wasmtimeVals; + std::vector cmcppVals; + std::vector cmcppWasmVals; + + wasmtimeVals = vals2WasmtimeVals(cmcpp::lower_values(*cx, {false, false})); + wasmtimeVals = bool_and.call(store, wasmtimeVals).unwrap(); + cmcppVals = cmcpp::lift_values(*cx, wasmtimeVals2WasmVals(wasmtimeVals), {cmcpp::ValType::Bool}); + CHECK(std::get(cmcppVals[0]) == false); + + wasmtimeVals = vals2WasmtimeVals(cmcpp::lower_values(*cx, {false, true})); + wasmtimeVals = bool_and.call(store, wasmtimeVals).unwrap(); + cmcppVals = cmcpp::lift_values(*cx, wasmtimeVals2WasmVals(wasmtimeVals), {cmcpp::ValType::Bool}); + CHECK(std::get(cmcppVals[0]) == false); + + wasmtimeVals = vals2WasmtimeVals(cmcpp::lower_values(*cx, {true, false})); + wasmtimeVals = bool_and.call(store, wasmtimeVals).unwrap(); + cmcppVals = cmcpp::lift_values(*cx, wasmtimeVals2WasmVals(wasmtimeVals), {cmcpp::ValType::Bool}); + CHECK(std::get(cmcppVals[0]) == false); + + wasmtimeVals = vals2WasmtimeVals(cmcpp::lower_values(*cx, {true, true})); + wasmtimeVals = bool_and.call(store, wasmtimeVals).unwrap(); + cmcppVals = cmcpp::lift_values(*cx, wasmtimeVals2WasmVals(wasmtimeVals), {cmcpp::ValType::Bool}); + CHECK(std::get(cmcppVals[0]) == true); + + auto myVec = std::vector{'a', 'b', 'c'}; + auto charList1 = cmcpp::lower_hostVal(*cx, myVec); + auto myVec2 = std::vector{'d', 'e', 'f'}; + auto charList2 = cmcpp::lower_hostVal(*cx, myVec2); + // auto charList2 = cmcpp::lower_hostVal(std::vector{'d', 'e', 'f'}); + + // wasmtimeVals = vals2WasmtimeVals({charList1, charList2}); + // wasmtimeVals = list_char_append.call(store, wasmtimeVals).unwrap(); + // cmcppWasmVals = wasmtimeVals2WasmVals(wasmtimeVals); + // cmcppVals = cmcpp::lift_values(*cx, cmcppWasmVals, {std::make_pair(cmcpp::ValType::List, cmcpp::ValType::Char)}); + // CHECK(std::get(cmcppVals[0])->vs.size() == 6); + // CHECK(std::get(std::get(cmcppVals[0])->vs[0]) == 'a'); + // CHECK(std::get(std::get(cmcppVals[0])->vs[3]) == 'd'); + // CHECK(std::get(std::get(cmcppVals[0])->vs[5]) == 'f'); + + auto lvs = cmcpp::lower_values(*cx, cmcpp::MAX_FLAT_PARAMS, {"1234", "5678"}); + auto ret = string_append.call(store, vals2WasmtimeVals(lvs)).unwrap(); + auto wret = wasmtimeVals2WasmVals(ret); + cmcppVals = cmcpp::lift_values(*cx, cmcpp::MAX_FLAT_PARAMS, wret, {cmcpp::ValType::String}); + auto str = std::get(cmcppVals[0]); + CHECK(str->to_string().compare("12345678") == 0); + + // auto myVec4 = std::vector{{"xxx1234", "xxxx5678", "xxxxxabcd", "xxxxxxefgh"}}; + // auto charListList4 = cmcpp::lower_hostVal(*cx, myVec4); + // lvs = cmcpp::lower_values(*cx, {charListList4}); + auto myVec3 = std::vector>{{"1234", "5678"}, {"abcd", "efgh"}}; + auto charListList = cmcpp::lower_hostVal(*cx, myVec3); + lvs = cmcpp::lower_values(*cx, {charListList, charListList}); + wasmtimeVals = vals2WasmtimeVals(lvs); + wasmtimeVals = list_list_string_append.call(store, wasmtimeVals).unwrap(); + wret = wasmtimeVals2WasmVals(wasmtimeVals); + cmcppVals = cmcpp::lift_values(*cx, wret, {cmcpp::ValType::String}); + CHECK(std::get(cmcppVals[0])->len == 32); + + // Actual ABI Test Code -------------------------------------------- +} + +TEST_CASE("component-model-cpp version") +{ + // static_assert(std::string(GREETER_VERSION) == std::string("1.0")); + // CHECK(std::string(GREETER_VERSION) == std::string("1.0")); +} diff --git a/tmp.txt b/tmp.txt new file mode 100644 index 0000000..26cf5e0 --- /dev/null +++ b/tmp.txt @@ -0,0 +1 @@ +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/vcpkg b/vcpkg new file mode 160000 index 0000000..3850888 --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit 3850888eb8480abc8f73f84bf618de3b68900572 diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..0f861db --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,18 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "fba75d09065fcc76a25dcf386b1d00d33f5175af", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ], + "overlay-ports": [ + "vcpkg_overlays" + ], + "overlay-triplets": [] +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..2701123 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,9 @@ +{ + "dependencies": [ + "boost-pfr", + "boost-type-index", + "doctest", + "fmt", + "wasmtime-cpp-api" + ] +} \ No newline at end of file diff --git a/vcpkg_overlays/wasmtime-c-api/portfile.cmake b/vcpkg_overlays/wasmtime-c-api/portfile.cmake new file mode 100644 index 0000000..ecf0c11 --- /dev/null +++ b/vcpkg_overlays/wasmtime-c-api/portfile.cmake @@ -0,0 +1,37 @@ +if (WIN32) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-windows-c-api.zip" + FILENAME "wasmtime-v${VERSION}-x86_64-windows-c-api.zip" + SHA512 0 + ) +elseif (APPLE) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-macos-c-api.tar.xz" + FILENAME "wasmtime-v${VERSION}-x86_64-macos-c-api.tar.xz" + SHA512 0 + ) +elseif (LINUX) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-linux-c-api.tar.xz" + FILENAME "wasmtime-v${VERSION}-x86_64-linux-c-api.tar.xz" + SHA512 35aa6de438fead5ab043eb9de5bb688c0c7da0ba3b8336f6f670675087b44af1385559037162cf5951cb9dadbd201db18c0678275bd7704fdc078bc75bc08326 + ) +endif() + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${ARCHIVE} +) + +file(COPY ${SOURCE_PATH}/include/. DESTINATION ${CURRENT_PACKAGES_DIR}/include/wasmtime-c-api) +if (WIN32) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/bin) +else () + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/lib) +endif () + +# Handle copyright +file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/wasmtime-c-api RENAME copyright) + diff --git a/vcpkg_overlays/wasmtime-c-api/vcpkg.json b/vcpkg_overlays/wasmtime-c-api/vcpkg.json new file mode 100644 index 0000000..f5b22fe --- /dev/null +++ b/vcpkg_overlays/wasmtime-c-api/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "wasmtime-c-api", + "version-string": "18.0.2", + "description": "Wasmtime is a standalone runtime for WebAssembly, using Cranelift to JIT-compile from WebAssembly to native code.", + "homepage": "https://github.com/bytecodealliance/wasmtime", + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake b/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake new file mode 100644 index 0000000..a4ada0d --- /dev/null +++ b/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake @@ -0,0 +1,16 @@ +vcpkg_download_distfile(ARCHIVE + URLS "https://codeload.github.com/bytecodealliance/wasmtime-cpp/zip/da579e78f799aca0a472875b7e348f74b3a04145" + FILENAME "wasmome-cpp-da579e78f799acaoa472875b7e348f74b3a04145.zip" + SHA512 917da1e41ea3e62c4167e1e42afe1d26537bb8351acddbd59cfe24d5ab535d11ce0e0980d38b5fbfca008303b50d552d042f1db835d8d13d59757b269c4f3887 +) + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${ARCHIVE} +) + +file(COPY ${SOURCE_PATH}/include/. DESTINATION ${CURRENT_PACKAGES_DIR}/include/wasmtime-cpp-api) + +# Handle copyright +file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/wasmtime-cpp-api RENAME copyright) + diff --git a/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json b/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json new file mode 100644 index 0000000..6cda053 --- /dev/null +++ b/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "wasmtime-cpp-api", + "version-string": "9.0.0", + "description": "Wasmtime is a standalone runtime for WebAssembly, using Cranelift to JIT-compile from WebAssembly to native code.", + "homepage": "https://github.com/bytecodealliance/wasmtime-cpp", + "license": "Apache-2.0", + "dependencies": [ + "wasmtime-c-api" + ] +} \ No newline at end of file