diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..173f8a6a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,2 @@
+BasedOnStyle: LLVM
+ColumnLimit: 120
diff --git a/.clangd b/.clangd
new file mode 100644
index 00000000..1ce440cd
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,2 @@
+CompileFlags:
+ CompilationDatabase: build-vscode/
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..9d146f53
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,82 @@
+name: CI
+
+on: [push, pull_request]
+env:
+ CIBW_BUILD_VERBOSITY: 3
+ CIBW_TEST_REQUIRES: "pytest"
+ CIBW_TEST_COMMAND: "pytest -svv --durations=20 {project}/tests/python/"
+ MLC_CIBW_VERSION: "2.20.0"
+ MLC_PYTHON_VERSION: "3.9"
+ MLC_CIBW_WIN_BUILD: "cp39-win_amd64"
+ MLC_CIBW_MAC_BUILD: "cp39-macosx_arm64"
+ MLC_CIBW_LINUX_BUILD: "cp312-manylinux_x86_64"
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - uses: pre-commit/action@v3.0.1
+ windows:
+ name: Windows
+ runs-on: windows-latest
+ needs: pre-commit
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel=="${{ env.MLC_CIBW_VERSION }}"
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BEFORE_ALL: ".\\scripts\\cpp_tests.bat"
+ CIBW_BUILD: ${{ env.MLC_CIBW_WIN_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ macos:
+ name: MacOS
+ runs-on: macos-latest
+ needs: pre-commit
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel==${{ env.MLC_CIBW_VERSION }}
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BEFORE_ALL: "./scripts/cpp_tests.sh"
+ CIBW_BUILD: ${{ env.MLC_CIBW_MAC_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ linux:
+ name: Linux
+ runs-on: ubuntu-latest
+ needs: pre-commit
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel==${{ env.MLC_CIBW_VERSION }}
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BEFORE_ALL: "./scripts/setup_manylinux2014.sh && ./scripts/cpp_tests.sh"
+ CIBW_BUILD: ${{ env.MLC_CIBW_LINUX_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..64fa06a6
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,168 @@
+name: Release
+
+on:
+ release:
+ types: [published]
+
+env:
+ CIBW_BUILD_VERBOSITY: 3
+ CIBW_TEST_COMMAND: "python -c \"import mlc\""
+ CIBW_SKIP: "cp313-win_amd64" # Python 3.13 is not quite ready yet
+ MLC_CIBW_VERSION: "2.20.0"
+ MLC_PYTHON_VERSION: "3.9"
+ MLC_CIBW_WIN_BUILD: "cp3*-win_amd64"
+ MLC_CIBW_MAC_BUILD: "cp3*-macosx_arm64"
+ MLC_CIBW_MAC_X86_BUILD: "cp3*-macosx_x86_64"
+ MLC_CIBW_LINUX_BUILD: "cp3*-manylinux_x86_64"
+
+jobs:
+ windows:
+ name: Windows
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel=="${{ env.MLC_CIBW_VERSION }}"
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BUILD: ${{ env.MLC_CIBW_WIN_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-windows
+ path: ./wheelhouse/*.whl
+ macos:
+ name: MacOS
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel==${{ env.MLC_CIBW_VERSION }}
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BUILD: ${{ env.MLC_CIBW_MAC_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-macos
+ path: ./wheelhouse/*.whl
+ macos-x86:
+ name: MacOS-x86
+ runs-on: macos-13
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel==${{ env.MLC_CIBW_VERSION }}
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BUILD: ${{ env.MLC_CIBW_MAC_X86_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-macos-x86
+ path: ./wheelhouse/*.whl
+ linux:
+ name: Linux
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Install cibuildwheel
+ run: python -m pip install cibuildwheel==${{ env.MLC_CIBW_VERSION }}
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BUILD: ${{ env.MLC_CIBW_LINUX_BUILD }}
+ - name: Show package contents
+ run: python scripts/show_wheel_content.py wheelhouse
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-linux
+ path: ./wheelhouse/*.whl
+ publish:
+ name: Publish
+ runs-on: ubuntu-latest
+ needs: [windows, macos, linux, macos-x86]
+ environment:
+ name: pypi
+ url: https://pypi.org/p/mlc-python
+ permissions:
+ id-token: write
+ contents: write
+ steps:
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MLC_PYTHON_VERSION }}
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: ./wheelhouse
+ - name: Prepare distribution files
+ run: |
+ mkdir -p dist
+ mv wheelhouse/wheels-macos-x86/*.whl dist/
+ mv wheelhouse/wheels-macos/*.whl dist/
+ mv wheelhouse/wheels-linux/*.whl dist/
+ mv wheelhouse/wheels-windows/*.whl dist/
+ - name: Upload wheels to release
+ env:
+ GITHUB_TOKEN: ${{ secrets.PYMLC_GITHUB_TOKEN }}
+ run: |
+ # Get the release ID
+ release_id=$(gh api repos/${{ github.repository }}/releases/tags/${{ github.ref_name }} --jq '.id')
+ if [ -z "$release_id" ]; then
+ echo "Error: Could not find release with tag ${{ github.ref_name }}"
+ exit 1
+ fi
+ upload_url=https://uploads.github.com/repos/${{ github.repository }}/releases/$release_id/assets
+ echo "Uploading to release ID: $release_id. Upload URL: $upload_url"
+ for wheel in dist/*.whl; do
+ echo "⏫ Uploading $(basename $wheel)"
+ response=$(curl -s -w "%{http_code}" -X POST \
+ -H "Authorization: token $GITHUB_TOKEN" \
+ -H "Content-Type: application/octet-stream" \
+ --data-binary @"$wheel" \
+ "$upload_url?name=$(basename $wheel)")
+ http_code=${response: -3}
+ if [ $http_code -eq 201 ]; then
+ echo "🎉 Successfully uploaded $(basename $wheel)"
+ else
+ echo "❌ Failed to upload $(basename $wheel). HTTP status: $http_code. Error response: ${response%???}"
+ exit 1
+ fi
+ done
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: dist/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..54e90205
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.vscode
+*.dSYM
+build
+build-cpp-tests
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..28b086b9
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "3rdparty/dlpack"]
+ path = 3rdparty/dlpack
+ url = https://github.com/dmlc/dlpack.git
+[submodule "3rdparty/libbacktrace"]
+ path = 3rdparty/libbacktrace
+ url = https://github.com/ianlancetaylor/libbacktrace
+[submodule "3rdparty/googletest"]
+ path = 3rdparty/googletest
+ url = https://github.com/google/googletest.git
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..bf1659c0
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,54 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: mixed-line-ending
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-toml
+ - id: check-added-large-files
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.4.7
+ hooks:
+ - id: ruff
+ types_or: [python, pyi, jupyter]
+ args: [--fix]
+ - id: ruff-format
+ types_or: [python, pyi, jupyter]
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: "v1.11.1"
+ hooks:
+ - id: mypy
+ additional_dependencies: ['numpy >= 1.22', "ml-dtypes >= 0.1", "pytest"]
+ args: [--show-error-codes]
+ - repo: https://github.com/pre-commit/mirrors-clang-format
+ rev: "v18.1.5"
+ hooks:
+ - id: clang-format
+ - repo: https://github.com/MarcoGorelli/cython-lint
+ rev: v0.16.2
+ hooks:
+ - id: cython-lint
+ - id: double-quote-cython-strings
+ - repo: https://github.com/scop/pre-commit-shfmt
+ rev: v3.8.0-1
+ hooks:
+ - id: shfmt
+ - repo: https://github.com/shellcheck-py/shellcheck-py
+ rev: v0.10.0.1
+ hooks:
+ - id: shellcheck
+ # - repo: https://github.com/cheshirekow/cmake-format-precommit
+ # rev: v0.6.10
+ # hooks:
+ # - id: cmake-format
+ # - id: cmake-lint
+ - repo: https://github.com/compilerla/conventional-pre-commit
+ rev: v2.1.1
+ hooks:
+ - id: conventional-pre-commit
+ stages: [commit-msg]
+ args: [feat, fix, ci, chore, test]
diff --git a/3rdparty/dlpack b/3rdparty/dlpack
new file mode 160000
index 00000000..bbd2f4d3
--- /dev/null
+++ b/3rdparty/dlpack
@@ -0,0 +1 @@
+Subproject commit bbd2f4d32427e548797929af08cfe2a9cbb3cf12
diff --git a/3rdparty/googletest b/3rdparty/googletest
new file mode 160000
index 00000000..f8d7d77c
--- /dev/null
+++ b/3rdparty/googletest
@@ -0,0 +1 @@
+Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571
diff --git a/3rdparty/libbacktrace b/3rdparty/libbacktrace
new file mode 160000
index 00000000..febbb9bf
--- /dev/null
+++ b/3rdparty/libbacktrace
@@ -0,0 +1 @@
+Subproject commit febbb9bff98b39ee596aa15d1f58e4bba442cd6a
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..961ccd48
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,107 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(
+ mlc
+ VERSION 0.0.14
+ DESCRIPTION "MLC-Python"
+ LANGUAGES C CXX
+)
+
+option(MLC_BUILD_TESTS "Build tests. This option will enable a test target `mlc_tests`." OFF)
+option(MLC_BUILD_PY "Build Python bindings." OFF)
+option(MLC_BUILD_REGISTRY
+ "Support for objects with non-static type indices. When turned on, \
+ targets linked against `mlc` will allow objects that comes with non-pre-defined type indices, \
+ so that the object hierarchy could expand without limitation. \
+ This will require the downstream targets to link against target `mlc_registry` to be effective."
+ OFF
+)
+
+include(cmake/Utils/CxxWarning.cmake)
+include(cmake/Utils/Sanitizer.cmake)
+include(cmake/Utils/Library.cmake)
+include(cmake/Utils/AddLibbacktrace.cmake)
+include(cmake/Utils/DebugSymbol.cmake)
+
+########## Target: `dlpack_header` ##########
+
+add_library(dlpack_header INTERFACE)
+target_include_directories(dlpack_header INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/dlpack/include")
+
+########## Target: `mlc` ##########
+
+add_library(mlc INTERFACE)
+target_link_libraries(mlc INTERFACE dlpack_header)
+target_compile_features(mlc INTERFACE cxx_std_17)
+target_include_directories(mlc INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
+
+########## Target: `mlc_registry` ##########
+
+if (MLC_BUILD_REGISTRY)
+ add_library(mlc_registry_objs OBJECT
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/c_api.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/c_api_tests.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/printer.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/json.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/structure.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/traceback.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cpp/traceback_win.cc"
+ )
+ set_target_properties(
+ mlc_registry_objs PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ CXX_STANDARD 17
+ CXX_EXTENSIONS OFF
+ CXX_STANDARD_REQUIRED ON
+ CXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN ON
+ PREFIX "lib"
+ )
+ add_cxx_warning(mlc_registry_objs)
+ target_link_libraries(mlc_registry_objs PRIVATE dlpack_header)
+ target_include_directories(mlc_registry_objs PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
+ add_target_from_obj(mlc_registry mlc_registry_objs)
+ if (TARGET libbacktrace)
+ target_link_libraries(mlc_registry_objs PRIVATE libbacktrace)
+ target_link_libraries(mlc_registry_shared PRIVATE libbacktrace)
+ target_link_libraries(mlc_registry_static PRIVATE libbacktrace)
+ add_debug_symbol_apple(mlc_registry_shared "lib/")
+ endif ()
+ install(TARGETS mlc_registry_static DESTINATION "lib/")
+ install(TARGETS mlc_registry_shared DESTINATION "lib/")
+endif (MLC_BUILD_REGISTRY)
+
+########## Target: `mlc_py` ##########
+
+if (MLC_BUILD_PY)
+ find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
+ file(GLOB _cython_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python/mlc/_cython/*.pyx")
+ set(_cython_outputs "")
+ foreach(_in IN LISTS _cython_sources)
+ get_filename_component(_file ${_in} NAME_WLE)
+ set(_out "${CMAKE_BINARY_DIR}/_cython/${_file}_cython.cc")
+ message(STATUS "Cythonize: ${_in} -> ${_out}")
+ add_custom_command(
+ OUTPUT "${_out}" DEPENDS "${_in}"
+ COMMENT "Making `${_out}` from `${_in}"
+ COMMAND Python::Interpreter -m cython "${_in}" --output-file "${_out}" --cplus
+ VERBATIM
+ )
+ list(APPEND _cython_outputs "${_out}")
+ endforeach()
+ Python_add_library(mlc_py MODULE ${_cython_outputs} WITH_SOABI)
+ set_target_properties(mlc_py PROPERTIES OUTPUT_NAME "core")
+ target_link_libraries(mlc_py PUBLIC mlc)
+ install(TARGETS mlc_py DESTINATION _cython/)
+endif (MLC_BUILD_PY)
+
+########## Adding tests ##########
+
+if (${PROJECT_NAME} STREQUAL ${CMAKE_PROJECT_NAME})
+ if (MLC_BUILD_TESTS)
+ enable_testing()
+ message(STATUS "Enable Testing")
+ include(cmake/Utils/AddGoogleTest.cmake)
+ add_subdirectory(tests/cpp/)
+ endif()
+endif ()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..a9fdd74f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,184 @@
+
+
+
+ MLC-Python
+
+
+* [:inbox_tray: Installation](#inbox_tray-installation)
+* [:key: Key Features](#key-key-features)
+ + [:building_construction: MLC Dataclass](#building_construction-mlc-dataclass)
+ + [:dart: Structure-Aware Tooling](#dart-structure-aware-tooling)
+ + [:snake: Text Formats in Python](#snake-text-formats-in-python)
+ + [:zap: Zero-Copy Interoperability with C++ Plugins](#zap-zero-copy-interoperability-with-c-plugins)
+* [:fuelpump: Development](#fuelpump-development)
+ + [:gear: Editable Build](#gear-editable-build)
+ + [:ferris_wheel: Create Wheels](#ferris_wheel-create-wheels)
+
+
+MLC is a Python-first toolkit that streamlines the development of AI compilers, runtimes, and compound AI systems with its Pythonic dataclasses, structure-aware tooling, and Python-based text formats.
+
+Beyond pure Python, MLC natively supports zero-copy interoperation with C++ plugins, and enables a smooth engineering practice transitioning from Python to hybrid or Python-free development.
+
+## :inbox_tray: Installation
+
+```bash
+pip install -U mlc-python
+```
+
+## :key: Key Features
+
+### :building_construction: MLC Dataclass
+
+MLC dataclass is similar to Python’s native dataclass:
+
+```python
+import mlc.dataclasses as mlcd
+
+@mlcd.py_class("demo.MyClass")
+class MyClass(mlcd.PyClass):
+ a: int
+ b: str
+ c: float | None
+
+instance = MyClass(12, "test", c=None)
+```
+
+**Type safety**. MLC dataclass enforces strict type checking using Cython and C++.
+
+```python
+>>> instance.c = 10; print(instance)
+demo.MyClass(a=12, b='test', c=10.0)
+
+>>> instance.c = "wrong type"
+TypeError: must be real number, not str
+
+>>> instance.non_exist = 1
+AttributeError: 'MyClass' object has no attribute 'non_exist' and no __dict__ for setting new attributes
+```
+
+**Serialization**. MLC dataclasses are picklable and JSON-serializable.
+
+```python
+>>> MyClass.from_json(instance.json())
+demo.MyClass(a=12, b='test', c=None)
+
+>>> import pickle; pickle.loads(pickle.dumps(instance))
+demo.MyClass(a=12, b='test', c=None)
+```
+
+### :dart: Structure-Aware Tooling
+
+An extra `structure` field are used to specify a dataclass's structure, indicating def site and scoping in an IR.
+
+ Define a toy IR with `structure`.
+
+```python
+import mlc.dataclasses as mlcd
+
+@mlcd.py_class
+class Expr(mlcd.PyClass):
+ def __add__(self, other):
+ return Add(a=self, b=other)
+
+@mlcd.py_class(structure="nobind")
+class Add(Expr):
+ a: Expr
+ b: Expr
+
+@mlcd.py_class(structure="var")
+class Var(Expr):
+ name: str = mlcd.field(structure=None) # excludes `name` from defined structure
+
+@mlcd.py_class(structure="bind")
+class Let(Expr):
+ rhs: Expr
+ lhs: Var = mlcd.field(structure="bind") # `Let.lhs` is the def-site
+ body: Expr
+```
+
+
+
+**Structural equality**. Member method `eq_s` compares the structural equality (alpha equivalence) of two IRs represented by MLC's structured dataclass.
+
+```python
+>>> x, y, z = Var("x"), Var("y"), Var("z")
+>>> L1 = Let(rhs=x + y, lhs=z, body=z) # let z = x + y; z
+>>> L2 = Let(rhs=y + z, lhs=x, body=x) # let x = y + z; x
+>>> L3 = Let(rhs=x + x, lhs=z, body=z) # let z = x + x; z
+>>> L1.eq_s(L2)
+True
+>>> L1.eq_s(L3, assert_mode=True)
+ValueError: Structural equality check failed at {root}.rhs.b: Inconsistent binding. RHS has been bound to a different node while LHS is not bound
+```
+
+**Structural hashing**. The structure of MLC dataclasses can be hashed via `hash_s`, which guarantees if two dataclasses are alpha-equivalent, they will share the same structural hash:
+
+```python
+>>> L1_hash, L2_hash, L3_hash = L1.hash_s(), L2.hash_s(), L3.hash_s()
+>>> assert L1_hash == L2_hash
+>>> assert L1_hash != L3_hash
+```
+
+### :snake: Text Formats in Python
+
+**Printer.** MLC converts an IR node to Python AST by looking up the `__ir_print__` method.
+
+**[[Example](https://github.com/mlc-ai/mlc-python/blob/main/python/mlc/testing/toy_ir/ir.py)]**. Copy the toy IR definition to REPL and then create a `Func` node below:
+
+```python
+>>> a, b, c, d, e = Var("a"), Var("b"), Var("c"), Var("d"), Var("e")
+>>> f = Func("f", [a, b, c],
+ stmts=[
+ Assign(lhs=d, rhs=Add(a, b)), # d = a + b
+ Assign(lhs=e, rhs=Add(d, c)), # e = d + c
+ ],
+ ret=e)
+```
+
+- Method `mlc.printer.to_python` converts an IR node to Python-based text;
+
+```python
+>>> print(mlcp.to_python(f)) # Stringify to Python
+def f(a, b, c):
+ d = a + b
+ e = d + c
+ return e
+```
+
+- Method `mlc.printer.print_python` further renders the text with proper syntax highlighting. [[Screenshot](https://raw.githubusercontent.com/gist/potatomashed/5a9b20edbdde1b9a91a360baa6bce9ff/raw/3c68031eaba0620a93add270f8ad7ed2c8724a78/mlc-python-printer.svg)]
+
+```python
+>>> mlcp.print_python(f) # Syntax highlighting
+```
+
+**AST Parser.** MLC has a concise set of APIs for implementing parser with Python's AST module, including:
+- Inspection API that obtains source code of a Python class or function and the variables they capture;
+- Variable management APIs that help with proper scoping;
+- AST fragment evaluation APIs;
+- Error rendering APIs.
+
+**[[Example](https://github.com/mlc-ai/mlc-python/blob/main/python/mlc/testing/toy_ir/parser.py)]**. With MLC APIs, a parser can be implemented with 100 lines of code for the Python text format above defined by `__ir_printer__`.
+
+### :zap: Zero-Copy Interoperability with C++ Plugins
+
+🚧 Under construction.
+
+## :fuelpump: Development
+
+### :gear: Editable Build
+
+```bash
+pip install --verbose --editable ".[dev]"
+pre-commit install
+```
+
+### :ferris_wheel: Create Wheels
+
+This project uses `cibuildwheel` to build cross-platform wheels. See `.github/workflows/wheels.ym` for more details.
+
+```bash
+export CIBW_BUILD_VERBOSITY=3
+export CIBW_BUILD="cp3*-manylinux_x86_64"
+python -m pip install pipx
+pipx run cibuildwheel==2.20.0 --output-dir wheelhouse
+```
diff --git a/cmake/Utils/AddGoogleTest.cmake b/cmake/Utils/AddGoogleTest.cmake
new file mode 100644
index 00000000..f2e52643
--- /dev/null
+++ b/cmake/Utils/AddGoogleTest.cmake
@@ -0,0 +1,43 @@
+set(gtest_force_shared_crt ON CACHE BOOL "Always use msvcrt.dll" FORCE)
+set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)
+set(BUILD_GTEST ON CACHE BOOL "" FORCE)
+set(GOOGLETEST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/googletest)
+
+add_subdirectory(${GOOGLETEST_ROOT})
+include(GoogleTest)
+
+set_target_properties(gtest gtest_main
+ PROPERTIES
+ EXPORT_COMPILE_COMMANDS OFF
+ EXCLUDE_FROM_ALL ON
+ FOLDER 3rdparty
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+)
+
+# Install gtest and gtest_main
+install(TARGETS gtest gtest_main DESTINATION "lib/")
+
+# Mark advanced variables
+mark_as_advanced(
+ BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS
+ gmock_build_tests gtest_build_samples gtest_build_tests
+ gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols
+)
+
+macro(add_googletest target_name)
+ add_test(
+ NAME ${target_name}
+ COMMAND ${target_name}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ target_link_libraries(${target_name} PRIVATE gtest_main)
+ gtest_discover_tests(${target_name}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DISCOVERY_MODE PRE_TEST
+ PROPERTIES
+ VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ )
+ set_target_properties(${target_name} PROPERTIES FOLDER tests)
+endmacro()
diff --git a/cmake/Utils/AddLibbacktrace.cmake b/cmake/Utils/AddLibbacktrace.cmake
new file mode 100644
index 00000000..c9697538
--- /dev/null
+++ b/cmake/Utils/AddLibbacktrace.cmake
@@ -0,0 +1,50 @@
+include(ExternalProject)
+
+function(_libbacktrace_compile)
+ set(_libbacktrace_source ${CMAKE_CURRENT_LIST_DIR}/../../3rdparty/libbacktrace)
+ set(_libbacktrace_prefix ${CMAKE_CURRENT_BINARY_DIR}/libbacktrace)
+ if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND (CMAKE_C_COMPILER MATCHES "^/Library" OR CMAKE_C_COMPILER MATCHES "^/Applications"))
+ set(_cmake_c_compiler "/usr/bin/cc")
+ else()
+ set(_cmake_c_compiler "${CMAKE_C_COMPILER}")
+ endif()
+
+ file(MAKE_DIRECTORY ${_libbacktrace_prefix}/include)
+ file(MAKE_DIRECTORY ${_libbacktrace_prefix}/lib)
+ ExternalProject_Add(project_libbacktrace
+ PREFIX libbacktrace
+ SOURCE_DIR ${_libbacktrace_source}
+ BINARY_DIR ${_libbacktrace_prefix}
+ CONFIGURE_COMMAND
+ ${_libbacktrace_source}/configure
+ "--prefix=${_libbacktrace_prefix}"
+ "--with-pic"
+ "CC=${_cmake_c_compiler}"
+ "CPP=${_cmake_c_compiler} -E"
+ "CFLAGS=${CMAKE_C_FLAGS}"
+ "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}"
+ "NM=${CMAKE_NM}"
+ "STRIP=${CMAKE_STRIP}"
+ "--host=${MACHINE_NAME}"
+ BUILD_COMMAND make -j
+ BUILD_BYPRODUCTS ${_libbacktrace_prefix}/lib/libbacktrace.a ${_libbacktrace_prefix}/include/backtrace.h
+ INSTALL_DIR ${_libbacktrace_prefix}
+ INSTALL_COMMAND make install
+ LOG_CONFIGURE ON
+ LOG_INSTALL ON
+ LOG_BUILD ON
+ LOG_OUTPUT_ON_FAILURE ON
+ )
+ ExternalProject_Add_Step(project_libbacktrace checkout DEPENDERS configure DEPENDEES download)
+ set_target_properties(project_libbacktrace PROPERTIES EXCLUDE_FROM_ALL TRUE)
+ add_library(libbacktrace STATIC IMPORTED)
+ add_dependencies(libbacktrace project_libbacktrace)
+ set_target_properties(libbacktrace PROPERTIES
+ IMPORTED_LOCATION ${_libbacktrace_prefix}/lib/libbacktrace.a
+ INTERFACE_INCLUDE_DIRECTORIES ${_libbacktrace_prefix}/include
+ )
+endfunction()
+
+if(NOT MSVC)
+ _libbacktrace_compile()
+endif()
diff --git a/cmake/Utils/CxxWarning.cmake b/cmake/Utils/CxxWarning.cmake
new file mode 100644
index 00000000..50ee5b61
--- /dev/null
+++ b/cmake/Utils/CxxWarning.cmake
@@ -0,0 +1,13 @@
+function(add_cxx_warning target_name)
+ # GNU, Clang, or AppleClang
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
+ target_compile_options(${target_name} PRIVATE "-Werror" "-Wall" "-Wextra" "-Wpedantic")
+ return()
+ endif()
+ # MSVC
+ if(MSVC)
+ target_compile_options(${target_name} PRIVATE "/W4" "/WX")
+ return()
+ endif()
+ message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}")
+endfunction()
diff --git a/cmake/Utils/DebugSymbol.cmake b/cmake/Utils/DebugSymbol.cmake
new file mode 100644
index 00000000..7f5bbae2
--- /dev/null
+++ b/cmake/Utils/DebugSymbol.cmake
@@ -0,0 +1,14 @@
+if(APPLE)
+ find_program(DSYMUTIL_PROGRAM dsymutil)
+ mark_as_advanced(DSYMUTIL_PROGRAM)
+endif()
+
+function(add_debug_symbol_apple _target _directory)
+ if (APPLE)
+ add_custom_command(TARGET ${_target} POST_BUILD
+ COMMAND ${DSYMUTIL_PROGRAM} ARGS $
+ COMMENT "Running dsymutil" VERBATIM
+ )
+ install(FILES $.dSYM DESTINATION ${_directory})
+ endif (APPLE)
+endfunction()
diff --git a/cmake/Utils/Library.cmake b/cmake/Utils/Library.cmake
new file mode 100644
index 00000000..26bdcef6
--- /dev/null
+++ b/cmake/Utils/Library.cmake
@@ -0,0 +1,28 @@
+function(add_target_from_obj target_name obj_target_name)
+ add_library(${target_name}_static STATIC $)
+ set_target_properties(
+ ${target_name}_static PROPERTIES
+ OUTPUT_NAME "${target_name}_static"
+ PREFIX "lib"
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ )
+ add_library(${target_name}_shared SHARED $)
+ set_target_properties(
+ ${target_name}_shared PROPERTIES
+ OUTPUT_NAME "${target_name}"
+ PREFIX "lib"
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ )
+ add_custom_target(${target_name})
+ add_dependencies(${target_name} ${target_name}_static ${target_name}_shared)
+ if (MSVC)
+ target_compile_definitions(${obj_target_name} PRIVATE MLC_EXPORTS)
+ set_target_properties(
+ ${obj_target_name} ${target_name}_shared ${target_name}_static
+ PROPERTIES
+ MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL"
+ )
+ endif()
+endfunction()
diff --git a/cmake/Utils/Sanitizer.cmake b/cmake/Utils/Sanitizer.cmake
new file mode 100644
index 00000000..c1facc19
--- /dev/null
+++ b/cmake/Utils/Sanitizer.cmake
@@ -0,0 +1,18 @@
+function(add_sanitizer_address target_name)
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
+ include(CheckCXXCompilerFlag)
+ set (_saved_CRF ${CMAKE_REQUIRED_FLAGS})
+ set(CMAKE_REQUIRED_FLAGS "-fsanitize=address")
+ check_cxx_source_compiles("int main() { return 0; }" COMPILER_SUPPORTS_ASAN)
+ set (CMAKE_REQUIRED_FLAGS ${_saved_CRF})
+ get_target_property(_saved_type ${target_name} TYPE)
+ if (${_saved_type} STREQUAL "INTERFACE_LIBRARY")
+ set(_saved_type INTERFACE)
+ else()
+ set(_saved_type PRIVATE)
+ endif()
+ target_link_options(${target_name} ${_saved_type} "-fsanitize=address")
+ target_compile_options(${target_name} ${_saved_type} "-fsanitize=address" "-fno-omit-frame-pointer" "-g")
+ return()
+ endif()
+endfunction()
diff --git a/cpp/c_api.cc b/cpp/c_api.cc
new file mode 100644
index 00000000..4b4f6651
--- /dev/null
+++ b/cpp/c_api.cc
@@ -0,0 +1,187 @@
+#include "./registry.h"
+#include
+
+namespace mlc {
+namespace registry {
+TypeTable *TypeTable::Global() {
+ static TypeTable *instance = TypeTable::New();
+ return instance;
+}
+} // namespace registry
+} // namespace mlc
+
+using ::mlc::Any;
+using ::mlc::AnyView;
+using ::mlc::ErrorObj;
+using ::mlc::FuncObj;
+using ::mlc::Ref;
+using ::mlc::registry::TypeTable;
+
+namespace {
+thread_local Any last_error;
+MLC_REGISTER_FUNC("mlc.ffi.LoadDSO").set_body([](std::string name) { TypeTable::Get(nullptr)->LoadDSO(name); });
+} // namespace
+
+MLC_API MLCAny MLCGetLastError() {
+ MLCAny ret;
+ *static_cast(&ret) = std::move(last_error);
+ return ret;
+}
+
+MLC_API int32_t MLCTypeRegister(MLCTypeTableHandle _self, int32_t parent_type_index, const char *type_key,
+ int32_t type_index, MLCTypeInfo **out_type_info) {
+ MLC_SAFE_CALL_BEGIN();
+ *out_type_info = TypeTable::Get(_self)->TypeRegister(parent_type_index, type_index, type_key);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCTypeIndex2Info(MLCTypeTableHandle _self, int32_t type_index, MLCTypeInfo **ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *ret = TypeTable::Get(_self)->GetTypeInfo(type_index);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCTypeKey2Info(MLCTypeTableHandle _self, const char *type_key, MLCTypeInfo **ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *ret = TypeTable::Get(_self)->GetTypeInfo(type_key);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCTypeRegisterFields(MLCTypeTableHandle _self, int32_t type_index, int64_t num_fields,
+ MLCTypeField *fields) {
+ MLC_SAFE_CALL_BEGIN();
+ TypeTable::Get(_self)->SetFields(type_index, num_fields, fields);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCTypeRegisterStructure(MLCTypeTableHandle _self, int32_t type_index, int32_t structure_kind,
+ int64_t num_sub_structures, int32_t *sub_structure_indices,
+ int32_t *sub_structure_kinds) {
+ MLC_SAFE_CALL_BEGIN();
+ TypeTable::Get(_self)->SetStructure(type_index, structure_kind, num_sub_structures, sub_structure_indices,
+ sub_structure_kinds);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCTypeAddMethod(MLCTypeTableHandle _self, int32_t type_index, MLCTypeMethod method) {
+ MLC_SAFE_CALL_BEGIN();
+ TypeTable::Get(_self)->AddMethod(type_index, method);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCVTableGetGlobal(MLCTypeTableHandle _self, const char *key, MLCVTableHandle *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *ret = TypeTable::Get(_self)->GetGlobalVTable(key);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCVTableGetFunc(MLCVTableHandle vtable, int32_t type_index, int32_t allow_ancestor, MLCAny *ret) {
+ using ::mlc::registry::VTable;
+ MLC_SAFE_CALL_BEGIN();
+ *static_cast(ret) = static_cast(vtable)->GetFunc(type_index, allow_ancestor);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCVTableSetFunc(MLCVTableHandle vtable, int32_t type_index, MLCFunc *func, int32_t override_mode) {
+ using ::mlc::registry::VTable;
+ MLC_SAFE_CALL_BEGIN();
+ static_cast(vtable)->Set(type_index, static_cast(func), override_mode);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCDynTypeTypeTableCreate(MLCTypeTableHandle *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *ret = TypeTable::New();
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCDynTypeTypeTableDestroy(MLCTypeTableHandle handle) {
+ MLC_SAFE_CALL_BEGIN();
+ delete static_cast(handle);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCAnyIncRef(MLCAny *any) {
+ MLC_SAFE_CALL_BEGIN();
+ if (!::mlc::base::IsTypeIndexPOD(any->type_index)) {
+ ::mlc::base::IncRef(any->v.v_obj);
+ }
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCAnyDecRef(MLCAny *any) {
+ MLC_SAFE_CALL_BEGIN();
+ if (!::mlc::base::IsTypeIndexPOD(any->type_index)) {
+ ::mlc::base::DecRef(any->v.v_obj);
+ }
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCAnyInplaceViewToOwned(MLCAny *any) {
+ MLC_SAFE_CALL_BEGIN();
+ MLCAny tmp{};
+ static_cast(tmp) = static_cast(*any);
+ *any = tmp;
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCFuncSetGlobal(MLCTypeTableHandle _self, const char *name, MLCAny func, int allow_override) {
+ MLC_SAFE_CALL_BEGIN();
+ TypeTable::Get(_self)->SetFunc(name, static_cast(func), allow_override);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCFuncGetGlobal(MLCTypeTableHandle _self, const char *name, MLCAny *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *static_cast(ret) = TypeTable::Get(_self)->GetFunc(name);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCFuncSafeCall(MLCFunc *func, int32_t num_args, MLCAny *args, MLCAny *ret) {
+#if defined(__APPLE__)
+ if (func->_mlc_header.type_index != MLCTypeIndex::kMLCFunc) {
+ std::cout << "func->type_index: " << func->_mlc_header.type_index << std::endl;
+ }
+#endif
+ return func->safe_call(func, num_args, args, ret);
+}
+
+MLC_API int32_t MLCFuncCreate(void *self, MLCDeleterType deleter, MLCFuncSafeCallType safe_call, MLCAny *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *static_cast(ret) = FuncObj::FromForeign(self, deleter, safe_call);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCErrorCreate(const char *kind, int64_t num_bytes, const char *bytes, MLCAny *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *static_cast(ret) = Ref::New(kind, num_bytes, bytes);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCErrorGetInfo(MLCAny error, int32_t *num_strs, const char ***strs) {
+ MLC_SAFE_CALL_BEGIN();
+ thread_local std::vector ret;
+ static_cast(error).operator Ref()->GetInfo(&ret);
+ *num_strs = static_cast(ret.size());
+ *strs = ret.data();
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t MLCExtObjCreate(int32_t num_bytes, int32_t type_index, MLCAny *ret) {
+ MLC_SAFE_CALL_BEGIN();
+ *static_cast(ret) = mlc::AllocExternObject(type_index, num_bytes);
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API int32_t _MLCExtObjDeleteImpl(void *objptr) {
+ MLC_SAFE_CALL_BEGIN();
+ ::mlc::core::DeleteExternObject(static_cast<::mlc::Object *>(objptr));
+ MLC_SAFE_CALL_END(&last_error);
+}
+
+MLC_API void MLCExtObjDelete(void *objptr) {
+ if (int32_t error_code = _MLCExtObjDeleteImpl(objptr)) {
+ std::cerr << "Error code (" << error_code << ") when deleting external object: " << last_error << std::endl;
+ std::abort();
+ }
+}
diff --git a/cpp/c_api_tests.cc b/cpp/c_api_tests.cc
new file mode 100644
index 00000000..838e88c5
--- /dev/null
+++ b/cpp/c_api_tests.cc
@@ -0,0 +1,195 @@
+#include
+
+namespace mlc {
+namespace {
+
+/**************** FFI ****************/
+
+MLC_REGISTER_FUNC("mlc.testing.cxx_none").set_body([]() -> void { return; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_null").set_body([]() -> void * { return nullptr; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_int").set_body([](int x) -> int { return x; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_float").set_body([](double x) -> double { return x; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_ptr").set_body([](void *x) -> void * { return x; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_dtype").set_body([](DLDataType x) { return x; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_device").set_body([](DLDevice x) { return x; });
+MLC_REGISTER_FUNC("mlc.testing.cxx_raw_str").set_body([](const char *x) { return x; });
+
+/**************** Reflection ****************/
+
+struct TestingCClassObj : public Object {
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ int64_t i64;
+ float f32;
+ double f64;
+ void *raw_ptr;
+ DLDataType dtype;
+ DLDevice device;
+ Any any;
+ Func func;
+ UList ulist;
+ UDict udict;
+ Str str_;
+ Str str_readonly;
+
+ List list_any;
+ List> list_list_int;
+ Dict dict_any_any;
+ Dict dict_str_any;
+ Dict dict_any_str;
+ Dict> dict_str_list_int;
+
+ Optional opt_i64;
+ Optional opt_f64;
+ Optional opt_raw_ptr;
+ Optional opt_dtype;
+ Optional opt_device;
+ Optional opt_func;
+ Optional opt_ulist;
+ Optional opt_udict;
+ Optional opt_str;
+
+ Optional> opt_list_any;
+ Optional>> opt_list_list_int;
+ Optional> opt_dict_any_any;
+ Optional> opt_dict_str_any;
+ Optional> opt_dict_any_str;
+ Optional>> opt_dict_str_list_int;
+
+ explicit TestingCClassObj(int8_t i8, int16_t i16, int32_t i32, int64_t i64, float f32, double f64, void *raw_ptr,
+ DLDataType dtype, DLDevice device, Any any, Func func, UList ulist, UDict udict, Str str_,
+ Str str_readonly, List list_any, List> list_list_int,
+ Dict dict_any_any, Dict dict_str_any, Dict dict_any_str,
+ Dict> dict_str_list_int, Optional opt_i64, Optional opt_f64,
+ Optional opt_raw_ptr, Optional opt_dtype, Optional opt_device,
+ Optional opt_func, Optional opt_ulist, Optional opt_udict,
+ Optional opt_str, Optional> opt_list_any,
+ Optional>> opt_list_list_int, Optional> opt_dict_any_any,
+ Optional> opt_dict_str_any, Optional> opt_dict_any_str,
+ Optional>> opt_dict_str_list_int)
+ : i8(i8), i16(i16), i32(i32), i64(i64), f32(f32), f64(f64), raw_ptr(raw_ptr), dtype(dtype), device(device),
+ any(any), func(func), ulist(ulist), udict(udict), str_(str_), str_readonly(str_readonly), list_any(list_any),
+ list_list_int(list_list_int), dict_any_any(dict_any_any), dict_str_any(dict_str_any),
+ dict_any_str(dict_any_str), dict_str_list_int(dict_str_list_int), opt_i64(opt_i64), opt_f64(opt_f64),
+ opt_raw_ptr(opt_raw_ptr), opt_dtype(opt_dtype), opt_device(opt_device), opt_func(opt_func),
+ opt_ulist(opt_ulist), opt_udict(opt_udict), opt_str(opt_str), opt_list_any(opt_list_any),
+ opt_list_list_int(opt_list_list_int), opt_dict_any_any(opt_dict_any_any), opt_dict_str_any(opt_dict_str_any),
+ opt_dict_any_str(opt_dict_any_str), opt_dict_str_list_int(opt_dict_str_list_int) {}
+
+ int64_t i64_plus_one() const { return i64 + 1; }
+
+ MLC_DEF_DYN_TYPE(TestingCClassObj, Object, "mlc.testing.c_class");
+};
+
+struct TestingCClass : public ObjectRef {
+ MLC_DEF_OBJ_REF(TestingCClass, TestingCClassObj, ObjectRef)
+ .Field("i8", &TestingCClassObj::i8)
+ .Field("i16", &TestingCClassObj::i16)
+ .Field("i32", &TestingCClassObj::i32)
+ .Field("i64", &TestingCClassObj::i64)
+ .Field("f32", &TestingCClassObj::f32)
+ .Field("f64", &TestingCClassObj::f64)
+ .Field("raw_ptr", &TestingCClassObj::raw_ptr)
+ .Field("dtype", &TestingCClassObj::dtype)
+ .Field("device", &TestingCClassObj::device)
+ .Field("any", &TestingCClassObj::any)
+ .Field("func", &TestingCClassObj::func)
+ .Field("ulist", &TestingCClassObj::ulist)
+ .Field("udict", &TestingCClassObj::udict)
+ .Field("str_", &TestingCClassObj::str_)
+ .FieldReadOnly("str_readonly", &TestingCClassObj::str_readonly)
+ .Field("list_any", &TestingCClassObj::list_any)
+ .Field("list_list_int", &TestingCClassObj::list_list_int)
+ .Field("dict_any_any", &TestingCClassObj::dict_any_any)
+ .Field("dict_str_any", &TestingCClassObj::dict_str_any)
+ .Field("dict_any_str", &TestingCClassObj::dict_any_str)
+ .Field("dict_str_list_int", &TestingCClassObj::dict_str_list_int)
+ .Field("opt_i64", &TestingCClassObj::opt_i64)
+ .Field("opt_f64", &TestingCClassObj::opt_f64)
+ .Field("opt_raw_ptr", &TestingCClassObj::opt_raw_ptr)
+ .Field("opt_dtype", &TestingCClassObj::opt_dtype)
+ .Field("opt_device", &TestingCClassObj::opt_device)
+ .Field("opt_func", &TestingCClassObj::opt_func)
+ .Field("opt_ulist", &TestingCClassObj::opt_ulist)
+ .Field("opt_udict", &TestingCClassObj::opt_udict)
+ .Field("opt_str", &TestingCClassObj::opt_str)
+ .Field("opt_list_any", &TestingCClassObj::opt_list_any)
+ .Field("opt_list_list_int", &TestingCClassObj::opt_list_list_int)
+ .Field("opt_dict_any_any", &TestingCClassObj::opt_dict_any_any)
+ .Field("opt_dict_str_any", &TestingCClassObj::opt_dict_str_any)
+ .Field("opt_dict_any_str", &TestingCClassObj::opt_dict_any_str)
+ .Field("opt_dict_str_list_int", &TestingCClassObj::opt_dict_str_list_int)
+ .MemFn("i64_plus_one", &TestingCClassObj::i64_plus_one)
+ .StaticFn("__init__",
+ InitOf, List>, Dict, Dict,
+ Dict, Dict>, Optional, Optional, Optional,
+ Optional, Optional, Optional, Optional, Optional,
+ Optional, Optional>, Optional>>, Optional>,
+ Optional>, Optional>, Optional>>>);
+};
+
+/**************** Traceback ****************/
+
+MLC_REGISTER_FUNC("mlc.testing.throw_exception_from_c").set_body([]() {
+ // Simply throw an exception in C++
+ MLC_THROW(ValueError) << "This is an error message";
+});
+
+MLC_REGISTER_FUNC("mlc.testing.throw_exception_from_ffi_in_c").set_body([](FuncObj *func) {
+ // call a Python function which throws an exception
+ (*func)();
+});
+
+MLC_REGISTER_FUNC("mlc.testing.throw_exception_from_ffi").set_body([](FuncObj *func) {
+ // Call a Python function `func` which throws an exception and returns it
+ Any ret;
+ try {
+ (*func)();
+ } catch (Exception &error) {
+ ret = std::move(error.data_);
+ }
+ return ret;
+});
+
+/**************** Type checking ****************/
+
+MLC_REGISTER_FUNC("mlc.testing.nested_type_checking_list").set_body([](Str name) {
+ if (name == "list") {
+ using Type = UList;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "list[Any]") {
+ using Type = List;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "list[list[int]]") {
+ using Type = List>;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "dict") {
+ using Type = UDict;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "dict[str, Any]") {
+ using Type = Dict;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "dict[Any, str]") {
+ using Type = Dict;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "dict[Any, Any]") {
+ using Type = Dict;
+ return Func([](Type v) { return v; });
+ }
+ if (name == "dict[str, list[int]]") {
+ using Type = Dict>;
+ return Func([](Type v) { return v; });
+ }
+ MLC_UNREACHABLE();
+});
+
+} // namespace
+} // namespace mlc
diff --git a/cpp/json.cc b/cpp/json.cc
new file mode 100644
index 00000000..2af5b474
--- /dev/null
+++ b/cpp/json.cc
@@ -0,0 +1,497 @@
+#include
+#include
+#include
+#include
+
+namespace mlc {
+namespace core {
+namespace {
+
+mlc::Str Serialize(Any any);
+Any Deserialize(const char *json_str, int64_t json_str_len);
+Any JSONLoads(const char *json_str, int64_t json_str_len);
+MLC_INLINE Any Deserialize(const char *json_str) { return Deserialize(json_str, -1); }
+MLC_INLINE Any Deserialize(const Str &json_str) { return Deserialize(json_str->data(), json_str->size()); }
+MLC_INLINE Any JSONLoads(const char *json_str) { return JSONLoads(json_str, -1); }
+MLC_INLINE Any JSONLoads(const Str &json_str) { return JSONLoads(json_str->data(), json_str->size()); }
+
+inline mlc::Str Serialize(Any any) {
+ using mlc::base::TypeTraits;
+ std::vector type_keys;
+ auto get_json_type_index = [type_key2index = std::unordered_map(),
+ &type_keys](const char *type_key) mutable -> int32_t {
+ if (auto it = type_key2index.find(type_key); it != type_key2index.end()) {
+ return it->second;
+ }
+ int32_t type_index = static_cast(type_key2index.size());
+ type_key2index[type_key] = type_index;
+ type_keys.push_back(type_key);
+ return type_index;
+ };
+ using TObj2Idx = std::unordered_map