diff --git a/.github/workflows/build_test_release_eudsl.yml b/.github/workflows/build_test_release_eudsl.yml index 02753e91..2bcaa8f0 100644 --- a/.github/workflows/build_test_release_eudsl.yml +++ b/.github/workflows/build_test_release_eudsl.yml @@ -54,9 +54,6 @@ jobs: - name: "macos_arm64" runs-on: "macos-14" os: "macos" - - name: "macos_x86_64" - runs-on: "macos-13" - os: "macos" runs-on: ${{ matrix.runs-on }} @@ -126,8 +123,6 @@ jobs: # ld: warning: object file (libLLVMTableGen.a[3](Error.cpp.o)) was built for newer 'macOS' version (13.7) than being linked (10.13) if [[ "${{ matrix.runs-on }}" == "macos-14" ]]; then echo MACOSX_DEPLOYMENT_TARGET=14.0 >> $GITHUB_ENV - elif [[ "${{ matrix.runs-on }}" == "macos-13" ]]; then - echo MACOSX_DEPLOYMENT_TARGET=13.7 >> $GITHUB_ENV fi # prevent OOM on free GHA @@ -135,9 +130,18 @@ jobs: pip install cibuildwheel - - name: "Build eudsl-tblgen" + - name: "Build eudsl-llvmpy" + if: ${{ ! startsWith(matrix.os, 'windows') }} run: | + if [[ "${{ matrix.os }}" == "ubuntu" ]]; then + export CCACHE_DIR=/host/$CCACHE_DIR + fi + $python3_command -m cibuildwheel "$PWD/projects/eudsl-llvmpy" --output-dir wheelhouse + + - name: "Build eudsl-tblgen" + run: | + if [[ "${{ matrix.os }}" == "ubuntu" ]]; then export CCACHE_DIR=/host/$CCACHE_DIR fi @@ -145,7 +149,7 @@ jobs: - name: "Build eudsl-nbgen" run: | - + if [[ "${{ matrix.os }}" == "ubuntu" ]]; then export CCACHE_DIR=/host/$CCACHE_DIR fi @@ -154,7 +158,7 @@ jobs: - name: "Build eudsl-py" if: ${{ ! startsWith(matrix.os, 'windows') }} run: | - + if [[ "${{ matrix.os }}" == "ubuntu" ]]; then export CCACHE_DIR=/host/$CCACHE_DIR fi @@ -163,7 +167,7 @@ jobs: # just to/make sure total build continues to work - name: "Build all of eudsl" run: | - + pip install -r requirements.txt cmake -B $PWD/eudsl-build -S $PWD \ -DCMAKE_PREFIX_PATH=$PWD/llvm-install \ @@ -199,13 +203,12 @@ jobs: strategy: fail-fast: false matrix: - runs-on: ["ubuntu-22.04", "macos-14", "macos-13", "windows-2019"] + runs-on: ["ubuntu-22.04", "macos-14", "windows-2019"] python-version: ["3.9", "3.10", "3.11", "3.12"] include: [ {runs-on: "ubuntu-22.04", name: "ubuntu_x86_64", os: "ubuntu"}, {runs-on: "windows-2019", name: "windows_x86_64", os: "windows"}, - {runs-on: "macos-14", name: "macos_arm64", os: "macos"}, - {runs-on: "macos-13", name: "macos_x86_64", os: "macos"}, + {runs-on: "macos-14", name: "macos_arm64", os: "macos"} ] runs-on: ${{ matrix.runs-on }} @@ -255,16 +258,17 @@ jobs: strategy: fail-fast: false matrix: - runs-on: ["ubuntu-22.04", "macos-14", "macos-13", - # "windows-2019" + runs-on: [ + "ubuntu-22.04", + "macos-14" + # "windows-2019" ] python-version: ["3.9", "3.10", "3.11", "3.12"] include: [ {runs-on: "ubuntu-22.04", name: "ubuntu_x86_64", os: "ubuntu"}, # TODO(max): enable on windows by statically linking # {runs-on: "windows-2019", name: "windows_x86_64", os: "windows"}, - {runs-on: "macos-14", name: "macos_arm64", os: "macos"}, - {runs-on: "macos-13", name: "macos_x86_64", os: "macos"}, + {runs-on: "macos-14", name: "macos_arm64", os: "macos"} ] runs-on: ${{ matrix.runs-on }} @@ -303,10 +307,70 @@ jobs: export TESTS_DIR="$PWD/projects/eudsl-py/tests" python -m pytest -rA --capture=tee-sys $TESTS_DIR + test-eudsl-llvmpy: + + needs: [build-eudsl] + + strategy: + fail-fast: false + matrix: + runs-on: [ + "ubuntu-22.04", + "macos-14", + # "windows-2019" + ] + python-version: ["3.10", "3.11", "3.12"] + include: [ + {runs-on: "ubuntu-22.04", name: "ubuntu_x86_64", os: "ubuntu"}, + # TODO(max): enable on windows by statically linking + # {runs-on: "windows-2019", name: "windows_x86_64", os: "windows"}, + {runs-on: "macos-14", name: "macos_arm64", os: "macos"}, + ] + + runs-on: ${{ matrix.runs-on }} + + name: "Test eudsl-llvmpy ${{ matrix.name }}" + + defaults: + run: + shell: bash + + steps: + - name: "Check out repository" + uses: actions/checkout@v4.2.2 + with: + submodules: false + + - name: "Install Python" + uses: actions/setup-python@v4 + with: + python-version: "${{ matrix.python-version }}" + + - uses: actions/download-artifact@v4 + with: + name: eudsl_${{ matrix.name }}_artifact + path: wheelhouse + + - name: "Install eudsl-llvmpy" + run: | + + python -m pip install pytest + python -m pip install eudsl-llvmpy -f wheelhouse + + - name: "Test eudsl-llvmpy" + run: | + + export TESTS_DIR="$PWD/projects/eudsl-llvmpy/tests" + python -m pytest -rA --capture=tee-sys $TESTS_DIR + release-eudsl: if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [test-eudsl-tblgen, test-eudsl-py] + needs: [ + test-eudsl-tblgen, + test-eudsl-py, + test-eudsl-llvmpy + ] runs-on: "ubuntu-22.04" name: "Release eudsl" @@ -317,12 +381,10 @@ jobs: strategy: fail-fast: false matrix: - runs-on: ["ubuntu-22.04"] - include: [ - {runs-on: "ubuntu-22.04", name: "ubuntu_x86_64"}, - {runs-on: "ubuntu-22.04", name: "macos_arm64"}, - {runs-on: "ubuntu-22.04", name: "macos_x86_64"}, - {runs-on: "ubuntu-22.04", name: "windows_x86_64"}, + name: [ + "ubuntu_x86_64", + "macos_arm64", + "windows_x86_64" ] steps: diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index e3b16175..102c8801 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -5,6 +5,7 @@ if(NOT WIN32) add_subdirectory(eudsl-py) + add_subdirectory(eudsl-llvmpy) endif() add_subdirectory(eudsl-nbgen) add_subdirectory(eudsl-tblgen) diff --git a/projects/eudsl-llvmpy/CMakeLists.txt b/projects/eudsl-llvmpy/CMakeLists.txt new file mode 100644 index 00000000..86a4d44d --- /dev/null +++ b/projects/eudsl-llvmpy/CMakeLists.txt @@ -0,0 +1,164 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# Copyright (c) 2024. + +cmake_minimum_required(VERSION 3.29) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(LLVM_SUBPROJECT_TITLE "EUDSLLLVM") +set(EUDSLLLVM_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + message("Building ${LLVM_SUBPROJECT_TITLE} as a standalone project.") + project(${LLVM_SUBPROJECT_TITLE} CXX C) + set(EUDSLLLVM_STANDALONE_BUILD ON) +else() + enable_language(CXX C) + set(EUDSLLLVM_STANDALONE_BUILD OFF) +endif() + +find_package(Python 3.9...<3.14 REQUIRED + REQUIRED COMPONENTS Interpreter Development.Module + OPTIONAL_COMPONENTS Development.SABIModule) + +if(EUDSLLLVM_STANDALONE_BUILD) + find_package(LLVM REQUIRED CONFIG) + + message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + + set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/bin) + + list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") + function(set_install_rpath_origin target) + set(_origin_prefix "\$ORIGIN") + if (APPLE) + set(_origin_prefix "@loader_path") + endif() + set_target_properties(${target} PROPERTIES INSTALL_RPATH "${_origin_prefix}") + endfunction() + function(patch_llvm_rpath target) + cmake_parse_arguments(ARG "STANDALONE" "" "" ${ARGN}) + # hack so we can move libLLVM into the wheel + # see AddLLVM.cmake#llvm_setup_rpath + if (APPLE OR UNIX) + set(_origin_prefix "\$ORIGIN") + if (APPLE) + set(_origin_prefix "@loader_path") + endif() + if (STANDALONE) + get_target_property(_llvm_loc ${target} LOCATION) + else() + set(_llvm_loc "$") + endif() + set(_old_rpath "${_origin_prefix}/../lib${LLVM_LIBDIR_SUFFIX}") + if (APPLE) + if (EXISTS ${_llvm_loc}) + execute_process(COMMAND install_name_tool -rpath "${_old_rpath}" ${_origin_prefix} "${_llvm_loc}" ERROR_VARIABLE rpath_err) + endif() + # maybe already updated... + if (rpath_err AND NOT rpath_err MATCHES "no LC_RPATH load command with path: ${_old_rpath}") + message(FATAL_ERROR "couldn't update rpath because: ${rpath_err}") + endif() + else() + # sneaky sneaky - undocumented + if (EXISTS ${_llvm_loc}) + file(RPATH_CHANGE FILE "${_llvm_loc}" OLD_RPATH "${_old_rpath}" NEW_RPATH "${_origin_prefix}") + endif() + endif() + set_target_properties(${target} PROPERTIES INSTALL_RPATH "${_origin_prefix}") + endif() + endfunction() + + include(AddLLVM) +endif() + +include_directories(${LLVM_INCLUDE_DIRS}) +link_directories(${LLVM_BUILD_LIBRARY_DIR}) +add_definitions(${LLVM_DEFINITIONS}) + +if(NOT TARGET LLVMSupport) + message(FATAL_ERROR "LLVMSupport not found") +endif() + +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_DIR) +find_package(nanobind CONFIG REQUIRED) + +set(nanobind_options + -Wno-cast-qual + -Wno-deprecated-literal-operator + -Wno-covered-switch-default + -Wno-nested-anon-types + -Wno-zero-length-array + -Wno-c++98-compat-extra-semi + -Wno-c++20-extensions + $<$:-fexceptions -frtti> + $<$:-fexceptions -frtti> + $<$:/EHsc /GR> +) + +set(EUDSLLLVM_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/src") +include_directories(${EUDSLLLVM_BINARY_DIR}) +include_directories(${EUDSLLLVM_SRC_DIR}) + +execute_process( + COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/eudsl-llvmpy-generate.py" + ${LLVM_INCLUDE_DIRS}/llvm-c "${EUDSLLLVM_BINARY_DIR}/generated" + RESULT_VARIABLE _has_err_generate +) +if (_has_err_generate AND NOT _has_err_generate EQUAL 0) + message(FATAL_ERROR "couldn't generate sources: ${_has_err_generate}") +endif() +include_directories("${EUDSLLLVM_BINARY_DIR}/generated") +file(GLOB _gen_src CONFIGURE_DEPENDS "${EUDSLLLVM_BINARY_DIR}/generated/*.cpp") + +nanobind_add_module(eudslllvm_ext + NB_STATIC + NB_DOMAIN eudslllvm + src/eudslllvm_ext.cpp + ${_gen_src} +) +target_link_libraries(eudslllvm_ext PRIVATE LLVM LTO) +set_target_properties(eudslllvm_ext + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${EUDSLLLVM_SRC_DIR}/llvm" +) + +set(STANDALONE) +if(EUDSLLLVM_STANDALONE_BUILD OR EUDSL_STANDALONE_BUILD) + set(STANDALONE STANDALONE) +endif() +patch_llvm_rpath(LLVM ${STANDALONE}) +patch_llvm_rpath(LTO ${STANDALONE}) +set_install_rpath_origin(eudslllvm_ext) +# copy libLLVM into the ext dir for wheels +install(IMPORTED_RUNTIME_ARTIFACTS LLVM LTO LIBRARY DESTINATION llvm) + +target_compile_options(eudslllvm_ext PRIVATE ${nanobind_options}) +target_compile_options(nanobind-static PRIVATE ${nanobind_options}) + +# note WORKING_DIRECTORY +set(NB_STUBGEN_CMD "${Python_EXECUTABLE}" "-m" "nanobind.stubgen" + --module eudslllvm_ext --recursive --include-private --output-dir .) +set(NB_STUBGEN_OUTPUTS "__init__.pyi") +add_custom_command( + OUTPUT ${NB_STUBGEN_OUTPUTS} + COMMAND ${NB_STUBGEN_CMD} + WORKING_DIRECTORY "${EUDSLLLVM_SRC_DIR}/llvm" + DEPENDS eudslllvm_ext +) +add_custom_target(eudslllvm_ext_stub ALL DEPENDS ${NB_STUBGEN_OUTPUTS}) + +install(TARGETS eudslllvm_ext LIBRARY DESTINATION llvm) +install( + DIRECTORY "${EUDSLLLVM_SRC_DIR}/llvm" + DESTINATION ${CMAKE_INSTALL_PREFIX} + PATTERN "*.pyc" EXCLUDE + PATTERN "*.so" EXCLUDE + PATTERN "*.a" EXCLUDE + PATTERN "__pycache__" EXCLUDE + PATTERN ".gitignore" EXCLUDE +) diff --git a/projects/eudsl-llvmpy/eudsl-llvmpy-generate.py b/projects/eudsl-llvmpy/eudsl-llvmpy-generate.py new file mode 100644 index 00000000..288dc46e --- /dev/null +++ b/projects/eudsl-llvmpy/eudsl-llvmpy-generate.py @@ -0,0 +1,127 @@ +import argparse +import re +from pathlib import Path +from textwrap import dedent + +import litgen + + +def preprocess_code(code: str, here, header_f) -> str: + i = 0 + + def replacement(s): + nonlocal i + r = f"enum {header_f.stem}_{i} {{" + i += 1 + return r + + pattern = r"^enum\s*\{" + transformed_code = re.sub(pattern, replacement, code, flags=re.MULTILINE) + + pattern = r"typedef\s+enum\s*\{(.*?)\}\s*([^;]+)\s*;" + replacement = r"enum \2 {\1};" + transformed_code = re.sub(pattern, replacement, transformed_code, flags=re.DOTALL) + + pattern = r"typedef\s+struct\s*\{(.*?)\}\s*([^;]+)\s*;" + replacement = r"struct \2 {\1};" + transformed_code = re.sub(pattern, replacement, transformed_code, flags=re.DOTALL) + + pattern = r"typedef struct \w+ (\w+);" + replacement = r"struct \1;" + transformed_code = re.sub(pattern, replacement, transformed_code) + + pattern = r"typedef\s+struct\s*\w+\s*\*([^;]+)\s*;" + replacement = r"struct \1 { void* ptr; };" + transformed_code = re.sub(pattern, replacement, transformed_code) + + pattern = r'#include "llvm-c/(\w+).h"' + replacement = rf'#include "{here}/\1.h"' + transformed_code = re.sub(pattern, replacement, transformed_code) + + transformed_code = transformed_code.replace( + "typedef const void *LLVMDisasmContextRef;", + "typedef void *LLVMDisasmContextRef;", + ) + transformed_code = transformed_code.replace( + "typedef const void *LLVMErrorTypeId;", "typedef void *LLVMErrorTypeId;" + ) + transformed_code = transformed_code.replace( + "extern const void*", "extern void*" + ) + transformed_code = transformed_code.replace("/**", "/*") + + pattern = "^LLVM_C_EXTERN_C_BEGIN" + replacement = 'extern "C" {' + transformed_code = re.sub(pattern, replacement, transformed_code, flags=re.MULTILINE) + + pattern = "^LLVM_C_EXTERN_C_END" + replacement = "}" + transformed_code = re.sub(pattern, replacement, transformed_code, flags=re.MULTILINE) + + return transformed_code + + +def postprocess(code: str) -> str: + code = code.replace('m, "LLVM', 'm, "') + code = code.replace('.value("llvm_', '.value("') + code = code.replace('.value("llvm', '.value("') + code = code.replace('m.def("llvm_', 'm.def("') + code = code.replace('m.def("llvm', 'm.def("') + + return code + + +def generate_header_bindings(cpp_code): + options = litgen.LitgenOptions() + options.srcmlcpp_options.preserve_empty_lines = False + options.use_nanobind() + options.python_reproduce_cpp_layout = False + options.python_strip_empty_comment_lines = True + options.postprocess_pydef_function = postprocess + # options.comments_exclude = True + options.fn_exclude_by_name__regex = "LLVMDisposeMessage|LLVMContextGetDiagnosticHandler|LLVMDisasmInstruction|LLVMDisposeErrorMessage|LLVMOrcCreateStaticLibrarySearchGeneratorForPath|LLVMRemarkVersion" + generated_code = litgen.generate_code(options, cpp_code) + return generated_code.pydef_code + + +def main(header_root, output_root): + pp_dir = output_root / "pp" + pp_dir.mkdir(parents=True, exist_ok=True) + for header_f in Path(header_root).rglob("*.h"): + with open(header_f) as ff: + orig_code = ff.read() + pp_header_f = pp_dir / header_f.name + with open(pp_header_f, "w") as ff: + ff.write(preprocess_code(orig_code, pp_dir, header_f)) + + g = generate_header_bindings(open(pp_header_f).read()) + if not len(g.strip()): + continue + with open(output_root / f"{pp_header_f.stem}.cpp", "w") as ff: + ff.write( + dedent( + f""" + #include "{pp_header_f}" + #include "types.h" + #include + #include + #include + namespace nb = nanobind; + """ + ) + ) + ff.write(f"void populate_{pp_header_f.stem}(nb::module_ &m) {{") + ff.write(g) + ff.write("}") + + for mod in Path(output_root).glob("*.cpp"): + print(f"extern void populate_{mod.stem}(nb::module_ &m);") + print(f"populate_{mod.stem}(m);") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog="eudsl-llvmpy-generate") + parser.add_argument("headers_root", type=Path) + parser.add_argument("output_root", type=Path) + args = parser.parse_args() + main(args.headers_root, args.output_root) diff --git a/projects/eudsl-llvmpy/pyproject.toml b/projects/eudsl-llvmpy/pyproject.toml new file mode 100644 index 00000000..5219c128 --- /dev/null +++ b/projects/eudsl-llvmpy/pyproject.toml @@ -0,0 +1,70 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# Copyright (c) 2024. + +[project] +name = "eudsl-llvmpy" +version = "0.0.1" +requires-python = ">=3.10,<3.13" +[project.urls] +Homepage = "https://github.com/llvm/eudsl" + +[build-system] +requires = [ + "scikit-build-core==0.10.7", + "nanobind==2.4.0", + "typing_extensions==4.12.2", + "litgen @ git+https://github.com/pthom/litgen@f5d154c6f7679e755baa1047563d7c340309bc00" +] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +minimum-version = "0.4" +build-dir = "build/{wheel_tag}" +cmake.source-dir = "." + +[tool.scikit-build.cmake.define] +LLVM_DIR = { env = "LLVM_DIR", default = "EMPTY" } +CMAKE_PREFIX_PATH = { env = "CMAKE_PREFIX_PATH", default = "" } +CMAKE_C_COMPILER_LAUNCHER = { env = "CMAKE_C_COMPILER_LAUNCHER", default = "" } +CMAKE_CXX_COMPILER_LAUNCHER = { env = "CMAKE_CXX_COMPILER_LAUNCHER", default = "" } +CMAKE_CXX_VISIBILITY_PRESET = "hidden" +CMAKE_VERBOSE_MAKEFILE = "ON" + +[tool.cibuildwheel] +build-verbosity = 1 +skip = ["*-manylinux_i686", "*-musllinux*", "pp*", "*-win32"] +archs = ["auto64"] +manylinux-x86_64-image = "manylinux_2_28" +environment-pass = [ + "LLVM_DIR", + "CMAKE_GENERATOR", + "CMAKE_PREFIX_PATH", + "DISABLE_COMPILE_OPT", + "CC", + "CXX", + "PIP_FIND_LINKS", + # ccache + "CCACHE_DIR", + "CCACHE_MAXSIZE=700M", + "CCACHE_SLOPPINESS", + "CCACHE_CPP2", + "CCACHE_UMASK", + "CMAKE_C_COMPILER_LAUNCHER", + "CMAKE_CXX_COMPILER_LAUNCHER" +] +# uncomment to make sure ccache is working inside containers +test-command = "ccache -sv" + +[tool.cibuildwheel.linux] +before-all = [ + "yum install -y clang", + # ccache + "curl -sLO https://github.com/ccache/ccache/releases/download/v4.10.2/ccache-4.10.2-linux-x86_64.tar.xz", + "tar -xf ccache-4.10.2-linux-x86_64.tar.xz", + "pushd ccache-4.10.2-linux-x86_64 && make install && popd", + "ccache -z" +] +# synchronize TZ with host so ccache files have correct timestamp +container-engine = { name = "docker", create-args = ["-v", "/etc/timezone:/etc/timezone:ro", "-v", "/etc/localtime:/etc/localtime:ro"] } diff --git a/projects/eudsl-llvmpy/src/eudslllvm_ext.cpp b/projects/eudsl-llvmpy/src/eudslllvm_ext.cpp new file mode 100644 index 00000000..9ca56395 --- /dev/null +++ b/projects/eudsl-llvmpy/src/eudslllvm_ext.cpp @@ -0,0 +1,216 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Copyright (c) 2025. + +#include "pp/Core.h" +#include "pp/IRReader.h" + +#include +#include +namespace nb = nanobind; + +NB_MODULE(eudslllvm_ext, m) { + extern void populate_LLJIT(nb::module_ & m); + populate_LLJIT(m); + extern void populate_BitReader(nb::module_ & m); + populate_BitReader(m); + extern void populate_ErrorHandling(nb::module_ & m); + populate_ErrorHandling(m); + extern void populate_BitWriter(nb::module_ & m); + populate_BitWriter(m); + extern void populate_DisassemblerTypes(nb::module_ & m); + populate_DisassemblerTypes(m); + extern void populate_Orc(nb::module_ & m); + populate_Orc(m); + extern void populate_IRReader(nb::module_ & m); + populate_IRReader(m); + extern void populate_PassBuilder(nb::module_ & m); + populate_PassBuilder(m); + extern void populate_Types(nb::module_ & m); + populate_Types(m); + extern void populate_DebugInfo(nb::module_ & m); + populate_DebugInfo(m); + extern void populate_ExecutionEngine(nb::module_ & m); + populate_ExecutionEngine(m); + extern void populate_Object(nb::module_ & m); + populate_Object(m); + extern void populate_Comdat(nb::module_ & m); + populate_Comdat(m); + extern void populate_Analysis(nb::module_ & m); + populate_Analysis(m); + extern void populate_Support(nb::module_ & m); + populate_Support(m); + extern void populate_blake3(nb::module_ & m); + populate_blake3(m); + extern void populate_LLJITUtils(nb::module_ & m); + populate_LLJITUtils(m); + extern void populate_Linker(nb::module_ & m); + populate_Linker(m); + extern void populate_Remarks(nb::module_ & m); + populate_Remarks(m); + extern void populate_TargetMachine(nb::module_ & m); + populate_TargetMachine(m); + extern void populate_Error(nb::module_ & m); + populate_Error(m); + extern void populate_lto(nb::module_ &m); + populate_lto(m); + extern void populate_OrcEE(nb::module_ & m); + populate_OrcEE(m); + extern void populate_Core(nb::module_ & m); + populate_Core(m); + extern void populate_Disassembler(nb::module_ & m); + populate_Disassembler(m); + extern void populate_Target(nb::module_ & m); + populate_Target(m); + + m.def("parse_ir_in_context", + [](LLVMContextRef ContextRef, LLVMMemoryBufferRef MemBuf, + LLVMModuleRef *OutM) { + char *OutMessage; + return LLVMParseIRInContext(ContextRef, MemBuf, OutM, &OutMessage); + }); + + m.def( + "function_type", + [](LLVMTypeRef ReturnType, std::vector ParamTypes, + unsigned ParamCount, LLVMBool IsVarArg) { + return LLVMFunctionType(ReturnType, ParamTypes.data(), ParamCount, + IsVarArg); + }, + nb::arg("return_type"), nb::arg("param_types"), nb::arg("param_count"), + nb::arg("is_var_arg"), + " * Obtain a function type consisting of a specified signature.\n *\n " + "* The function is defined as a tuple of a return Type, a list of\n * " + "parameter types, and whether the function is variadic."); + + m.def( + "get_param_types", + [](LLVMTypeRef FunctionTy, std::vector Dest) { + LLVMGetParamTypes(FunctionTy, Dest.data()); + }, + nb::arg("function_ty"), nb::arg("dest"), + " * Obtain the types of a function's parameters.\n *\n * The Dest " + "parameter should point to a pre-allocated array of\n * LLVMTypeRef at " + "least LLVMCountParamTypes() large. On return, the\n * first " + "LLVMCountParamTypes() entries in the array will be populated\n * with " + "LLVMTypeRef instances.\n *\n * @param FunctionTy The function type to " + "operate on.\n * @param Dest Memory address of an array to be filled " + "with result."); + + m.def( + "struct_type_in_context", + [](LLVMContextRef C, std::vector ElementTypes, + unsigned ElementCount, LLVMBool Packed) { + return LLVMStructTypeInContext(C, ElementTypes.data(), ElementCount, + Packed); + }, + nb::arg("c"), nb::arg("element_types"), nb::arg("element_count"), + nb::arg("packed"), + " * Create a new structure type in a context.\n *\n * A structure is " + "specified by a list of inner elements/types and\n * whether these can " + "be packed together.\n *\n * @see llvm::StructType::create()"); + + m.def( + "struct_type", + [](std::vector ElementTypes, unsigned ElementCount, + LLVMBool Packed) { + return LLVMStructType(ElementTypes.data(), ElementCount, Packed); + }, + nb::arg("element_types"), nb::arg("element_count"), nb::arg("packed"), + " * Create a new structure type in the global context.\n *\n * @see " + "llvm::StructType::create()"); + + m.def( + "struct_set_body", + [](LLVMTypeRef StructTy, std::vector ElementTypes, + unsigned ElementCount, LLVMBool Packed) { + LLVMStructSetBody(StructTy, ElementTypes.data(), ElementCount, Packed); + }, + nb::arg("struct_ty"), nb::arg("element_types"), nb::arg("element_count"), + nb::arg("packed"), + " * Set the contents of a structure type.\n *\n * @see " + "llvm::StructType::setBody()"); + + m.def( + "const_gep2", + [](LLVMTypeRef Ty, LLVMValueRef ConstantVal, + std::vector ConstantIndices, unsigned NumIndices) { + return LLVMConstGEP2(Ty, ConstantVal, ConstantIndices.data(), + NumIndices); + }, + nb::arg("ty"), nb::arg("constant_val"), nb::arg("constant_indices"), + nb::arg("num_indices")); + + m.def( + "const_in_bounds_gep2", + [](LLVMTypeRef Ty, LLVMValueRef ConstantVal, + std::vector ConstantIndices, unsigned NumIndices) { + return LLVMConstInBoundsGEP2(Ty, ConstantVal, ConstantIndices.data(), + NumIndices); + }, + nb::arg("ty"), nb::arg("constant_val"), nb::arg("constant_indices"), + nb::arg("num_indices")); + + m.def( + "const_gep_with_no_wrap_flags", + [](LLVMTypeRef Ty, LLVMValueRef ConstantVal, + std::vector ConstantIndices, unsigned NumIndices, + LLVMGEPNoWrapFlags NoWrapFlags) { + return LLVMConstGEPWithNoWrapFlags( + Ty, ConstantVal, ConstantIndices.data(), NumIndices, NoWrapFlags); + }, + nb::arg("ty"), nb::arg("constant_val"), nb::arg("constant_indices"), + nb::arg("num_indices"), nb::arg("no_wrap_flags")); + + m.def( + "get_intrinsic_declaration", + [](LLVMModuleRef Mod, unsigned ID, std::vector ParamTypes, + size_t ParamCount) { + return LLVMGetIntrinsicDeclaration(Mod, ID, ParamTypes.data(), + ParamCount); + }, + nb::arg("mod"), nb::arg("id"), nb::arg("param_types"), + nb::arg("param_count"), + " * Get or insert the declaration of an intrinsic. For overloaded " + "intrinsics,\n * parameter types must be provided to uniquely identify " + "an overload.\n *\n * @see llvm::Intrinsic::getOrInsertDeclaration()"); + + m.def( + "intrinsic_get_type", + [](LLVMContextRef Ctx, unsigned ID, std::vector ParamTypes, + size_t ParamCount) { + return LLVMIntrinsicGetType(Ctx, ID, ParamTypes.data(), ParamCount); + }, + nb::arg("ctx"), nb::arg("id"), nb::arg("param_types"), + nb::arg("param_count"), + " * Retrieves the type of an intrinsic. For overloaded intrinsics, " + "parameter\n * types must be provided to uniquely identify an " + "overload.\n *\n * @see llvm::Intrinsic::getType()"); + + m.def( + "intrinsic_copy_overloaded_name", + [](unsigned ID, std::vector ParamTypes, size_t ParamCount, + size_t *NameLength) { + return LLVMIntrinsicCopyOverloadedName(ID, ParamTypes.data(), + ParamCount, NameLength); + }, + nb::arg("id"), nb::arg("param_types"), nb::arg("param_count"), + nb::arg("name_length"), + "Deprecated: Use LLVMIntrinsicCopyOverloadedName2 instead."); + + m.def( + "intrinsic_copy_overloaded_name2", + [](LLVMModuleRef Mod, unsigned ID, std::vector ParamTypes, + size_t ParamCount, size_t *NameLength) { + return LLVMIntrinsicCopyOverloadedName2(Mod, ID, ParamTypes.data(), + ParamCount, NameLength); + }, + nb::arg("mod"), nb::arg("id"), nb::arg("param_types"), + nb::arg("param_count"), nb::arg("name_length"), + " * Copies the name of an overloaded intrinsic identified by a given " + "list of\n * parameter types.\n *\n * Unlike LLVMIntrinsicGetName, the " + "caller is responsible for freeing the\n * returned string.\n *\n * " + "This version also supports unnamed types.\n *\n * @see " + "llvm::Intrinsic::getName()"); +} diff --git a/projects/eudsl-llvmpy/src/llvm/.gitignore b/projects/eudsl-llvmpy/src/llvm/.gitignore new file mode 100644 index 00000000..765255eb --- /dev/null +++ b/projects/eudsl-llvmpy/src/llvm/.gitignore @@ -0,0 +1 @@ +*.pyi \ No newline at end of file diff --git a/projects/eudsl-llvmpy/src/llvm/__init__.py b/projects/eudsl-llvmpy/src/llvm/__init__.py new file mode 100644 index 00000000..ba20ccd3 --- /dev/null +++ b/projects/eudsl-llvmpy/src/llvm/__init__.py @@ -0,0 +1,6 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# Copyright (c) 2025. + +from .eudslllvm_ext import * diff --git a/projects/eudsl-llvmpy/src/types.h b/projects/eudsl-llvmpy/src/types.h new file mode 100644 index 00000000..8ac456f0 --- /dev/null +++ b/projects/eudsl-llvmpy/src/types.h @@ -0,0 +1,21 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Copyright (c) 2025. + +#pragma once + +#include "pp/Core.h" +#include "pp/Types.h" + +struct LLVMModuleFlagEntry { + LLVMModuleFlagBehavior Behavior; + const char *Key; + size_t KeyLen; + LLVMMetadataRef Metadata; +}; + +struct LLVMValueMetadataEntry { + unsigned Kind; + LLVMMetadataRef Metadata; +}; diff --git a/projects/eudsl-llvmpy/tests/test_bindings.py b/projects/eudsl-llvmpy/tests/test_bindings.py new file mode 100644 index 00000000..992fdd4d --- /dev/null +++ b/projects/eudsl-llvmpy/tests/test_bindings.py @@ -0,0 +1,94 @@ +# Part of the Project, under the Apache License v2.0 with Exceptions. +# See https:#llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH -exception +# Copyright (c) 2024. +from textwrap import dedent + +from llvm import ( + ContextRef, + MemoryBufferRef, + ModuleRef, + context_create, + parse_ir_in_context, + create_memory_buffer_with_memory_range, + ModuleRef, + dump_module, + print_module_to_string, + create_builder, + int32_type, + function_type, + add_function, + append_basic_block, + position_builder_at_end, + get_param, + build_add, + build_ret, + dispose_builder, + module_create_with_name_in_context, +) + + +def test_smoke(): + src = dedent( + """ + declare i32 @foo() + declare i32 @bar() + define i32 @entry(i32 %argc) { + entry: + %and = and i32 %argc, 1 + %tobool = icmp eq i32 %and, 0 + br i1 %tobool, label %if.end, label %if.then + if.then: + %call = tail call i32 @foo() + br label %return + if.end: + %call1 = tail call i32 @bar() + br label %return + return: + %retval.0 = phi i32 [ %call, %if.then ], [ %call1, %if.end ] + ret i32 %retval.0 + } + """ + ) + ctx = context_create() + buf = create_memory_buffer_with_memory_range(src, len(src), "", True) + mod = ModuleRef() + parse_ir_in_context(ctx, buf, mod) + print(print_module_to_string(mod)) + + +def test_builder(): + ctx = context_create() + mod = module_create_with_name_in_context("demo", ctx) + + # Add a "sum" function": + # - Create the function type and function instance. + param_types = [int32_type(), int32_type()] + sum_function_type = function_type(int32_type(), param_types, 2, 0) + sum_function = add_function(mod, "sum", sum_function_type) + + # - Add a basic block to the function. + entry_bb = append_basic_block(sum_function, "entry") + + # - Add an IR builder and point it at the end of the basic block. + builder = create_builder() + position_builder_at_end(builder, entry_bb) + + # - Get the two function arguments and use them co construct an "add" + # instruction. + sum_arg_0 = get_param(sum_function, 0) + sum_arg_1 = get_param(sum_function, 1) + result = build_add(builder, sum_arg_0, sum_arg_1, "result") + + # - Build the return instruction. + build_ret(builder, result) + + # - Free the builder. + dispose_builder(builder) + + print(print_module_to_string(mod)) + + +if __name__ == "__main__": + # test_smoke() + test_builder() diff --git a/projects/eudsl-nbgen/cmake/eudsl_nbgen-config.cmake b/projects/eudsl-nbgen/cmake/eudsl_nbgen-config.cmake index 2548f4e5..e5d78b4f 100644 --- a/projects/eudsl-nbgen/cmake/eudsl_nbgen-config.cmake +++ b/projects/eudsl-nbgen/cmake/eudsl_nbgen-config.cmake @@ -168,9 +168,17 @@ function(eudsl_nbgen target input_file) set_source_files_properties(${_shards} PROPERTIES GENERATED 1) endfunction() -function(patch_mlir_llvm_rpath target) +function(set_install_rpath_origin target) + set(_origin_prefix "\$ORIGIN") + if (APPLE) + set(_origin_prefix "@loader_path") + endif() + set_target_properties(${target} PROPERTIES INSTALL_RPATH "${_origin_prefix}") +endfunction() + +function(patch_llvm_rpath target) cmake_parse_arguments(ARG "STANDALONE" "" "" ${ARGN}) - # hack so we can move libMLIR and libLLVM into the wheel + # hack so we can move libLLVM into the wheel # see AddLLVM.cmake#llvm_setup_rpath if (APPLE OR UNIX) set(_origin_prefix "\$ORIGIN") @@ -178,17 +186,12 @@ function(patch_mlir_llvm_rpath target) set(_origin_prefix "@loader_path") endif() if (STANDALONE) - get_target_property(_mlir_loc MLIR LOCATION) - get_target_property(_llvm_loc LLVM LOCATION) + get_target_property(_llvm_loc ${target} LOCATION) else() - set(_mlir_loc "$") - set(_llvm_loc "$") + set(_llvm_loc "$") endif() set(_old_rpath "${_origin_prefix}/../lib${LLVM_LIBDIR_SUFFIX}") if (APPLE) - if (EXISTS ${_mlir_loc}) - execute_process(COMMAND install_name_tool -rpath "${_old_rpath}" ${_origin_prefix} "${_mlir_loc}" ERROR_VARIABLE rpath_err) - endif() if (EXISTS ${_llvm_loc}) execute_process(COMMAND install_name_tool -rpath "${_old_rpath}" ${_origin_prefix} "${_llvm_loc}" ERROR_VARIABLE rpath_err) endif() @@ -198,13 +201,9 @@ function(patch_mlir_llvm_rpath target) endif() else() # sneaky sneaky - undocumented - if (EXISTS ${_mlir_loc}) - file(RPATH_CHANGE FILE "${_mlir_loc}" OLD_RPATH "${_old_rpath}" NEW_RPATH "${_origin_prefix}") - endif() if (EXISTS ${_llvm_loc}) file(RPATH_CHANGE FILE "${_llvm_loc}" OLD_RPATH "${_old_rpath}" NEW_RPATH "${_origin_prefix}") endif() endif() - set_target_properties(${target} PROPERTIES INSTALL_RPATH "${_origin_prefix}") endif() endfunction() diff --git a/projects/eudsl-py/CMakeLists.txt b/projects/eudsl-py/CMakeLists.txt index bad1d4cf..17569a37 100644 --- a/projects/eudsl-py/CMakeLists.txt +++ b/projects/eudsl-py/CMakeLists.txt @@ -420,7 +420,9 @@ set(STANDALONE) if(EUDSLPY_STANDALONE_BUILD OR EUDSL_STANDALONE_BUILD) set(STANDALONE STANDALONE) endif() -patch_mlir_llvm_rpath(eudslpy_ext ${STANDALONE}) +patch_llvm_rpath(MLIR ${STANDALONE}) +patch_llvm_rpath(LLVM ${STANDALONE}) +set_install_rpath_origin(eudslpy_ext) # copy libMLIR into the ext dir for wheels install(IMPORTED_RUNTIME_ARTIFACTS MLIR LLVM LIBRARY DESTINATION eudsl) diff --git a/requirements.txt b/requirements.txt index 53635869..bf532c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ nanobind==2.4.0 numpy==2.0.2 +litgen @ git+https://github.com/pthom/litgen@f5d154c6f7679e755baa1047563d7c340309bc00