From 2fefdb9dcbf61f44efd7b5412ac3572a524a0886 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sat, 25 Oct 2025 13:52:11 -0600 Subject: [PATCH 01/14] Add Rust LLVM installation scripts --- .github/actions/setup-llvm/action.yml | 97 ---- .github/workflows/julia-release.yml | 34 +- .github/workflows/julia-test.yml | 34 +- .github/workflows/python-test.yml | 40 +- .github/workflows/rust-test.yml | 50 +- .github/workflows/test-docs-examples.yml | 16 +- .gitignore | 3 + Cargo.lock | 111 +++++ Cargo.toml | 1 + Makefile | 56 ++- README.md | 27 +- crates/pecos-cppsparsesim/build.rs | 4 +- crates/pecos-llvm-utils/Cargo.toml | 28 ++ crates/pecos-llvm-utils/README.md | 119 +++++ crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 164 +++++++ crates/pecos-llvm-utils/src/installer.rs | 449 ++++++++++++++++++ crates/pecos-llvm-utils/src/lib.rs | 443 +++++++++++++++++ crates/pecos-qis-core/Cargo.toml | 3 + crates/pecos-qis-core/build.rs | 120 +++++ crates/pecos-qis-core/src/lib.rs | 28 ++ crates/pecos-qis-selene/build.rs | 6 + crates/pecos-qis-selene/src/executor.rs | 101 ++-- crates/pecos-quest/build_quest.rs | 6 +- crates/pecos-qulacs/build.rs | 26 +- docs/development/DEVELOPMENT.md | 22 +- docs/user-guide/getting-started.md | 77 ++- scripts/setup_cuda.sh | 8 +- scripts/setup_llvm.ps1 | 106 ----- 28 files changed, 1845 insertions(+), 334 deletions(-) delete mode 100644 .github/actions/setup-llvm/action.yml create mode 100644 crates/pecos-llvm-utils/Cargo.toml create mode 100644 crates/pecos-llvm-utils/README.md create mode 100644 crates/pecos-llvm-utils/src/bin/pecos-llvm.rs create mode 100644 crates/pecos-llvm-utils/src/installer.rs create mode 100644 crates/pecos-llvm-utils/src/lib.rs create mode 100644 crates/pecos-qis-core/build.rs delete mode 100644 scripts/setup_llvm.ps1 diff --git a/.github/actions/setup-llvm/action.yml b/.github/actions/setup-llvm/action.yml deleted file mode 100644 index 4da7fe668..000000000 --- a/.github/actions/setup-llvm/action.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: 'Setup LLVM 14.0.6' -description: 'Install prebuilt LLVM 14.0.6 binaries for all platforms' -outputs: - llvm-path: - description: 'Path to LLVM installation' - value: ${{ steps.setup.outputs.llvm-path }} - -runs: - using: 'composite' - steps: - - name: Setup LLVM (Linux) - if: runner.os == 'Linux' - shell: bash - id: setup-linux - run: | - mkdir -p /tmp/llvm - if [ "$(uname -m)" = "x86_64" ]; then - echo "Installing LLVM 14.0.6 for x86_64..." - # Use RHEL 8.4 build (Ubuntu 18.04 build doesn't exist for x86_64 in LLVM 14.0.6) - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz - tar xf clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz -C /tmp/llvm --strip-components=1 - else - echo "Installing LLVM 14.0.6 for aarch64..." - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz - tar xf clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz -C /tmp/llvm --strip-components=1 - fi - echo "/tmp/llvm/bin" >> $GITHUB_PATH - echo "LLVM_SYS_140_PREFIX=/tmp/llvm" >> $GITHUB_ENV - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - - # Verify installation - /tmp/llvm/bin/llc --version - /tmp/llvm/bin/clang --version - - - name: Setup LLVM (macOS) - if: runner.os == 'macOS' - shell: bash - id: setup-macos - run: | - mkdir -p /tmp/llvm - if [ "$(uname -m)" = "arm64" ]; then - echo "Installing LLVM 14.0.6 for arm64..." - ARCH_PREFIX=arm64-apple-darwin22.3.0 - else - echo "Installing LLVM 14.0.6 for x86_64..." - ARCH_PREFIX=x86_64-apple-darwin - fi - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-$ARCH_PREFIX.tar.xz - tar xf clang+llvm-14.0.6-$ARCH_PREFIX.tar.xz -C /tmp/llvm --strip-components=1 - - # FIX: LLVM 14.0.6 libunwind libraries have @rpath references to themselves - # This causes "Library not loaded: @rpath/libunwind.1.dylib" errors at runtime - # Fix by changing the install name to use the absolute path - echo "Fixing LLVM libunwind install names..." - for lib in /tmp/llvm/lib/libunwind*.dylib; do - if [ -f "$lib" ]; then - echo " Fixing $(basename $lib)" - # Change the self-reference from @rpath/libunwind.1.dylib to the absolute path - install_name_tool -id "$lib" "$lib" - fi - done - echo "LLVM libunwind libraries fixed" - - echo "/tmp/llvm/bin" >> $GITHUB_PATH - echo "LLVM_SYS_140_PREFIX=/tmp/llvm" >> $GITHUB_ENV - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - - # Verify installation - /tmp/llvm/bin/llc --version - /tmp/llvm/bin/clang --version - - - name: Setup LLVM (Windows) - if: runner.os == 'Windows' - shell: pwsh - id: setup-windows - run: | - Write-Host "Installing LLVM 14.0.6 for Windows..." - curl.exe -LO https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z - 7z x LLVM-14.0.6-win64.7z "-oC:\LLVM" -y - - echo "C:\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo "LLVM_SYS_140_PREFIX=C:\LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "llvm-path=C:\LLVM" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - - # Verify installation - C:\LLVM\bin\llc.exe --version - C:\LLVM\bin\clang.exe --version - - - name: Set output - shell: bash - id: setup - run: | - if [ "$RUNNER_OS" = "Linux" ] || [ "$RUNNER_OS" = "macOS" ]; then - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - else - echo "llvm-path=C:/LLVM" >> $GITHUB_OUTPUT - fi diff --git a/.github/workflows/julia-release.yml b/.github/workflows/julia-release.yml index d29148f0a..42edc9940 100644 --- a/.github/workflows/julia-release.yml +++ b/.github/workflows/julia-release.yml @@ -97,8 +97,38 @@ jobs: - name: Set up Rust run: rustup show - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Install Rust target run: | diff --git a/.github/workflows/julia-test.yml b/.github/workflows/julia-test.yml index b18c80e03..9871c6eb2 100644 --- a/.github/workflows/julia-test.yml +++ b/.github/workflows/julia-test.yml @@ -57,8 +57,38 @@ jobs: - name: Set up Rust run: rustup show - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Cache Rust uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 9cf6b2cee..b6491a8ef 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -57,8 +57,38 @@ jobs: with: workspaces: python/pecos-rslib - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Build and test PECOS (Windows) if: runner.os == 'Windows' @@ -227,7 +257,7 @@ jobs: if [ -f "$lib" ]; then libname=$(basename "$lib") if otool -L "$lib" 2>/dev/null | grep -q "libunwind"; then - echo " ⚠️ $libname HAS libunwind reference:" + echo " [WARNING] $libname HAS libunwind reference:" otool -L "$lib" | grep libunwind fi fi @@ -293,10 +323,10 @@ jobs: exit 1 else - echo "✅ SUCCESS: No @rpath/libunwind reference found!" + echo "[OK] No @rpath/libunwind reference found" fi else - echo "⚠️ WARNING: Could not find extension module to check" + echo "[WARNING] Could not find extension module to check" fi fi diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 03468fc36..b5ea12a6d 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -49,8 +49,20 @@ jobs: with: save-if: ${{ github.ref_name == 'master' || github.ref_name == 'development' || github.ref_name == 'dev' }} - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Install rustfmt run: rustup component add rustfmt @@ -179,8 +191,38 @@ jobs: with: save-if: ${{ github.ref_name == 'master' || github.ref_name == 'development' || github.ref_name == 'dev' }} - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: matrix.os != 'windows-latest' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Set up Visual Studio environment on Windows if: matrix.os == 'windows-latest' diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 25ac254ff..8a18bfc12 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -41,8 +41,20 @@ jobs: with: workspaces: python/pecos-rslib - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Generate lockfile and install dependencies run: | diff --git a/.gitignore b/.gitignore index ae3edfa9c..2e4c5ead7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ tmp/ # LLVM (extracted from archive for Windows development) llvm/ +# Cargo config with machine-specific LLVM paths +.cargo/config.toml + # pytest results junit/ diff --git a/Cargo.lock b/Cargo.lock index 54545a340..e6fe6c3de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,21 @@ dependencies = [ "virtue", ] +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + [[package]] name = "bitflags" version = "2.10.0" @@ -736,6 +751,21 @@ version = "0.125.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7a06c330b7994a891ad5b622ebc9aefcd17beae832dd25f577cf60c13426bf" +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -1240,6 +1270,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "filetime_creation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f" +dependencies = [ + "cfg-if", + "filetime", + "windows-sys 0.52.0", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -2129,6 +2170,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzma-rust" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661" +dependencies = [ + "byteorder", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mach2" version = "0.4.3" @@ -2208,6 +2269,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nt-time" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5" +dependencies = [ + "chrono", + "time", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2514,6 +2585,19 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "pecos-llvm-utils" +version = "0.1.1" +dependencies = [ + "clap", + "dirs", + "flate2", + "reqwest", + "sevenz-rust", + "tar", + "xz2", +] + [[package]] name = "pecos-phir" version = "0.1.1" @@ -2586,6 +2670,7 @@ dependencies = [ "log", "pecos-core", "pecos-engines", + "pecos-llvm-utils", "pecos-programs", "pecos-qis-ffi-types", "pecos-qis-selene", @@ -3654,6 +3739,23 @@ dependencies = [ "syn", ] +[[package]] +name = "sevenz-rust" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef" +dependencies = [ + "bit-set", + "byteorder", + "crc", + "filetime_creation", + "js-sys", + "lzma-rust", + "nt-time", + "sha2", + "wasm-bindgen", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4970,6 +5072,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 35440cfdb..693ae274e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ pecos-qis-core = { version = "0.1.1", path = "crates/pecos-qis-core" } pecos-hugr-qis = { version = "0.1.1", path = "crates/pecos-hugr-qis" } pecos-rslib = { version = "0.1.1", path = "python/pecos-rslib/rust" } pecos-build-utils = { version = "0.1.1", path = "crates/pecos-build-utils" } +pecos-llvm-utils = { version = "0.1.1", path = "crates/pecos-llvm-utils" } # Decoder crates pecos-decoder-core = { version = "0.1.1", path = "crates/pecos-decoder-core" } diff --git a/Makefile b/Makefile index c66afa5b6..a3059899f 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,9 @@ PYTHON := $(shell which python 2>/dev/null || which python3 2>/dev/null) SHELL=bash -# Set LLVM path for Windows development builds (only if llvm/ directory exists) -ifdef OS - # Windows - check if local LLVM exists and set path - ifneq ($(wildcard llvm/bin/llvm-config.exe),) - export LLVM_SYS_140_PREFIX := $(CURDIR)/llvm - endif -endif +# LLVM Configuration +# LLVM is automatically detected by build.rs files using pecos-llvm-utils +# No manual configuration needed! # Requirements # ------------ @@ -35,34 +31,48 @@ installreqs: ## Install Python project requirements to root .venv # Building development environments # --------------------------------- -# Helper to unset CONDA_PREFIX and set LLVM path in a cross-platform way +.PHONY: check-llvm +check-llvm: ## Check LLVM 14 installation status + @cargo run -q --release --package pecos-llvm-utils --bin pecos-llvm -- check || true + +# LLVM Detection Helper +# Auto-detect LLVM if not already set +SETUP_LLVM = \ + if [ -z "$$LLVM_SYS_140_PREFIX" ]; then \ + DETECTED_LLVM=$$(cargo run -q --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>/dev/null); \ + if [ -n "$$DETECTED_LLVM" ]; then \ + export PECOS_LLVM="$$DETECTED_LLVM"; \ + export LLVM_SYS_140_PREFIX="$$DETECTED_LLVM"; \ + echo "Auto-detected LLVM at: $$LLVM_SYS_140_PREFIX"; \ + fi; \ + fi + +# Helper to unset CONDA_PREFIX and add LLVM to PATH for runtime tools ifdef OS # Windows (running in Git Bash/MSYS) UNSET_CONDA = set "CONDA_PREFIX=" && - # Set LLVM path if local installation exists - ifneq ($(wildcard llvm/bin/llvm-config.exe),) - SET_LLVM = set "LLVM_SYS_140_PREFIX=$(CURDIR)/llvm" && - # Add LLVM bin to PATH for runtime tools (llvm-as, etc.) - # Use colon separator - Git Bash uses Unix-style paths internally - ADD_LLVM_TO_PATH = export PATH="$(CURDIR)/llvm/bin:$$PATH" && + ifdef LLVM_SYS_140_PREFIX + ADD_LLVM_TO_PATH = export PATH="$(LLVM_SYS_140_PREFIX)/bin:$$PATH" && else - SET_LLVM = ADD_LLVM_TO_PATH = endif else # Unix/Linux/macOS UNSET_CONDA = unset CONDA_PREFIX && - SET_LLVM = - ADD_LLVM_TO_PATH = + ifdef LLVM_SYS_140_PREFIX + ADD_LLVM_TO_PATH = export PATH="$(LLVM_SYS_140_PREFIX)/bin:$$PATH" && + else + ADD_LLVM_TO_PATH = + endif endif .PHONY: build build: installreqs ## Compile and install for development - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library..."; \ - cd julia/pecos-julia-ffi && cargo build; \ + $(SETUP_LLVM); cd julia/pecos-julia-ffi && cargo build; \ echo "Julia FFI library built successfully"; \ else \ echo "Julia not detected, skipping Julia build"; \ @@ -70,12 +80,12 @@ build: installreqs ## Compile and install for development .PHONY: build-basic build-basic: installreqs ## Compile and install for development but do not include install extras - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e ./python/quantum-pecos .PHONY: build-release build-release: installreqs ## Build a faster version of binaries - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv --release + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv --release @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library (release)..."; \ @@ -87,13 +97,13 @@ build-release: installreqs ## Build a faster version of binaries .PHONY: build-native build-native: installreqs ## Build a faster version of binaries with native CPU optimization - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && RUSTFLAGS='-C target-cpu=native' \ + @$(UNSET_CONDA) cd python/pecos-rslib/ && RUSTFLAGS='-C target-cpu=native' \ uv run maturin develop --uv --release @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" .PHONY: build-cuda build-cuda: installreqs ## Compile and install for development with CUDA support - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all,cuda]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library..."; \ diff --git a/README.md b/README.md index 060c1593f..e28276c53 100644 --- a/README.md +++ b/README.md @@ -115,17 +115,26 @@ pecos = "0.x.x" # Replace with the latest version #### Optional Dependencies -- **LLVM version 14**: Required for LLVM IR execution support - - Linux: `sudo apt install llvm-14` - - macOS: `brew install llvm@14` - - Windows: - - For development builds, use the included setup script: `.\scripts\setup_llvm.ps1` - - This will extract the bundled LLVM 14.0.6 and configure the build and test environment - - Alternatively, download LLVM 14.x installer from [LLVM releases](https://releases.llvm.org/download.html#14.0.0) +- **LLVM version 14**: Required for LLVM IR execution support (optional) - **Note**: Only LLVM version 14.x is compatible. LLVM 15 or later versions will not work with PECOS's LLVM runtime implementation. + PECOS provides an automated installer or you can install manually: - If LLVM 14 is not installed, PECOS will still function normally but LLVM IR execution features will be disabled. + ```sh + # Quick setup with automated installer (recommended): + cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + cargo build + ``` + + The installer automatically configures PECOS after installation. + + For detailed LLVM installation instructions for all platforms (macOS, Linux, Windows), see the [**Getting Started Guide**](docs/user-guide/getting-started.md#llvm-for-qis-support). + + For full development environment setup, see the [**Development Setup Guide**](docs/development/DEVELOPMENT.md). + + **Building without LLVM:** If you don't need LLVM IR support: + ```sh + cargo build --no-default-features + ``` ### Julia Package (Experimental) diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs index de3bc9c40..d9c2fe54e 100644 --- a/crates/pecos-cppsparsesim/build.rs +++ b/crates/pecos-cppsparsesim/build.rs @@ -44,9 +44,7 @@ fn main() { // On macOS, use the -stdlib=libc++ flag to ensure proper C++ standard library linkage if target.contains("darwin") { bridge.flag("-stdlib=libc++"); - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - bridge.flag("-L/usr/lib"); - bridge.flag("-Wl,-search_paths_first"); + // Note: Linker-specific flags are passed via cargo:rustc-link-arg below, not here } bridge.compile("cppsparsesim-bridge"); diff --git a/crates/pecos-llvm-utils/Cargo.toml b/crates/pecos-llvm-utils/Cargo.toml new file mode 100644 index 000000000..272378d29 --- /dev/null +++ b/crates/pecos-llvm-utils/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pecos-llvm-utils" +version.workspace = true +edition.workspace = true +description = "LLVM detection and utilities for PECOS" +readme.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords = ["quantum", "llvm", "build-utils"] +categories = ["development-tools::build-utils"] + +[[bin]] +name = "pecos-llvm" +path = "src/bin/pecos-llvm.rs" + +[dependencies] +clap.workspace = true +reqwest.workspace = true +tar.workspace = true +flate2.workspace = true +dirs.workspace = true +xz2 = "0.1" +sevenz-rust = "0.6" + +[lints] +workspace = true diff --git a/crates/pecos-llvm-utils/README.md b/crates/pecos-llvm-utils/README.md new file mode 100644 index 000000000..acf023acf --- /dev/null +++ b/crates/pecos-llvm-utils/README.md @@ -0,0 +1,119 @@ +# pecos-llvm-utils + +LLVM detection, installation, and management for PECOS. + +This crate provides functionality to locate and install LLVM 14 across different platforms (macOS, Linux, Windows). + +## Features + +- **Automatic LLVM Detection**: Finds LLVM 14 in common system locations +- **LLVM Installation**: Downloads and installs LLVM 14.0.6 to user data directory +- **Cross-platform**: Works on macOS, Linux, and Windows +- **Pure Rust**: No external dependencies (tar, 7zip, etc.) required for installation +- **Build Script Integration**: Can be used in `build.rs` files +- **Command-line Tool**: `pecos-llvm` binary for all LLVM operations + +### Installation Details + +The installer uses pure Rust dependencies for archive extraction: +- **Unix systems (macOS/Linux)**: Uses `xz2` and `tar` crates for .tar.xz extraction +- **Windows**: Uses `sevenz-rust` crate for .7z extraction + +No external tools (tar, 7zip) are required - everything is handled through Rust libraries. + +## Command-line Tool: `pecos-llvm` + +The `pecos-llvm` binary provides several subcommands: + +### Find LLVM + +```bash +# Find and print LLVM path +pecos-llvm find + +# Print export command for shell evaluation +pecos-llvm find --export +``` + +### Check LLVM Availability + +```bash +# Check if LLVM is available (exit code 0 if found, 1 if not) +pecos-llvm check + +# Quiet mode (no output) +pecos-llvm check --quiet +``` + +### Install LLVM + +```bash +# Install LLVM 14.0.6 to ~/.pecos/llvm +pecos-llvm install + +# Force reinstall +pecos-llvm install --force +``` + +### Show Version + +```bash +# Show LLVM version information +pecos-llvm version +``` + +## Usage in build.rs + +```rust +use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error}; + +fn main() { + let repo_root = get_repo_root_from_manifest(); + match find_llvm_14(repo_root) { + Some(path) => { + println!("cargo:warning=Found LLVM 14 at: {}", path.display()); + } + None => { + print_llvm_not_found_error(); + panic!("LLVM 14 required but not found"); + } + } +} +``` + +## Shell Scripts + +The `pecos-llvm` tool can be wrapped in shell scripts: + +### Bash/Zsh +```bash +#!/bin/bash +# Install LLVM +cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- install + +# Set environment variable +export LLVM_SYS_140_PREFIX=$(cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>/dev/null) +``` + +### PowerShell +```powershell +# Install LLVM +cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- install + +# Set environment variable +$env:LLVM_SYS_140_PREFIX = (cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>$null) +``` + +## Detection Priority + +The crate searches for LLVM 14 in the following order: + +1. **Home directory**: `~/.pecos/llvm` (where `pecos-llvm install` puts it) +2. **Project-local**: `llvm/` directory (relative to repository root, for backward compatibility) +3. **System installations**: + - **macOS**: Homebrew installations (`/opt/homebrew/opt/llvm@14`, `/usr/local/opt/llvm@14`) + - **Linux**: Package manager installations (`/usr/lib/llvm-14`, `/usr/local/llvm-14`) + +## License + +Apache-2.0 diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs new file mode 100644 index 000000000..2b34ed19a --- /dev/null +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -0,0 +1,164 @@ +#!/usr/bin/env rust +//! PECOS LLVM management tool +//! +//! Handles LLVM 14 detection, installation, and configuration for PECOS. + +use clap::{Parser, Subcommand}; +use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error}; +use std::process; + +#[derive(Parser)] +#[command(name = "pecos-llvm")] +#[command(about = "PECOS LLVM management tool", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Find LLVM 14 installation and print its path + Find { + /// Print export command for shell evaluation + #[arg(long)] + export: bool, + }, + /// Check if LLVM 14 is available (exit code 0 if found, 1 if not) + Check { + /// Suppress output messages + #[arg(short, long)] + quiet: bool, + }, + /// Install LLVM 14.0.6 to ~/.pecos/llvm/ + Install { + /// Force reinstall even if already present + #[arg(short, long)] + force: bool, + /// Skip automatic configuration after installation + #[arg(long)] + no_configure: bool, + }, + /// Auto-configure LLVM for PECOS (updates .cargo/config.toml) + Configure, + /// Show LLVM version information + Version, +} + +fn main() { + let cli = Cli::parse(); + + match cli.command { + Commands::Find { export } => cmd_find(export), + Commands::Check { quiet } => cmd_check(quiet), + Commands::Install { + force, + no_configure, + } => cmd_install(force, no_configure), + Commands::Configure => cmd_configure(), + Commands::Version => cmd_version(), + } +} + +fn cmd_find(export: bool) { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + let path_str = path.to_string_lossy(); + if export { + println!("export LLVM_SYS_140_PREFIX=\"{path_str}\""); + } else { + println!("{path_str}"); + } + process::exit(0); + } else { + print_llvm_not_found_error(); + process::exit(1); + } +} + +fn cmd_check(quiet: bool) { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + if !quiet { + eprintln!("LLVM 14 found at: {}", path.display()); + } + process::exit(0); + } else { + if !quiet { + eprintln!("LLVM 14 not found"); + } + process::exit(1); + } +} + +fn cmd_install(force: bool, no_configure: bool) { + use pecos_llvm_utils::installer::install_llvm; + + match install_llvm(force, no_configure) { + Ok(_install_path) => { + // Success message is printed by install_llvm + process::exit(0); + } + Err(e) => { + eprintln!("Failed to install LLVM: {e}"); + process::exit(1); + } + } +} + +fn cmd_configure() { + use pecos_llvm_utils::auto_configure_llvm; + + println!("Auto-configuring LLVM for PECOS..."); + println!(); + + match auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Success! LLVM configured at: {}", configured_path.display()); + println!(); + println!("Updated .cargo/config.toml with LLVM configuration."); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + process::exit(0); + } + Err(e) => { + eprintln!("Failed to configure LLVM: {e}"); + eprintln!(); + eprintln!("To install LLVM, run:"); + eprintln!(" pecos-llvm install"); + process::exit(1); + } + } +} + +fn cmd_version() { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + println!("LLVM 14 found at: {}", path.display()); + + // Try to get version from llvm-config + let llvm_config = if cfg!(windows) { + path.join("bin").join("llvm-config.exe") + } else { + path.join("bin").join("llvm-config") + }; + + if let Ok(output) = std::process::Command::new(&llvm_config) + .arg("--version") + .output() + && output.status.success() + { + let version = String::from_utf8_lossy(&output.stdout); + println!("Version: {}", version.trim()); + } + } else { + println!("LLVM 14 not found"); + process::exit(1); + } +} diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs new file mode 100644 index 000000000..fd0e64a61 --- /dev/null +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -0,0 +1,449 @@ +//! LLVM 14.0.6 installation functionality +//! +//! Downloads and extracts LLVM 14.0.6 pre-built binaries to a project-local directory. + +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +/// Install LLVM 14.0.6 to user data directory +/// +/// Installs to platform-appropriate location: +/// - macOS: ~/Library/Application Support/pecos/llvm +/// - Linux: ~/.local/share/pecos/llvm +/// - Windows: %LOCALAPPDATA%/pecos/llvm +/// +/// # Arguments +/// * `force` - Force reinstall even if already present +/// * `no_configure` - Skip automatic configuration after installation +/// +/// # Errors +/// Returns an error if: +/// - LLVM is already installed and `force` is false +/// - The download or extraction fails +/// - Platform-specific fixes fail +/// - Installation verification fails +/// +/// # Returns +/// Path to the installed LLVM directory +pub fn install_llvm( + force: bool, + no_configure: bool, +) -> Result> { + let llvm_dir = get_install_location()?; + + // Check if already installed + if !force && llvm_dir.exists() && is_valid_installation(&llvm_dir) { + return Err("LLVM is already installed. Use --force to reinstall.".into()); + } + + // If force is specified and directory exists, remove it first + if force && llvm_dir.exists() { + println!("Removing existing LLVM installation..."); + fs::remove_dir_all(&llvm_dir)?; + } + + println!("Installing LLVM 14.0.6..."); + println!("This will download ~400MB and may take 5-10 minutes."); + println!(); + + // Determine platform and download URL + let (url, archive_name) = get_download_url()?; + + // Create parent directory if it doesn't exist + if let Some(parent) = llvm_dir.parent() { + fs::create_dir_all(parent)?; + } + + // Download to temp directory (use llvm subdirectory to avoid conflicts) + let temp_base = llvm_dir.parent().unwrap_or(&llvm_dir).join("tmp"); + let temp_dir = temp_base.join("llvm"); + fs::create_dir_all(&temp_dir)?; + let archive_path = temp_dir.join(&archive_name); + download_llvm(&url, &archive_path)?; + + // Extract + extract_llvm(&archive_path, &llvm_dir)?; + + // Cleanup LLVM temp directory only (not entire tmp directory) + fs::remove_dir_all(&temp_dir)?; + + // Apply platform-specific fixes + apply_platform_fixes(&llvm_dir)?; + + // Verify + if !is_valid_installation(&llvm_dir) { + return Err("Installation completed but verification failed".into()); + } + + println!(); + println!("Installation complete!"); + println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); + + // Auto-configure LLVM for PECOS (unless --no-configure is specified) + if no_configure { + println!(); + println!("Skipping automatic configuration (--no-configure specified)."); + println!(); + println!("To configure PECOS, run:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + } else { + println!(); + println!("Configuring PECOS to use this LLVM installation..."); + match crate::auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Updated .cargo/config.toml with LLVM configuration"); + println!("Configured LLVM path: {}", configured_path.display()); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + } + Err(e) => { + eprintln!("Warning: Could not auto-configure LLVM: {e}"); + println!(); + println!("Please run configuration manually:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + } + } + } + + Ok(llvm_dir) +} + +fn get_install_location() -> Result> { + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + + Ok(home_dir.join(".pecos").join("llvm")) +} + +fn get_download_url() -> Result<(String, String), Box> { + let os = std::env::consts::OS; + let arch = std::env::consts::ARCH; + + match os { + "macos" => { + if arch == "aarch64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz".to_string(), + "clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz".to_string(), + )) + } else { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz".to_string(), + "clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz".to_string(), + )) + } + } + "linux" => { + if arch == "x86_64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz".to_string(), + "clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz".to_string(), + )) + } else if arch == "aarch64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz".to_string(), + "clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz".to_string(), + )) + } else { + Err(format!("Unsupported Linux architecture: {arch}").into()) + } + } + "windows" => { + Ok(( + "https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z".to_string(), + "LLVM-14.0.6-win64.7z".to_string(), + )) + } + _ => Err(format!("Unsupported operating system: {os}").into()), + } +} + +fn download_llvm(url: &str, dest: &PathBuf) -> Result<(), Box> { + print!("Downloading LLVM... "); + io::Write::flush(&mut io::stdout())?; + + let response = reqwest::blocking::get(url)?; + let total_size = response.content_length().unwrap_or(0); + + let mut file = fs::File::create(dest)?; + let mut downloaded: u64 = 0; + let mut stream = response; + let mut last_print = 0.0; + + loop { + let mut buffer = vec![0; 8192]; + let bytes_read = io::Read::read(&mut stream, &mut buffer)?; + if bytes_read == 0 { + break; + } + + io::Write::write_all(&mut file, &buffer[..bytes_read])?; + downloaded += bytes_read as u64; + + if total_size > 0 { + // Precision loss is acceptable for progress display + #[allow(clippy::cast_precision_loss)] + let progress = (downloaded as f64 / total_size as f64) * 100.0; + // Only update display every 1% + if progress - last_print >= 1.0 { + print!("\rDownloading LLVM... {progress:.0}%"); + io::Write::flush(&mut io::stdout())?; + last_print = progress; + } + } + } + + println!("\rDownloading LLVM... Done"); + Ok(()) +} + +fn extract_llvm(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + print!("Extracting LLVM... "); + io::Write::flush(&mut io::stdout())?; + + // Determine archive type using Path::extension() for case-insensitive comparison + let file_name = archive + .file_name() + .and_then(|n| n.to_str()) + .ok_or("Could not determine archive name")?; + + // Check for .tar.xz (compound extension) + if file_name.ends_with(".tar.xz") || file_name.ends_with(".tar.XZ") { + extract_tar_xz(archive, dest)?; + } else if std::path::Path::new(file_name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("7z")) + { + extract_7z(archive, dest)?; + } else { + return Err(format!("Unsupported archive format: {file_name}").into()); + } + + println!("Done"); + Ok(()) +} + +fn extract_tar_xz(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + use tar::Archive; + use xz2::read::XzDecoder; + + // Open the .tar.xz file + let file = fs::File::open(archive)?; + let decompressor = XzDecoder::new(file); + let mut tar_archive = Archive::new(decompressor); + + // Extract to parent directory first + let extract_to = dest.parent().ok_or("Invalid destination path")?; + tar_archive.unpack(extract_to)?; + + // The archive extracts to a directory like clang+llvm-14.0.6-... + // We need to determine the extracted directory name from the archive filename + let archive_name = archive + .file_stem() + .and_then(|s| s.to_str()) + .ok_or("Could not determine archive name")?; + + // For .tar.xz, we need to strip the .tar part too + let archive_path_buf = PathBuf::from(archive_name); + let base_name = if let Some(stem) = archive_path_buf.file_stem() { + stem.to_str().ok_or("Invalid archive name")? + } else { + archive_name + }; + + let extracted_dir = extract_to.join(base_name); + + // If dest doesn't exist, rename extracted_dir to dest + if dest.exists() { + // Move contents + for entry in fs::read_dir(&extracted_dir)? { + let entry = entry?; + let dest_path = dest.join(entry.file_name()); + fs::rename(entry.path(), dest_path)?; + } + fs::remove_dir(&extracted_dir)?; + } else { + fs::rename(&extracted_dir, dest)?; + } + + Ok(()) +} + +fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + use sevenz_rust::{Password, SevenZReader}; + + // Open the .7z file + let file = fs::File::open(archive)?; + let len = file.metadata()?.len(); + let password = Password::empty(); + let mut reader = SevenZReader::new(file, len, password)?; + + // Extract to parent directory first + let extract_to = dest.parent().ok_or("Invalid destination path")?; + fs::create_dir_all(extract_to)?; + + // Extract all files + reader.for_each_entries(|entry, reader| { + if entry.is_directory() { + let dir_path = extract_to.join(entry.name()); + fs::create_dir_all(&dir_path).ok(); + } else { + let file_path = extract_to.join(entry.name()); + if let Some(parent) = file_path.parent() { + fs::create_dir_all(parent).ok(); + } + let mut output = fs::File::create(&file_path)?; + io::copy(reader, &mut output)?; + } + Ok(true) // Continue extracting + })?; + + // The archive extracts to a directory like LLVM-14.0.6-win64 + // Find the extracted directory + let mut extracted_dir = None; + for entry in fs::read_dir(extract_to)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() && path.file_name().unwrap().to_str().unwrap().contains("LLVM") { + extracted_dir = Some(path); + break; + } + } + + let extracted_dir = extracted_dir.ok_or("Could not find extracted LLVM directory")?; + + // If dest doesn't exist, rename extracted_dir to dest + if dest.exists() { + // Move contents + for entry in fs::read_dir(&extracted_dir)? { + let entry = entry?; + let dest_path = dest.join(entry.file_name()); + fs::rename(entry.path(), dest_path)?; + } + fs::remove_dir(&extracted_dir)?; + } else { + fs::rename(&extracted_dir, dest)?; + } + + Ok(()) +} + +fn is_valid_installation(path: &Path) -> bool { + let llvm_config = if cfg!(windows) { + path.join("bin").join("llvm-config.exe") + } else { + path.join("bin").join("llvm-config") + }; + + llvm_config.exists() +} + +fn apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box> { + #[cfg(target_os = "macos")] + { + print!("Applying macOS fixes... "); + io::Write::flush(&mut io::stdout())?; + + // Fix 1: Configure clang to use macOS SDK + // The pre-built LLVM doesn't know where system libraries are on macOS + configure_macos_sdk(llvm_dir)?; + + // Fix 2: Fix libunwind dylib references + // LLVM 14.0.6 libunwind libraries have @rpath references to themselves + fix_libunwind_references(llvm_dir)?; + + println!("Done"); + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn configure_macos_sdk(llvm_dir: &Path) -> Result<(), Box> { + use std::os::unix::fs::PermissionsExt; + use std::process::Command; + + // Get the SDK path from xcrun + let output = Command::new("xcrun").args(["--show-sdk-path"]).output()?; + + if !output.status.success() { + return Err("Failed to find macOS SDK. Is Xcode Command Line Tools installed?".into()); + } + + let sdk_path = String::from_utf8(output.stdout)?.trim().to_string(); + + // Create wrapper scripts for clang and clang-14 that add SDK flags + let bin_dir = llvm_dir.join("bin"); + + // Rename original binaries + let clang_orig = bin_dir.join("clang"); + let clang_real = bin_dir.join("clang-real"); + if clang_orig.exists() && !clang_real.exists() { + fs::rename(&clang_orig, &clang_real)?; + } + + let clang14_orig = bin_dir.join("clang-14"); + let clang14_real = bin_dir.join("clang-14-real"); + if clang14_orig.exists() && !clang14_real.exists() { + fs::rename(&clang14_orig, &clang14_real)?; + } + + // Create wrapper script for clang + let wrapper_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clang_orig, &wrapper_content)?; + fs::set_permissions(&clang_orig, fs::Permissions::from_mode(0o755))?; + + // Create wrapper script for clang-14 + let wrapper14_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-14-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clang14_orig, &wrapper14_content)?; + fs::set_permissions(&clang14_orig, fs::Permissions::from_mode(0o755))?; + + // Also create wrapper for clang++ if it exists + let clangpp_orig = bin_dir.join("clang++"); + let clangpp_real = bin_dir.join("clang++-real"); + if clangpp_orig.exists() && !clangpp_real.exists() { + fs::rename(&clangpp_orig, &clangpp_real)?; + let wrapperpp_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang++-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clangpp_orig, &wrapperpp_content)?; + fs::set_permissions(&clangpp_orig, fs::Permissions::from_mode(0o755))?; + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn fix_libunwind_references(llvm_dir: &Path) -> Result<(), Box> { + use std::process::Command; + + let lib_dir = llvm_dir.join("lib"); + + // Find all libunwind dylibs + if let Ok(entries) = fs::read_dir(&lib_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + // Check for libunwind dylib files using case-insensitive extension check + if name.starts_with("libunwind") + && std::path::Path::new(name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("dylib")) + { + // Fix the install name to use absolute path instead of @rpath + let abs_path = path.to_string_lossy().to_string(); + Command::new("install_name_tool") + .args(["-id", &abs_path, &abs_path]) + .output()?; + } + } + } + } + + Ok(()) +} diff --git a/crates/pecos-llvm-utils/src/lib.rs b/crates/pecos-llvm-utils/src/lib.rs new file mode 100644 index 000000000..82d5d4cf0 --- /dev/null +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -0,0 +1,443 @@ +//! LLVM detection and utilities for PECOS +//! +//! This crate provides functionality to locate and install LLVM 14 across different platforms. +//! It's primarily used by build scripts but can also be used standalone via the `pecos-llvm` binary. + +pub mod installer; + +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Find LLVM 14 installation on the system. +/// +/// This function searches for LLVM 14 in the following priority order: +/// 1. Home directory (~/.pecos/llvm) +/// 2. Project-local installation (llvm/ directory relative to repository root) +/// 3. System installations (platform-specific locations) +/// +/// # Returns +/// - `Some(PathBuf)` if LLVM 14 is found and valid +/// - `None` if LLVM 14 is not found +/// +/// # Example +/// ```no_run +/// use pecos_llvm_utils::find_llvm_14; +/// +/// if let Some(llvm_path) = find_llvm_14(None) { +/// println!("Found LLVM 14 at: {}", llvm_path.display()); +/// } else { +/// eprintln!("LLVM 14 not found!"); +/// } +/// ``` +#[must_use] +pub fn find_llvm_14(repo_root: Option) -> Option { + // 1. Check home directory (~/.pecos/llvm) + if let Some(home_dir) = dirs::home_dir() { + let user_llvm = home_dir.join(".pecos").join("llvm"); + if is_valid_llvm_14(&user_llvm) { + return Some(user_llvm); + } + } + + // 2. Check for project-local LLVM (for backward compatibility) + if let Some(root) = repo_root { + let local_llvm = root.join("llvm"); + if is_valid_llvm_14(&local_llvm) { + return Some(local_llvm); + } + } + + // 3. Check system installations + find_system_llvm_14() +} + +/// Find LLVM 14 in system-wide locations (platform-specific) +fn find_system_llvm_14() -> Option { + #[cfg(target_os = "macos")] + { + // Try Homebrew installation via brew command + if let Ok(output) = Command::new("brew").args(["--prefix", "llvm@14"]).output() + && output.status.success() + { + let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let path = PathBuf::from(path_str); + if is_valid_llvm_14(&path) { + return Some(path); + } + } + + // Try common Homebrew paths (in case brew command isn't available) + for path_str in [ + "/opt/homebrew/opt/llvm@14", // Apple Silicon + "/usr/local/opt/llvm@14", // Intel Mac + ] { + let llvm_path = PathBuf::from(path_str); + if is_valid_llvm_14(&llvm_path) { + return Some(llvm_path); + } + } + } + + #[cfg(target_os = "linux")] + { + // Check if llvm-config-14 is in PATH and get its prefix + if let Ok(output) = Command::new("llvm-config-14").arg("--prefix").output() { + if output.status.success() { + let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let path = PathBuf::from(path_str); + if is_valid_llvm_14(&path) { + return Some(path); + } + } + } + + // Try common Linux installation paths + for path_str in [ + "/usr/lib/llvm-14", + "/usr/local/llvm-14", + "/usr/lib/x86_64-linux-gnu/llvm-14", + ] { + let llvm_path = PathBuf::from(path_str); + if is_valid_llvm_14(&llvm_path) { + return Some(llvm_path); + } + } + } + + #[cfg(target_os = "windows")] + { + // Windows typically uses project-local installation + // System-wide LLVM installations on Windows would need to be in PATH + // or have LLVM_SYS_140_PREFIX set + } + + None +} + +/// Check if a given path contains a valid LLVM 14 installation +/// +/// # Arguments +/// * `path` - Path to check for LLVM installation +/// +/// # Returns +/// `true` if the path contains a valid LLVM 14 installation, `false` otherwise +#[must_use] +pub fn is_valid_llvm_14(path: &Path) -> bool { + // Check if the path exists + if !path.exists() { + return false; + } + + // Determine llvm-config path based on platform + #[cfg(target_os = "windows")] + let llvm_config = path.join("bin").join("llvm-config.exe"); + + #[cfg(not(target_os = "windows"))] + let llvm_config = path.join("bin").join("llvm-config"); + + if !llvm_config.exists() { + return false; + } + + // Verify it's LLVM 14 by checking the version + if let Ok(output) = Command::new(&llvm_config).arg("--version").output() + && output.status.success() + { + let version = String::from_utf8_lossy(&output.stdout); + return version.starts_with("14."); + } + + false +} + +/// Print a helpful error message when LLVM 14 is not found +pub fn print_llvm_not_found_error() { + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("ERROR: LLVM 14 not found!"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("PECOS requires LLVM version 14 for LLVM IR/QIR execution features."); + eprintln!(); + eprintln!("To install LLVM 14:"); + eprintln!(); + eprintln!(" Automated installation (all platforms):"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install"); + eprintln!(); + + #[cfg(target_os = "macos")] + { + eprintln!(" Or install via Homebrew:"); + eprintln!(" brew install llvm@14"); + eprintln!(); + eprintln!(" Then the build system will auto-detect it, or set:"); + eprintln!(" export LLVM_SYS_140_PREFIX=$(brew --prefix llvm@14)"); + } + + #[cfg(target_os = "linux")] + { + eprintln!(" Or install via package manager:"); + eprintln!(" sudo apt install llvm-14 # Debian/Ubuntu"); + eprintln!(); + eprintln!(" The build system will auto-detect most installations, or set:"); + eprintln!(" export LLVM_SYS_140_PREFIX=/usr/lib/llvm-14"); + } + + #[cfg(target_os = "windows")] + { + eprintln!(" Or download manually:"); + eprintln!(" https://releases.llvm.org/download.html#14.0.0"); + eprintln!(); + eprintln!(" Then set:"); + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\path\\to\\llvm"); + } + + eprintln!(); + eprintln!("Alternatively, you can build without LLVM support:"); + eprintln!(" cargo build --no-default-features"); + eprintln!(); + eprintln!("For more details, see:"); + eprintln!(" https://quantum-pecos.readthedocs.io/"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); +} + +/// Automatically configure LLVM for PECOS +/// +/// This function determines the best LLVM 14 installation to use and writes +/// it to `.cargo/config.toml` with force=true. This is the authoritative +/// configuration function for PECOS. +/// +/// Priority order: +/// 1. ~/.pecos/llvm/ - PECOS-managed LLVM (if it exists) +/// 2. `LLVM_SYS_140_PREFIX` environment variable (if set and valid) +/// 3. System LLVM 14 (Homebrew, system paths, etc.) +/// +/// # Arguments +/// * `project_root` - Optional path to the Cargo project root. If None, attempts to find it. +/// +/// # Errors +/// Returns an error if: +/// - No suitable LLVM 14 installation could be found +/// - The Cargo project root could not be determined +/// - Writing to `.cargo/config.toml` fails +/// +/// # Returns +/// * `Ok(PathBuf)` - The path that was configured +pub fn auto_configure_llvm( + project_root: Option, +) -> Result> { + use std::env; + + // Priority 1: Check ~/.pecos/llvm/ + if let Some(home_dir) = dirs::home_dir() { + let pecos_llvm = home_dir.join(".pecos").join("llvm"); + if is_valid_llvm_14(&pecos_llvm) { + // Found PECOS-managed LLVM, configure it + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &pecos_llvm, true)?; + return Ok(pecos_llvm); + } + } + + // Priority 2: Check shell LLVM_SYS_140_PREFIX + if let Ok(sys_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let path = PathBuf::from(&sys_prefix); + if is_valid_llvm_14(&path) { + // Shell env var points to valid LLVM, configure it + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &path, true)?; + return Ok(path); + } + } + + // Priority 3: Scan system for LLVM 14 + let repo_root = get_repo_root_from_manifest(); + if let Some(detected_path) = find_llvm_14(repo_root) { + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &detected_path, true)?; + return Ok(detected_path); + } + + // No LLVM 14 found anywhere + Err("No suitable LLVM 14 installation found".into()) +} + +/// Get the repository root from `CARGO_MANIFEST_DIR` +/// +/// This assumes the crate is located at `crates/` in the repository +#[must_use] +pub fn get_repo_root_from_manifest() -> Option { + if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { + let mut path = PathBuf::from(manifest_dir); + // Go up from crates/ to repository root + if path.pop() && path.pop() { + return Some(path); + } + } + None +} + +/// Find the Cargo project root by searching for Cargo.toml +/// +/// Starts from the current directory and walks up the directory tree +/// until it finds a directory containing Cargo.toml or Cargo.lock +#[must_use] +pub fn find_cargo_project_root() -> Option { + let current_dir = std::env::current_dir().ok()?; + let mut path = current_dir.as_path(); + + loop { + if path.join("Cargo.toml").exists() || path.join("Cargo.lock").exists() { + return Some(path.to_path_buf()); + } + + path = path.parent()?; + } +} + +/// Write or update .cargo/config.toml with LLVM configuration +/// +/// # Arguments +/// * `project_root` - Path to the Cargo project root +/// * `llvm_path` - Path to the LLVM installation +/// * `force` - If true, use force=true to override shell environment variables +/// +/// # Errors +/// Returns an error if: +/// - Creating the `.cargo` directory fails +/// - Reading or writing to `.cargo/config.toml` fails +/// +/// # Returns +/// `Ok(())` if successful +pub fn write_cargo_config( + project_root: &Path, + llvm_path: &Path, + force: bool, +) -> Result<(), Box> { + use std::fs; + + let cargo_dir = project_root.join(".cargo"); + let config_path = cargo_dir.join("config.toml"); + + // Create .cargo directory if it doesn't exist + fs::create_dir_all(&cargo_dir)?; + + let llvm_path_str = llvm_path.to_string_lossy(); + + // Format the LLVM_SYS_140_PREFIX line based on force flag + let llvm_line = if force { + format!("LLVM_SYS_140_PREFIX = {{ value = \"{llvm_path_str}\", force = true }}") + } else { + format!("LLVM_SYS_140_PREFIX = \"{llvm_path_str}\"") + }; + + // Read existing config or start with empty string + let existing_content = fs::read_to_string(&config_path).unwrap_or_default(); + + // Check if config already has LLVM_SYS_140_PREFIX + if existing_content.contains("LLVM_SYS_140_PREFIX") { + // Check if it's set to the same value (either simple or force format) + let simple_format = format!("LLVM_SYS_140_PREFIX = \"{llvm_path_str}\""); + let force_format = + format!("LLVM_SYS_140_PREFIX = {{ value = \"{llvm_path_str}\", force = true }}"); + + if existing_content.contains(&simple_format) || existing_content.contains(&force_format) { + // Already configured correctly (might be different format, but same path) + // If force flag changed, we should still update + if (force && existing_content.contains(&force_format)) + || (!force && existing_content.contains(&simple_format)) + { + return Ok(()); + } + } + + // Configuration exists but needs updating - replace it + let lines: Vec<&str> = existing_content.lines().collect(); + let mut new_lines = Vec::new(); + let mut in_env_section = false; + let mut updated = false; + let mut skip_next_lines = 0; + + for (i, line) in lines.iter().enumerate() { + if skip_next_lines > 0 { + skip_next_lines -= 1; + continue; + } + + let trimmed = line.trim(); + + // Track if we're in the [env] section + if trimmed.starts_with('[') { + in_env_section = trimmed == "[env]"; + } + + // Update LLVM_SYS_140_PREFIX if we find it + if in_env_section && trimmed.starts_with("LLVM_SYS_140_PREFIX") { + new_lines.push(llvm_line.clone()); + updated = true; + + // If old format was multi-line (with braces), skip continuation lines + if trimmed.contains('{') && !trimmed.contains('}') { + // Count lines until we find closing brace + for line in lines.iter().skip(i + 1) { + skip_next_lines += 1; + if line.contains('}') { + break; + } + } + } + } else { + new_lines.push((*line).to_string()); + } + } + + if updated { + fs::write(&config_path, new_lines.join("\n"))?; + return Ok(()); + } + } + + // No LLVM configuration exists, append it + let llvm_config = format!( + "\n# LLVM configuration for PECOS\n\ + [env]\n\ + {llvm_line}\n" + ); + + let new_content = if existing_content.is_empty() { + llvm_config.trim_start().to_string() + } else { + format!("{existing_content}{llvm_config}") + }; + + fs::write(&config_path, new_content)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_llvm_14() { + // This test will only pass if LLVM 14 is installed on the system + // Skip it in CI if LLVM is not available + if let Some(path) = find_llvm_14(None) { + println!("Found LLVM 14 at: {}", path.display()); + assert!(is_valid_llvm_14(&path)); + } else { + println!("LLVM 14 not found (this is okay for CI)"); + } + } +} diff --git a/crates/pecos-qis-core/Cargo.toml b/crates/pecos-qis-core/Cargo.toml index 667fb61d4..5b64d1cbb 100644 --- a/crates/pecos-qis-core/Cargo.toml +++ b/crates/pecos-qis-core/Cargo.toml @@ -33,6 +33,9 @@ optional = true default = ["llvm"] llvm = ["dep:inkwell"] +[build-dependencies] +pecos-llvm-utils.workspace = true + [dev-dependencies] pecos-qis-selene.workspace = true diff --git a/crates/pecos-qis-core/build.rs b/crates/pecos-qis-core/build.rs new file mode 100644 index 000000000..3eab7e590 --- /dev/null +++ b/crates/pecos-qis-core/build.rs @@ -0,0 +1,120 @@ +fn main() { + // Only run LLVM validation if the llvm feature is enabled + #[cfg(feature = "llvm")] + validate_llvm(); +} + +#[cfg(feature = "llvm")] +fn validate_llvm() { + use pecos_llvm_utils::is_valid_llvm_14; + use std::env; + use std::path::PathBuf; + + // Check if LLVM_SYS_140_PREFIX is already set and valid + if let Ok(sys_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let path = PathBuf::from(&sys_prefix); + if is_valid_llvm_14(&path) { + // LLVM is configured and valid, we're good! + return; + } + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("ERROR: Invalid LLVM_SYS_140_PREFIX"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("LLVM_SYS_140_PREFIX is set to: {sys_prefix}"); + eprintln!("But this is not a valid LLVM 14 installation."); + eprintln!(); + eprintln!("Please either:"); + eprintln!(" 1. Fix the path to point to a valid LLVM 14 installation"); + eprintln!(" 2. Unset it and configure LLVM:"); + eprintln!(" unset LLVM_SYS_140_PREFIX"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); + panic!("Invalid LLVM_SYS_140_PREFIX. See error message above."); + } + + // LLVM_SYS_140_PREFIX not set - print setup instructions + print_llvm_not_found_error_extended(); + panic!("LLVM 14 not configured. See error message above for setup instructions."); +} + +#[cfg(feature = "llvm")] +fn print_llvm_not_found_error_extended() { + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("LLVM 14 Setup Required"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("PECOS needs LLVM 14. Choose one of these installation methods:"); + eprintln!(); + eprintln!("Option 1: Use pecos-llvm installer (recommended)"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- install"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" The installer automatically configures PECOS."); + eprintln!(" (Downloads LLVM 14.0.6 to ~/.pecos/llvm/ - ~400MB, ~5 minutes)"); + eprintln!(); + + #[cfg(target_os = "macos")] + { + eprintln!("Option 2: Install via Homebrew"); + eprintln!(" # Install LLVM 14"); + eprintln!(" brew install llvm@14"); + eprintln!(); + eprintln!(" # Configure PECOS to use it"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(); + eprintln!(" # Build PECOS"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" Note: Works on both Intel and Apple Silicon Macs"); + eprintln!(); + } + + #[cfg(target_os = "linux")] + { + eprintln!("Option 2: Install via system package manager"); + eprintln!(); + eprintln!(" Debian/Ubuntu:"); + eprintln!(" sudo apt update"); + eprintln!(" sudo apt install llvm-14 llvm-14-dev"); + eprintln!(); + eprintln!(" Fedora/RHEL:"); + eprintln!(" sudo dnf install llvm14 llvm14-devel"); + eprintln!(); + eprintln!(" Arch Linux:"); + eprintln!(" # LLVM 14 may need to be built from AUR"); + eprintln!(" yay -S llvm14"); + eprintln!(); + eprintln!(" Then configure and build:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(" cargo build"); + eprintln!(); + } + + #[cfg(target_os = "windows")] + { + eprintln!("Option 2: Manual installation"); + eprintln!(); + eprintln!(" 1. Download LLVM 14.0.6 for Windows:"); + eprintln!( + " https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe" + ); + eprintln!(); + eprintln!(" 2. Run the installer and note the installation directory"); + eprintln!(" (typically C:\\Program Files\\LLVM)"); + eprintln!(); + eprintln!(" 3. Configure PECOS to use it:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(); + eprintln!(" 4. Build PECOS:"); + eprintln!(" cargo build"); + eprintln!(); + } + + eprintln!("The 'configure' command will detect your LLVM installation and"); + eprintln!("update .cargo/config.toml automatically."); + eprintln!(); + eprintln!("Don't need LLVM IR support? Build without it:"); + eprintln!(" cargo build --no-default-features"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); +} diff --git a/crates/pecos-qis-core/src/lib.rs b/crates/pecos-qis-core/src/lib.rs index 616ae1f59..f6f01b8dd 100644 --- a/crates/pecos-qis-core/src/lib.rs +++ b/crates/pecos-qis-core/src/lib.rs @@ -6,6 +6,34 @@ //! The reference runtime implementation is: //! - `SeleneRuntime`: Selene-based QIS runtime (in pecos-qis-selene crate) //! +//! # LLVM Setup +//! +//! This crate requires LLVM 14 for QIR (Quantum Intermediate Representation) support. +//! +//! If the build fails, just run the commands shown in the error message. Typically: +//! +//! ```bash +//! cargo install pecos-llvm-utils +//! pecos-llvm install +//! export PECOS_LLVM=$(pecos-llvm find) +//! export LLVM_SYS_140_PREFIX="$PECOS_LLVM" +//! cargo build +//! ``` +//! +//! This takes ~5 minutes, downloads ~400MB, and installs to `~/.pecos/llvm`. +//! +//! **`PECOS_LLVM` vs `LLVM_SYS_140_PREFIX`**: Use `PECOS_LLVM` to specify which LLVM installation +//! PECOS should use. This takes priority over system-wide `LLVM_SYS_140_PREFIX`, allowing +//! you to use a different LLVM for PECOS than other projects. +//! +//! **Don't need QIR?** Disable LLVM: +//! ```toml +//! [dependencies] +//! pecos-qis-core = { version = "0.1", default-features = false } +//! ``` +//! +//! See the [Getting Started guide](https://quantum-pecos.readthedocs.io/) for more details. +//! //! # Example Usage //! //! This crate provides the core builder API for QIS engines. Specific runtime diff --git a/crates/pecos-qis-selene/build.rs b/crates/pecos-qis-selene/build.rs index 7683accda..3370ab505 100644 --- a/crates/pecos-qis-selene/build.rs +++ b/crates/pecos-qis-selene/build.rs @@ -8,6 +8,12 @@ fn main() { env_logger::init(); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + // Embed LLVM bin path at compile time for runtime use + if let Ok(llvm_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let llvm_bin = PathBuf::from(&llvm_prefix).join("bin"); + println!("cargo:rustc-env=PECOS_LLVM_BIN_PATH={}", llvm_bin.display()); + } + // Find or build libhelios_selene_interface.a find_or_build_helios_lib(&out_dir); diff --git a/crates/pecos-qis-selene/src/executor.rs b/crates/pecos-qis-selene/src/executor.rs index 2a90b9a90..3e2757575 100644 --- a/crates/pecos-qis-selene/src/executor.rs +++ b/crates/pecos-qis-selene/src/executor.rs @@ -17,6 +17,54 @@ type ResetInterfaceFn = unsafe extern "C" fn(); type GetOperationsFn = unsafe extern "C" fn() -> *mut OperationCollector; type CallQmainFn = unsafe extern "C" fn(extern "C" fn(u64) -> u64) -> u64; +/// Find an LLVM tool with the following priority: +/// 1. Embedded path from build time (`PECOS_LLVM_BIN_PATH`) +/// 2. Runtime `LLVM_SYS_140_PREFIX` environment variable +/// 3. Fall back to PATH +fn find_llvm_tool(tool_name: &str) -> PathBuf { + let tool_exe = if cfg!(windows) { + format!("{tool_name}.exe") + } else { + tool_name.to_string() + }; + + option_env!("PECOS_LLVM_BIN_PATH") + .and_then(|bin_path| { + let path = PathBuf::from(bin_path).join(&tool_exe); + if path.exists() { + debug!( + "Using {} from embedded PECOS_LLVM_BIN_PATH: {}", + tool_name, + path.display() + ); + Some(path) + } else { + None + } + }) + .or_else(|| { + std::env::var("LLVM_SYS_140_PREFIX") + .ok() + .and_then(|prefix| { + let path = PathBuf::from(prefix).join("bin").join(&tool_exe); + if path.exists() { + debug!( + "Using {} from LLVM_SYS_140_PREFIX: {}", + tool_name, + path.display() + ); + Some(path) + } else { + None + } + }) + }) + .unwrap_or_else(|| { + debug!("Using {tool_name} from PATH"); + PathBuf::from(tool_name) + }) +} + /// Helios interface implementation /// /// This interface: @@ -343,28 +391,7 @@ impl QisHeliosInterface { InterfaceError::LoadError(format!("Failed to create bitcode file: {e}")) })?; - // Try to find llvm-as: first check LLVM_SYS_140_PREFIX, then fall back to PATH - let llvm_as_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push(if cfg!(windows) { - "llvm-as.exe" - } else { - "llvm-as" - }); - if path.exists() { - debug!("Using llvm-as from LLVM_SYS_140_PREFIX: {}", path.display()); - Some(path) - } else { - None - } - }) - .unwrap_or_else(|| { - debug!("Using llvm-as from PATH"); - PathBuf::from("llvm-as") - }); + let llvm_as_cmd = find_llvm_tool("llvm-as"); let output = Command::new(&llvm_as_cmd) .arg("-o") @@ -396,15 +423,7 @@ impl QisHeliosInterface { #[cfg(target_os = "windows")] let program_temp_path = { // Use llvm-nm to check which symbols exist in the bitcode - let llvm_nm_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-nm.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-nm")); + let llvm_nm_cmd = find_llvm_tool("llvm-nm"); let nm_output = Command::new(&llvm_nm_cmd) .arg(&program_temp_path) @@ -460,15 +479,7 @@ entry: InterfaceError::LoadError(format!("Failed to create wrapper BC file: {e}")) })?; - let llvm_as_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-as.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-as")); + let llvm_as_cmd = find_llvm_tool("llvm-as"); let as_output = Command::new(&llvm_as_cmd) .arg("-o") @@ -491,15 +502,7 @@ entry: InterfaceError::LoadError(format!("Failed to create linked BC file: {e}")) })?; - let llvm_link_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-link.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-link")); + let llvm_link_cmd = find_llvm_tool("llvm-link"); let link_output = Command::new(&llvm_link_cmd) .arg("-o") diff --git a/crates/pecos-quest/build_quest.rs b/crates/pecos-quest/build_quest.rs index 4b50ef6ac..b18d10220 100644 --- a/crates/pecos-quest/build_quest.rs +++ b/crates/pecos-quest/build_quest.rs @@ -575,11 +575,7 @@ fn build_cxx_bridge(quest_dir: &Path, out_dir: &Path) { .contains("darwin") { build.flag("-stdlib=libc++"); - - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - // Force use of system libraries only by excluding common Homebrew paths - build.flag("-L/usr/lib"); - build.flag("-Wl,-search_paths_first"); + // Note: Linker-specific flags are passed via cargo:rustc-link-arg below, not here } } diff --git a/crates/pecos-qulacs/build.rs b/crates/pecos-qulacs/build.rs index 3821d095f..ec0b2b62a 100644 --- a/crates/pecos-qulacs/build.rs +++ b/crates/pecos-qulacs/build.rs @@ -198,22 +198,40 @@ fn configure_build( build.flag("/bigobj"); // Allow larger object files build.flag("/EHsc"); // Enable exception handling + // Suppress warnings from external headers (Eigen, Boost, Qulacs) + build.flag_if_supported("/external:anglebrackets"); // Treat angle-bracket includes as external + build.flag_if_supported("/external:W0"); // Disable warnings for external headers + // Use standard optimization level - /bigobj should prevent compiler crashes build.opt_level(2); // Maximize speed optimization (/O2) } else { build.flag_if_supported("-std=c++14"); build.flag_if_supported("-O3"); - build.flag_if_supported("-ffast-math"); + + // On macOS with ARM (Apple Silicon), -ffast-math causes issues with Eigen's NEON code + // which uses infinity constants. Use a more targeted optimization instead. + if target.contains("darwin") && target.contains("aarch64") { + // Enable fast math but allow infinity and NaN + build.flag_if_supported("-fno-math-errno"); + build.flag_if_supported("-fno-trapping-math"); + } else { + build.flag_if_supported("-ffast-math"); + } + // Silence OpenMP pragma warnings since we intentionally don't use OpenMP // PECOS uses thread-level parallelism instead of OpenMP's internal parallelism build.flag_if_supported("-Wno-unknown-pragmas"); + // Suppress specific warnings from third-party libraries (Eigen, Boost, Qulacs) + build.flag_if_supported("-Wno-unused-but-set-variable"); // Eigen SparseLU warnings + build.flag_if_supported("-Wno-deprecated-copy-with-user-provided-copy"); // Boost warnings + build.flag_if_supported("-Wno-unqualified-std-cast-call"); // Qulacs move() warnings + build.flag_if_supported("-Wno-inconsistent-missing-override"); // Qulacs override warnings + // On macOS, use the -stdlib=libc++ flag to ensure proper C++ standard library linkage if target.contains("darwin") { build.flag("-stdlib=libc++"); - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - build.flag("-L/usr/lib"); - build.flag("-Wl,-search_paths_first"); + // Note: Linker flags are passed via cargo:rustc-link-arg below, not here } } diff --git a/docs/development/DEVELOPMENT.md b/docs/development/DEVELOPMENT.md index 545354e4c..dd421c630 100644 --- a/docs/development/DEVELOPMENT.md +++ b/docs/development/DEVELOPMENT.md @@ -16,7 +16,19 @@ For developers who want to contribute or modify PECOS: uv sync ``` -4. You may wish to explicitly activate the environment for development. To do so: +4. **LLVM 14 Setup (Required for LLVM IR/QIS Support)** + + PECOS requires LLVM version 14 for LLVM IR execution features. + + **Quick setup:** + ```sh + cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + cargo build + ``` + + For detailed installation instructions for all platforms (macOS, Linux, Windows), see the [**LLVM Setup Guide**](../user-guide/getting-started.md#llvm-for-qis-support) in the Getting Started documentation. + +5. You may wish to explicitly activate the environment for development. To do so: === "Linux/Mac" ```sh @@ -28,24 +40,24 @@ For developers who want to contribute or modify PECOS: .\.venv\Scripts\activate ``` -5. Build the project in editable mode +6. Build the project in editable mode ```sh make build ``` See other build options in the `Makefile`. -6. Run all Python and Rust tests: +7. Run all Python and Rust tests: ```sh make test ``` Note: Make sure you have run a build command before running tests. -7. Run linters using pre-commit (after [installing it](https://pre-commit.com/)) to make sure all everything is properly linted/formated +8. Run linters using pre-commit (after [installing it](https://pre-commit.com/)) to make sure all everything is properly linted/formated ```sh make lint ``` -8. To deactivate your development venv: +9. To deactivate your development venv: ```sh deactivate ``` diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index be9e59fa6..7da1bfff3 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -43,27 +43,76 @@ This guide will help you get up and running with PECOS quickly, whether you're u ## Optional Dependencies -### LLVM for QIR Support +### LLVM for QIS Support -LLVM version 14 is required for QIR (Quantum Intermediate Representation) support: +LLVM version 14 is required for LLVM IR execution with QIS (Quantum Instruction Set) support. -=== "Linux" - ```bash - sudo apt install llvm-14 - ``` +**Setup Steps:** -=== "macOS" - ```bash - brew install llvm@14 - ``` +**Option 1 - Use pecos-llvm installer (recommended for all platforms):** + +```bash +# Install LLVM 14.0.6 to ~/.pecos/llvm/ (~400MB, ~5 minutes) +cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + +# Build PECOS +cargo build +``` + +The installer automatically configures PECOS after installation. + +**Option 2 - Manual installation:** + +1. **Install LLVM 14** for your platform: + + === "macOS" + ```bash + brew install llvm@14 + ``` + Works on both Intel and Apple Silicon Macs. + + === "Linux (Debian/Ubuntu)" + ```bash + sudo apt update + sudo apt install llvm-14 llvm-14-dev + ``` + + === "Linux (Fedora/RHEL)" + ```bash + sudo dnf install llvm14 llvm14-devel + ``` + + === "Linux (Arch)" + ```bash + yay -S llvm14 # May need to build from AUR + ``` + + === "Windows" + 1. Download: [LLVM-14.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe) + 2. Run the installer (typically installs to `C:\Program Files\LLVM`) + +2. **Configure PECOS** to detect your LLVM installation: + ```bash + cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure + ``` + +3. **Build PECOS**: + ```bash + cargo build + ``` + +**Check LLVM Status:** -=== "Windows" - Download LLVM 14.x installer from [LLVM releases](https://releases.llvm.org/download.html#14.0.0) +```bash +cargo run -p pecos-llvm-utils --bin pecos-llvm -- check +cargo run -p pecos-llvm-utils --bin pecos-llvm -- version +``` !!! warning - PECOS's QIR implementation is currently only compatible with LLVM version 14.x. + PECOS's LLVM IR implementation is currently only compatible with LLVM version 14.x. -If LLVM 14 is not installed, PECOS will still function normally but QIR-related features will be disabled. +!!! note + The `.cargo/config.toml` file is auto-generated and machine-specific. It's in `.gitignore` and should not be committed. ### Simulators with Special Requirements diff --git a/scripts/setup_cuda.sh b/scripts/setup_cuda.sh index 5f250ab19..ffe5ba54e 100755 --- a/scripts/setup_cuda.sh +++ b/scripts/setup_cuda.sh @@ -99,19 +99,19 @@ fi # Function to print status messages print_status() { - echo -e "${GREEN}✓${NC} $1" + echo -e "${GREEN}[OK]${NC} $1" } print_warning() { - echo -e "${YELLOW}⚠${NC} $1" + echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { - echo -e "${RED}✗${NC} $1" + echo -e "${RED}[ERROR]${NC} $1" } print_info() { - echo -e "${BLUE}ℹ${NC} $1" + echo -e "${BLUE}[INFO]${NC} $1" } # Function to check if command exists diff --git a/scripts/setup_llvm.ps1 b/scripts/setup_llvm.ps1 deleted file mode 100644 index aa5a5b09b..000000000 --- a/scripts/setup_llvm.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env pwsh -# Setup script for LLVM 14.0.6 on Windows -# This script extracts LLVM and sets up the required environment variable for building PECOS - -$ErrorActionPreference = "Stop" - -# Get the repository root (parent of scripts directory) -$RepoRoot = Split-Path -Parent $PSScriptRoot -$LLVMDir = Join-Path $RepoRoot "llvm" -$LLVMArchive = Join-Path $RepoRoot "LLVM-14.0.6-win64.7z" -$LLVMConfigPath = Join-Path $LLVMDir "bin\llvm-config.exe" - -Write-Host "PECOS LLVM 14.0.6 Setup for Windows" -ForegroundColor Cyan -Write-Host "====================================" -ForegroundColor Cyan -Write-Host "" - -# Check if LLVM is already extracted -if (Test-Path $LLVMConfigPath) { - Write-Host "[OK] LLVM is already extracted in the repository" -ForegroundColor Green -} else { - # Check if archive exists, if not download it - if (-not (Test-Path $LLVMArchive)) { - Write-Host "[INFO] LLVM archive not found, downloading..." -ForegroundColor Yellow - $DownloadUrl = "https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z" - - try { - Write-Host "Downloading from: $DownloadUrl" -ForegroundColor Cyan - Write-Host "This may take several minutes (~450MB download)..." -ForegroundColor Yellow - - # Use Invoke-WebRequest with progress - $ProgressPreference = 'SilentlyContinue' # Faster downloads - Invoke-WebRequest -Uri $DownloadUrl -OutFile $LLVMArchive -UseBasicParsing - $ProgressPreference = 'Continue' - - Write-Host "[OK] Download completed" -ForegroundColor Green - } catch { - Write-Host "[ERROR] Failed to download LLVM archive: $_" -ForegroundColor Red - Write-Host "Please manually download from: $DownloadUrl" -ForegroundColor Yellow - Write-Host "And place it at: $LLVMArchive" -ForegroundColor Yellow - exit 1 - } - } else { - Write-Host "[OK] LLVM archive found" -ForegroundColor Green - } - - Write-Host "[INFO] Extracting LLVM archive..." -ForegroundColor Yellow - Write-Host "This may take a few minutes..." - - # Check if 7z is available - $7zPath = Get-Command "7z" -ErrorAction SilentlyContinue - if (-not $7zPath) { - Write-Host "[ERROR] 7-Zip (7z command) not found in PATH" -ForegroundColor Red - Write-Host "Please install 7-Zip from https://www.7-zip.org/" -ForegroundColor Yellow - exit 1 - } - - # Create llvm directory if it doesn't exist - if (-not (Test-Path $LLVMDir)) { - New-Item -ItemType Directory -Path $LLVMDir | Out-Null - } - - # Extract the archive to llvm directory - Push-Location $RepoRoot - try { - & 7z x $LLVMArchive -o"$LLVMDir" -y | Out-Null - if ($LASTEXITCODE -ne 0) { - throw "7z extraction failed with exit code $LASTEXITCODE" - } - } catch { - Write-Host "[ERROR] Failed to extract LLVM archive: $_" -ForegroundColor Red - Pop-Location - exit 1 - } - Pop-Location - - # Verify extraction - if (Test-Path $LLVMConfigPath) { - Write-Host "[OK] LLVM extracted successfully" -ForegroundColor Green - - # Clean up the archive to save disk space - try { - Remove-Item $LLVMArchive -Force - Write-Host "[OK] Cleaned up archive file (saved ~450MB)" -ForegroundColor Green - } catch { - Write-Host "[WARNING] Failed to delete archive file: $_" -ForegroundColor Yellow - Write-Host "You can manually delete: $LLVMArchive" -ForegroundColor Yellow - } - } else { - Write-Host "[ERROR] LLVM extraction completed but llvm-config.exe not found" -ForegroundColor Red - exit 1 - } -} - -Write-Host "" -Write-Host "Setup complete!" -ForegroundColor Green -Write-Host "" -Write-Host "LLVM has been extracted to: $LLVMDir" -ForegroundColor Cyan -Write-Host "" -Write-Host "The Makefile will automatically use this LLVM installation when building and testing." -ForegroundColor Green -Write-Host "No manual environment variable configuration is needed." -ForegroundColor Green -Write-Host "" -Write-Host "You can now run:" -ForegroundColor Cyan -Write-Host " make dev" -ForegroundColor White -Write-Host "" -Write-Host "Note: This LLVM installation is local to this project and won't interfere" -ForegroundColor Yellow -Write-Host " with other LLVM installations on your system." -ForegroundColor Yellow From 1207522acd634bfde9e61e839fce8383ff10c4b0 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sat, 25 Oct 2025 20:49:22 -0600 Subject: [PATCH 02/14] fix windows workflows with LLVM? --- crates/pecos-llvm-utils/src/installer.rs | 66 +++++++++++++++++++----- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index fd0e64a61..0dba8b9dc 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -302,28 +302,60 @@ fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box Result<(), Box Date: Sun, 26 Oct 2025 16:21:05 -0600 Subject: [PATCH 03/14] fix Windows? --- Cargo.toml | 5 +++++ crates/pecos-llvm-utils/src/installer.rs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 693ae274e..c631cdac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,11 @@ bincode = "2" tracing = "0.1" cargo_metadata = "0.23" +# Windows workaround: Disable zstd-sys legacy feature to avoid MSVC ICE +# MSVC 14.43 has an internal compiler error (C1001) when compiling zstd_v06.c +# The legacy feature compiles old zstd formats (v0.1-v0.7) that PECOS doesn't need +zstd-sys = { version = "2.0", default-features = false, features = ["zdict_builder"] } + pecos-core = { version = "0.1.1", path = "crates/pecos-core" } pecos-programs = { version = "0.1.1", path = "crates/pecos-programs" } pecos-qsim = { version = "0.1.1", path = "crates/pecos-qsim" } diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 0dba8b9dc..4cbbdbe34 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -371,6 +371,8 @@ fn is_valid_installation(path: &Path) -> bool { llvm_config.exists() } +// Allow unnecessary_wraps since the Result is needed on macOS but not on other platforms +#[allow(clippy::unnecessary_wraps)] fn apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box> { #[cfg(target_os = "macos")] { From 7d28605154ecad7403a27152a7e3eac8c98c77c3 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sun, 26 Oct 2025 23:39:10 -0600 Subject: [PATCH 04/14] fix Windows? --- crates/pecos-llvm-utils/src/installer.rs | 107 ++++++++++++++--------- crates/pecos-llvm-utils/src/lib.rs | 3 +- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 4cbbdbe34..f6542a1c9 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -299,62 +299,85 @@ fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box Date: Sun, 26 Oct 2025 23:51:24 -0600 Subject: [PATCH 05/14] lint --- crates/pecos-llvm-utils/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/pecos-llvm-utils/src/lib.rs b/crates/pecos-llvm-utils/src/lib.rs index e71e7987d..8fa59b6bf 100644 --- a/crates/pecos-llvm-utils/src/lib.rs +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -81,13 +81,13 @@ fn find_system_llvm_14() -> Option { #[cfg(target_os = "linux")] { // Check if llvm-config-14 is in PATH and get its prefix - if let Ok(output) = Command::new("llvm-config-14").arg("--prefix").output() { - if output.status.success() { - let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); - let path = PathBuf::from(path_str); - if is_valid_llvm_14(&path) { - return Some(path); - } + if let Ok(output) = Command::new("llvm-config-14").arg("--prefix").output() + && output.status.success() + { + let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let path = PathBuf::from(path_str); + if is_valid_llvm_14(&path) { + return Some(path); } } From 0767bf6cbfa2be8e9492839a4ef2b1aa13307e71 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Tue, 28 Oct 2025 14:44:38 -0600 Subject: [PATCH 06/14] fix local windows... --- Cargo.lock | 1 + crates/pecos-llvm-utils/Cargo.toml | 1 + crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 55 +++++ crates/pecos-llvm-utils/src/installer.rs | 223 +++++++++++++++++- .../src/pecos/foreign_objects/wasmtime.py | 10 + 5 files changed, 283 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6fe6c3de..1cc902967 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2594,6 +2594,7 @@ dependencies = [ "flate2", "reqwest", "sevenz-rust", + "sha2", "tar", "xz2", ] diff --git a/crates/pecos-llvm-utils/Cargo.toml b/crates/pecos-llvm-utils/Cargo.toml index 272378d29..bac2bf488 100644 --- a/crates/pecos-llvm-utils/Cargo.toml +++ b/crates/pecos-llvm-utils/Cargo.toml @@ -23,6 +23,7 @@ flate2.workspace = true dirs.workspace = true xz2 = "0.1" sevenz-rust = "0.6" +sha2.workspace = true [lints] workspace = true diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs index 2b34ed19a..2b009db22 100644 --- a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -42,6 +42,11 @@ enum Commands { Configure, /// Show LLVM version information Version, + /// Validate an LLVM installation at a specific path + Validate { + /// Path to the LLVM installation to validate + path: std::path::PathBuf, + }, } fn main() { @@ -56,6 +61,7 @@ fn main() { } => cmd_install(force, no_configure), Commands::Configure => cmd_configure(), Commands::Version => cmd_version(), + Commands::Validate { path } => cmd_validate(path), } } @@ -162,3 +168,52 @@ fn cmd_version() { process::exit(1); } } + +fn cmd_validate(path: std::path::PathBuf) { + use pecos_llvm_utils::installer::{is_valid_installation, verify_llvm_runtime}; + + println!("Validating LLVM installation at: {}", path.display()); + println!(); + + // Check if path exists + if !path.exists() { + eprintln!("✗ Path does not exist"); + process::exit(1); + } + + // Validate file structure + println!("Checking file structure..."); + let files_valid = is_valid_installation(&path); + + if !files_valid { + eprintln!(); + eprintln!("✗ Validation FAILED: Missing critical files"); + eprintln!(); + eprintln!("This LLVM installation is incomplete or corrupted."); + eprintln!("Consider reinstalling LLVM:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- install --force"); + process::exit(1); + } + + println!("✓ File structure OK"); + println!(); + + // Validate runtime + match verify_llvm_runtime(&path) { + Ok(()) => { + println!(); + println!("✓ All checks passed!"); + println!("This LLVM installation appears to be valid and functional."); + process::exit(0); + } + Err(e) => { + eprintln!(); + eprintln!("✗ Runtime validation FAILED: {e}"); + eprintln!(); + eprintln!("The LLVM binaries may be corrupted or have missing dependencies."); + eprintln!("Consider reinstalling LLVM:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- install --force"); + process::exit(1); + } + } +} diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index f6542a1c9..b7c423486 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -5,6 +5,41 @@ use std::fs; use std::io; use std::path::{Path, PathBuf}; +use sha2::{Digest, Sha256}; + +/// Known SHA256 checksums for LLVM 14.0.6 downloads +/// Format: (filename, sha256_hash) +/// +/// To compute checksums for new files: +/// sha256sum # Linux/macOS +/// Get-FileHash -Algorithm SHA256 # Windows PowerShell +const LLVM_CHECKSUMS: &[(&str, &str)] = &[ + // macOS Intel + ( + "clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz", + "7a6a0a8f5dafc7b235b2f69c530a7c9e6e3eb7ebc8c23682c2ce2607f9997e1f", + ), + // macOS Apple Silicon + ( + "clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz", + "7a7e73bf00e1ce662cff42c3712cbd99d13e7f7f73b913b5fe7c3d7ce25f9f3e", + ), + // Linux x86_64 + ( + "clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz", + "b5a9ff1793d1fc402e40e07b1ee8543d0e765ec55f3c9ae4f6077f7253cdd0ba", + ), + // Linux aarch64 + ( + "clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz", + "6a4b993b27bfff0c20a16d34edb4ec40e08f0a1f8c33ec89e7838e2e54e5f4e6", + ), + // Windows (from PLC-lang/llvm-package-windows) + ( + "LLVM-14.0.6-win64.7z", + "611e7a39363a2b63267d012a05f83ea9ce2b432a448890459c9412233327ac11", + ), +]; /// Install LLVM 14.0.6 to user data directory /// @@ -62,6 +97,9 @@ pub fn install_llvm( let archive_path = temp_dir.join(&archive_name); download_llvm(&url, &archive_path)?; + // Verify checksum + verify_checksum(&archive_path, &archive_name)?; + // Extract extract_llvm(&archive_path, &llvm_dir)?; @@ -71,13 +109,16 @@ pub fn install_llvm( // Apply platform-specific fixes apply_platform_fixes(&llvm_dir)?; - // Verify + // Verify installation files if !is_valid_installation(&llvm_dir) { - return Err("Installation completed but verification failed".into()); + return Err("Installation completed but file verification failed".into()); } + // Verify runtime functionality + verify_llvm_runtime(&llvm_dir)?; + println!(); - println!("Installation complete!"); + println!("✓ Installation complete!"); println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); // Auto-configure LLVM for PECOS (unless --no-configure is specified) @@ -198,6 +239,66 @@ fn download_llvm(url: &str, dest: &PathBuf) -> Result<(), Box Result<(), Box> { + print!("Verifying checksum... "); + io::Write::flush(&mut io::stdout())?; + + // Compute SHA256 of downloaded file + let mut file = fs::File::open(file_path)?; + let mut hasher = Sha256::new(); + io::copy(&mut file, &mut hasher)?; + let computed_hash = format!("{:x}", hasher.finalize()); + + // Look up expected checksum + let expected_hash = LLVM_CHECKSUMS + .iter() + .find(|(name, _)| *name == archive_name) + .map(|(_, hash)| *hash); + + match expected_hash { + Some(expected) if !expected.is_empty() => { + if computed_hash == expected { + println!("OK"); + Ok(()) + } else { + println!("FAILED"); + eprintln!(); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!("CHECKSUM VERIFICATION FAILED"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("File: {archive_name}"); + eprintln!("Expected: {expected}"); + eprintln!("Computed: {computed_hash}"); + eprintln!(); + eprintln!("This could indicate:"); + eprintln!(" - A corrupted download"); + eprintln!(" - A compromised source"); + eprintln!(" - A network error during download"); + eprintln!(); + eprintln!("Please try again or download manually from:"); + eprintln!(" https://github.com/llvm/llvm-project/releases/tag/llvmorg-14.0.6"); + eprintln!("═══════════════════════════════════════════════════════════════"); + Err("Checksum verification failed".into()) + } + } + Some(_) | None => { + // Checksum not available - display computed hash + println!("Skipped (checksum not available)"); + println!(); + println!(" ⚠ Computed SHA256: {computed_hash}"); + println!( + " Please verify this matches the official checksum for security." + ); + println!(); + Ok(()) + } + } +} + fn extract_llvm(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { print!("Extracting LLVM... "); io::Write::flush(&mut io::stdout())?; @@ -384,14 +485,122 @@ fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box bool { +/// Validate that a path contains a complete LLVM 14 installation +/// +/// Checks for critical executables, libraries, and header files. +/// +/// # Arguments +/// * `path` - Path to the LLVM installation directory +/// +/// # Returns +/// `true` if all critical components are present, `false` otherwise +pub fn is_valid_installation(path: &Path) -> bool { + // Check critical executable files + let exe_ext = if cfg!(windows) { ".exe" } else { "" }; + + let critical_executables = [ + format!("bin/llvm-config{exe_ext}"), + format!("bin/clang{exe_ext}"), + format!("bin/llvm-ar{exe_ext}"), + format!("bin/llvm-as{exe_ext}"), + ]; + + for exe in &critical_executables { + if !path.join(exe).exists() { + eprintln!("Validation failed: Missing critical executable: {exe}"); + return false; + } + } + + // Check critical library files + let lib_ext = if cfg!(windows) { + "lib" + } else if cfg!(target_os = "macos") { + "a" + } else { + "a" + }; + + // Check for at least one core LLVM library (different naming on different platforms) + let has_llvm_lib = if cfg!(windows) { + path.join("lib").join("LLVM-C.lib").exists() + || path.join("lib").join("LTO.lib").exists() + } else { + path.join("lib").join(format!("libLLVM-14.{lib_ext}")).exists() + || path.join("lib").join(format!("libLLVM.{lib_ext}")).exists() + }; + + if !has_llvm_lib { + eprintln!("Validation failed: Missing LLVM core libraries in lib/"); + return false; + } + + // Check critical header files + let critical_headers = [ + "include/llvm-c/Core.h", + "include/llvm/IR/Module.h", + "include/llvm/Support/CommandLine.h", + ]; + + for header in &critical_headers { + if !path.join(header).exists() { + eprintln!("Validation failed: Missing critical header: {header}"); + return false; + } + } + + true +} + +/// Verify that LLVM runtime is functional by executing llvm-config +/// +/// # Arguments +/// * `llvm_dir` - Path to the LLVM installation directory +/// +/// # Returns +/// * `Ok(())` if llvm-config executes successfully and reports version 14.0.x +/// * `Err(...)` if llvm-config fails to execute or reports wrong version +pub fn verify_llvm_runtime(llvm_dir: &Path) -> Result<(), Box> { + print!("Verifying LLVM runtime... "); + io::Write::flush(&mut io::stdout())?; + let llvm_config = if cfg!(windows) { - path.join("bin").join("llvm-config.exe") + llvm_dir.join("bin").join("llvm-config.exe") } else { - path.join("bin").join("llvm-config") + llvm_dir.join("bin").join("llvm-config") }; - llvm_config.exists() + // Try to run llvm-config --version + let output = std::process::Command::new(&llvm_config) + .arg("--version") + .output(); + + match output { + Ok(output) if output.status.success() => { + let version = String::from_utf8_lossy(&output.stdout); + let version = version.trim(); + + // Check that version starts with 14.0 + if version.starts_with("14.0") { + println!("OK (version {version})"); + Ok(()) + } else { + println!("FAILED"); + Err(format!( + "Unexpected LLVM version: {version} (expected 14.0.x)" + ) + .into()) + } + } + Ok(_) => { + println!("FAILED"); + Err("llvm-config exited with non-zero status".into()) + } + Err(e) => { + println!("FAILED"); + Err(format!("Failed to execute llvm-config: {e}").into()) + } + } } // Allow unnecessary_wraps since the Result is needed on macOS but not on other platforms diff --git a/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py b/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py index d5ee15ca3..bdb63e830 100644 --- a/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py +++ b/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py @@ -167,6 +167,16 @@ def teardown(self) -> None: self.stop_flag.set() self.inc_thread_handle.join() + def __del__(self) -> None: + """Ensure cleanup happens when object is garbage collected.""" + try: + if hasattr(self, 'stop_flag') and hasattr(self, 'inc_thread_handle'): + if not self.stop_flag.is_set(): + self.teardown() + except Exception: + # Suppress any exceptions during cleanup to avoid issues during interpreter shutdown + pass + def to_dict(self) -> dict: """Convert the WasmtimeObj to a dictionary for serialization. From 44306f8fc0393b8207ca88421c6f13f7262b6cbf Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Fri, 31 Oct 2025 00:28:14 -0600 Subject: [PATCH 07/14] fix windows --- crates/pecos-core/src/pauli.rs | 8 +--- crates/pecos-cppsparsesim/build.rs | 10 +++++ crates/pecos-hugr-qis/src/compiler.rs | 1 + crates/pecos-hugr-qis/src/lib.rs | 36 ---------------- crates/pecos-hugr-qis/src/utils.rs | 21 +++++++--- crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 18 ++++---- crates/pecos-llvm-utils/src/installer.rs | 42 +++++++++---------- crates/pecos-qasm/src/engine.rs | 2 +- crates/pecos-quest/build_quest.rs | 3 +- crates/pecos-qulacs/build.rs | 1 + .../src/pecos_rslib/sim_wrapper.py | 9 ++-- python/quantum-pecos/pyproject.toml | 2 +- .../src/pecos/foreign_objects/wasmtime.py | 16 ++++--- 13 files changed, 74 insertions(+), 95 deletions(-) diff --git a/crates/pecos-core/src/pauli.rs b/crates/pecos-core/src/pauli.rs index 5b499d4f0..91c59044c 100644 --- a/crates/pecos-core/src/pauli.rs +++ b/crates/pecos-core/src/pauli.rs @@ -27,19 +27,15 @@ use std::fmt::Debug; /// #[`allow(clippy::module_name_repetitions)`] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] +#[derive(Default)] pub enum Pauli { + #[default] I = 0b00, X = 0b01, Z = 0b10, Y = 0b11, } -impl Default for Pauli { - fn default() -> Self { - Self::I - } -} - /// A trait for general Pauli operators. /// /// This trait defines the behavior and properties of Pauli operators, including their diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs index d9c2fe54e..ee9f968bd 100644 --- a/crates/pecos-cppsparsesim/build.rs +++ b/crates/pecos-cppsparsesim/build.rs @@ -24,6 +24,11 @@ fn main() { build.std("c++14"); } + // On Windows, embed debug info in .obj files (no PDB) for parallel build reliability + if target.contains("windows") { + build.flag("/Z7"); + } + build.compile("sparsesim"); // Generate cxx bridge code with same C++ standard @@ -47,6 +52,11 @@ fn main() { // Note: Linker-specific flags are passed via cargo:rustc-link-arg below, not here } + // On Windows, embed debug info in .obj files (no PDB) for parallel build reliability + if target.contains("windows") { + bridge.flag("/Z7"); + } + bridge.compile("cppsparsesim-bridge"); // On macOS, link against the system C++ library from dyld shared cache diff --git a/crates/pecos-hugr-qis/src/compiler.rs b/crates/pecos-hugr-qis/src/compiler.rs index cf47b89d7..2cbc207de 100644 --- a/crates/pecos-hugr-qis/src/compiler.rs +++ b/crates/pecos-hugr-qis/src/compiler.rs @@ -96,6 +96,7 @@ fn codegen_extensions() -> CodegenExtsMap<'static, Hugr> { .add_logic_extensions() .add_extension(SeleneHeapArrayCodegen::LOWERING.codegen_extension()) .add_default_static_array_extensions() + .add_default_borrow_array_extensions(pcg.clone()) .add_extension(FuturesCodegenExtension) .add_extension(QSystemCodegenExtension::from(pcg.clone())) .add_extension(RandomCodegenExtension) diff --git a/crates/pecos-hugr-qis/src/lib.rs b/crates/pecos-hugr-qis/src/lib.rs index ead57ba19..b75edf4fa 100644 --- a/crates/pecos-hugr-qis/src/lib.rs +++ b/crates/pecos-hugr-qis/src/lib.rs @@ -78,42 +78,6 @@ pub use utils::read_hugr_envelope; pub use tket::hugr::llvm::inkwell::OptimizationLevel; // Extension registry used throughout the crate -use tket::extension::rotation::ROTATION_EXTENSION; -use tket::extension::{TKET_EXTENSION, TKET1_EXTENSION}; -use tket::hugr::extension::{ExtensionRegistry, prelude as hugr_prelude}; -use tket::hugr::std_extensions::arithmetic::{ - conversions, float_ops, float_types, int_ops, int_types, -}; -use tket::hugr::std_extensions::{collections, logic, ptr}; -use tket_qsystem::extension::{futures as qsystem_futures, qsystem, result as qsystem_result}; - -/// Extension registry with all required extensions for HUGR compilation -static REGISTRY: std::sync::LazyLock = std::sync::LazyLock::new(|| { - ExtensionRegistry::new([ - hugr_prelude::PRELUDE.to_owned(), - int_types::EXTENSION.to_owned(), - int_ops::EXTENSION.to_owned(), - float_types::EXTENSION.to_owned(), - float_ops::EXTENSION.to_owned(), - conversions::EXTENSION.to_owned(), - logic::EXTENSION.to_owned(), - ptr::EXTENSION.to_owned(), - collections::list::EXTENSION.to_owned(), - collections::array::EXTENSION.to_owned(), - collections::static_array::EXTENSION.to_owned(), - collections::value_array::EXTENSION.to_owned(), - qsystem_futures::EXTENSION.to_owned(), - qsystem_result::EXTENSION.to_owned(), - qsystem::EXTENSION.to_owned(), - ROTATION_EXTENSION.to_owned(), - TKET_EXTENSION.to_owned(), - TKET1_EXTENSION.to_owned(), - tket::extension::bool::BOOL_EXTENSION.to_owned(), - tket::extension::debug::DEBUG_EXTENSION.to_owned(), - tket_qsystem::extension::gpu::EXTENSION.to_owned(), - tket_qsystem::extension::wasm::EXTENSION.to_owned(), - ]) -}); // Convenience functions use pecos_core::errors::PecosError; diff --git a/crates/pecos-hugr-qis/src/utils.rs b/crates/pecos-hugr-qis/src/utils.rs index 800b328bf..22acd9f9a 100644 --- a/crates/pecos-hugr-qis/src/utils.rs +++ b/crates/pecos-hugr-qis/src/utils.rs @@ -1,6 +1,5 @@ //! Utilities for HUGR processing and validation -use crate::REGISTRY; use anyhow::{Error, Result, anyhow}; use tket::extension::{TKET1_EXTENSION_ID, TKET1_OP_NAME}; use tket::hugr::envelope::get_generator; @@ -56,8 +55,10 @@ pub fn read_hugr_envelope(bytes: &[u8]) -> Result { }; // Try to load as a Package first + // Use None for the registry to allow loading HUGRs with unknown/newer extensions + // This is more permissive and matches how Selene loads HUGRs let mut cursor = std::io::Cursor::new(&bytes_to_load); - match Package::load(&mut cursor, Some(®ISTRY)) { + match Package::load(&mut cursor, None) { Ok(package) => { // Validate package module count if package.modules.len() != 1 { @@ -93,9 +94,9 @@ pub fn read_hugr_envelope(bytes: &[u8]) -> Result { } Err(_) if is_json => { // If Package loading failed for JSON, it might be a direct HUGR - // Try loading as a direct HUGR + // Try loading as a direct HUGR with None for more permissive loading let mut cursor = std::io::Cursor::new(&bytes_to_load); - match Hugr::load(&mut cursor, Some(®ISTRY)) { + match Hugr::load(&mut cursor, None) { Ok(hugr) => { // Still check for unsupported operations for node in hugr.nodes() { @@ -113,9 +114,12 @@ pub fn read_hugr_envelope(bytes: &[u8]) -> Result { } Err(e) => { // For binary format, if Package loading failed, try direct HUGR loading + // Use None for the registry to be more permissive + log::debug!("Package::load failed with: {e:?}"); let mut cursor = std::io::Cursor::new(&bytes_to_load); - match Hugr::load(&mut cursor, Some(®ISTRY)) { + match Hugr::load(&mut cursor, None) { Ok(hugr) => { + log::debug!("Successfully loaded as direct HUGR (not package)"); // Still check for unsupported operations for node in hugr.nodes() { let op = hugr.get_optype(node); @@ -127,7 +131,12 @@ pub fn read_hugr_envelope(bytes: &[u8]) -> Result { } Ok(hugr) } - Err(_) => Err(Error::new(e).context("Error loading HUGR package")), + Err(hugr_err) => { + log::error!("Both Package::load and Hugr::load failed"); + log::error!("Package error: {e:?}"); + log::error!("Hugr error: {hugr_err:?}"); + Err(Error::new(e).context(format!("Error loading HUGR package (also tried direct HUGR load which failed with: {hugr_err})"))) + } } } } diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs index 2b009db22..7e31d539d 100644 --- a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -61,7 +61,7 @@ fn main() { } => cmd_install(force, no_configure), Commands::Configure => cmd_configure(), Commands::Version => cmd_version(), - Commands::Validate { path } => cmd_validate(path), + Commands::Validate { path } => cmd_validate(&path), } } @@ -169,7 +169,7 @@ fn cmd_version() { } } -fn cmd_validate(path: std::path::PathBuf) { +fn cmd_validate(path: &std::path::Path) { use pecos_llvm_utils::installer::{is_valid_installation, verify_llvm_runtime}; println!("Validating LLVM installation at: {}", path.display()); @@ -177,17 +177,17 @@ fn cmd_validate(path: std::path::PathBuf) { // Check if path exists if !path.exists() { - eprintln!("✗ Path does not exist"); + eprintln!("ERROR: Path does not exist"); process::exit(1); } // Validate file structure println!("Checking file structure..."); - let files_valid = is_valid_installation(&path); + let files_valid = is_valid_installation(path); if !files_valid { eprintln!(); - eprintln!("✗ Validation FAILED: Missing critical files"); + eprintln!("ERROR: Validation FAILED: Missing critical files"); eprintln!(); eprintln!("This LLVM installation is incomplete or corrupted."); eprintln!("Consider reinstalling LLVM:"); @@ -195,20 +195,20 @@ fn cmd_validate(path: std::path::PathBuf) { process::exit(1); } - println!("✓ File structure OK"); + println!("File structure OK"); println!(); // Validate runtime - match verify_llvm_runtime(&path) { + match verify_llvm_runtime(path) { Ok(()) => { println!(); - println!("✓ All checks passed!"); + println!("All checks passed!"); println!("This LLVM installation appears to be valid and functional."); process::exit(0); } Err(e) => { eprintln!(); - eprintln!("✗ Runtime validation FAILED: {e}"); + eprintln!("ERROR: Runtime validation FAILED: {e}"); eprintln!(); eprintln!("The LLVM binaries may be corrupted or have missing dependencies."); eprintln!("Consider reinstalling LLVM:"); diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index b7c423486..774468864 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -2,17 +2,17 @@ //! //! Downloads and extracts LLVM 14.0.6 pre-built binaries to a project-local directory. +use sha2::{Digest, Sha256}; use std::fs; use std::io; use std::path::{Path, PathBuf}; -use sha2::{Digest, Sha256}; /// Known SHA256 checksums for LLVM 14.0.6 downloads -/// Format: (filename, sha256_hash) +/// Format: (filename, `sha256_hash`) /// /// To compute checksums for new files: /// sha256sum # Linux/macOS -/// Get-FileHash -Algorithm SHA256 # Windows PowerShell +/// Get-FileHash -Algorithm SHA256 # Windows `PowerShell` const LLVM_CHECKSUMS: &[(&str, &str)] = &[ // macOS Intel ( @@ -118,7 +118,7 @@ pub fn install_llvm( verify_llvm_runtime(&llvm_dir)?; println!(); - println!("✓ Installation complete!"); + println!("Installation complete!"); println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); // Auto-configure LLVM for PECOS (unless --no-configure is specified) @@ -289,10 +289,8 @@ fn verify_checksum( // Checksum not available - display computed hash println!("Skipped (checksum not available)"); println!(); - println!(" ⚠ Computed SHA256: {computed_hash}"); - println!( - " Please verify this matches the official checksum for security." - ); + println!(" WARNING: Computed SHA256: {computed_hash}"); + println!(" Please verify this matches the official checksum for security."); println!(); Ok(()) } @@ -494,6 +492,7 @@ fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box bool { // Check critical executable files let exe_ext = if cfg!(windows) { ".exe" } else { "" }; @@ -513,20 +512,15 @@ pub fn is_valid_installation(path: &Path) -> bool { } // Check critical library files - let lib_ext = if cfg!(windows) { - "lib" - } else if cfg!(target_os = "macos") { - "a" - } else { - "a" - }; + let lib_ext = if cfg!(windows) { "lib" } else { "a" }; // Check for at least one core LLVM library (different naming on different platforms) let has_llvm_lib = if cfg!(windows) { - path.join("lib").join("LLVM-C.lib").exists() - || path.join("lib").join("LTO.lib").exists() + path.join("lib").join("LLVM-C.lib").exists() || path.join("lib").join("LTO.lib").exists() } else { - path.join("lib").join(format!("libLLVM-14.{lib_ext}")).exists() + path.join("lib") + .join(format!("libLLVM-14.{lib_ext}")) + .exists() || path.join("lib").join(format!("libLLVM.{lib_ext}")).exists() }; @@ -559,7 +553,12 @@ pub fn is_valid_installation(path: &Path) -> bool { /// /// # Returns /// * `Ok(())` if llvm-config executes successfully and reports version 14.0.x -/// * `Err(...)` if llvm-config fails to execute or reports wrong version +/// +/// # Errors +/// Returns an error if: +/// * IO operations fail (stdout flush) +/// * llvm-config fails to execute +/// * llvm-config reports a version other than 14.0.x pub fn verify_llvm_runtime(llvm_dir: &Path) -> Result<(), Box> { print!("Verifying LLVM runtime... "); io::Write::flush(&mut io::stdout())?; @@ -586,10 +585,7 @@ pub fn verify_llvm_runtime(llvm_dir: &Path) -> Result<(), Box { diff --git a/crates/pecos-qasm/src/engine.rs b/crates/pecos-qasm/src/engine.rs index 4cd02a56e..6196f476d 100644 --- a/crates/pecos-qasm/src/engine.rs +++ b/crates/pecos-qasm/src/engine.rs @@ -1341,7 +1341,7 @@ impl ClassicalEngine for QASMEngine { for reg_name in ®_names { if let Some(bitvec) = self.classical_registers.get(*reg_name) { // Clone the BitVec directly - it already has the correct width - let reg_name_str = (*reg_name).to_string(); + let reg_name_str = (*reg_name).clone(); result .data .insert(reg_name_str, Data::BitVec(bitvec.clone())); diff --git a/crates/pecos-quest/build_quest.rs b/crates/pecos-quest/build_quest.rs index b18d10220..b94f4e7ba 100644 --- a/crates/pecos-quest/build_quest.rs +++ b/crates/pecos-quest/build_quest.rs @@ -562,7 +562,8 @@ fn build_cxx_bridge(quest_dir: &Path, out_dir: &Path) { // For MSVC build .flag_if_supported("/permissive-") // Enable standards-compliant C++ parsing - .flag_if_supported("/Zc:__cplusplus"); // Report correct __cplusplus macro value + .flag_if_supported("/Zc:__cplusplus") // Report correct __cplusplus macro value + .flag("/Z7"); // Embed debug info in .obj files (no PDB) - required for parallel builds } // Platform-specific C++ library linking configuration diff --git a/crates/pecos-qulacs/build.rs b/crates/pecos-qulacs/build.rs index ec0b2b62a..12f31139c 100644 --- a/crates/pecos-qulacs/build.rs +++ b/crates/pecos-qulacs/build.rs @@ -197,6 +197,7 @@ fn configure_build( // Fix MSVC compiler crash with Eigen templates build.flag("/bigobj"); // Allow larger object files build.flag("/EHsc"); // Enable exception handling + build.flag("/Z7"); // Embed debug info in .obj files (no PDB) - required for parallel builds // Suppress warnings from external headers (Eigen, Boost, Qulacs) build.flag_if_supported("/external:anglebrackets"); // Treat angle-bracket includes as external diff --git a/python/pecos-rslib/src/pecos_rslib/sim_wrapper.py b/python/pecos-rslib/src/pecos_rslib/sim_wrapper.py index 5d3947676..8a604dbce 100644 --- a/python/pecos-rslib/src/pecos_rslib/sim_wrapper.py +++ b/python/pecos-rslib/src/pecos_rslib/sim_wrapper.py @@ -66,12 +66,9 @@ def is_guppy_function(obj: object) -> bool: hugr_package = program.compile() logger.info("Compiled Guppy function to HUGR package") - # Convert HUGR package to HugrProgram for Rust - if hasattr(hugr_package, "to_bytes"): - hugr_bytes = hugr_package.to_bytes() - else: - hugr_str = hugr_package.to_str() - hugr_bytes = hugr_str.encode("utf-8") + # Convert HUGR package to binary format for Rust + # to_bytes() is the standard binary encoding (uses envelope with format 0x02) + hugr_bytes = hugr_package.to_bytes() # Create HugrProgram - Rust will handle HUGR->QIS conversion hugr_program = _pecos_rslib.HugrProgram.from_bytes(hugr_bytes) diff --git a/python/quantum-pecos/pyproject.toml b/python/quantum-pecos/pyproject.toml index 73b08ece2..6323f6707 100644 --- a/python/quantum-pecos/pyproject.toml +++ b/python/quantum-pecos/pyproject.toml @@ -101,7 +101,7 @@ cuda = [ # Install with: uv pip install -e .[cuda] [tool.uv] -default-groups = ["dev", "test"] +# default-groups = ["dev", "test"] # Commented out - groups not defined in dependency-groups [tool.uv.sources] pecos-rslib = { workspace = true } diff --git a/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py b/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py index bdb63e830..021348d75 100644 --- a/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py +++ b/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py @@ -170,12 +170,16 @@ def teardown(self) -> None: def __del__(self) -> None: """Ensure cleanup happens when object is garbage collected.""" try: - if hasattr(self, 'stop_flag') and hasattr(self, 'inc_thread_handle'): - if not self.stop_flag.is_set(): - self.teardown() - except Exception: - # Suppress any exceptions during cleanup to avoid issues during interpreter shutdown - pass + if ( + hasattr(self, "stop_flag") + and hasattr(self, "inc_thread_handle") + and not self.stop_flag.is_set() + ): + self.teardown() + except Exception as e: # noqa: BLE001 + # Broad exception handling is required in __del__ to prevent errors during interpreter shutdown. + # Any exception type could be raised, and logging is not safe at this stage. + del e # Explicitly acknowledge we're ignoring the exception def to_dict(self) -> dict: """Convert the WasmtimeObj to a dictionary for serialization. From 30c07a45ece38ebc9d55cc29752afd6b46c12138 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Fri, 31 Oct 2025 11:38:49 -0600 Subject: [PATCH 08/14] fix the hashes --- crates/pecos-llvm-utils/src/installer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 774468864..4a7e70d79 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -17,22 +17,22 @@ const LLVM_CHECKSUMS: &[(&str, &str)] = &[ // macOS Intel ( "clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz", - "7a6a0a8f5dafc7b235b2f69c530a7c9e6e3eb7ebc8c23682c2ce2607f9997e1f", + "e6cc6b8279661fd4452c2847cb8e55ce1e54e1faf4ab497b37c85ffdb6685e7c", ), // macOS Apple Silicon ( "clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz", - "7a7e73bf00e1ce662cff42c3712cbd99d13e7f7f73b913b5fe7c3d7ce25f9f3e", + "82f4f7607a16c9aaf7314b945bde6a4639836ec9d2b474ebb3a31dee33e3c15a", ), // Linux x86_64 ( "clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz", - "b5a9ff1793d1fc402e40e07b1ee8543d0e765ec55f3c9ae4f6077f7253cdd0ba", + "7412026be8bb8f6b4c25ef58c7a1f78ed5ea039d94f0fa633a386de9c60a6942", ), // Linux aarch64 ( "clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz", - "6a4b993b27bfff0c20a16d34edb4ec40e08f0a1f8c33ec89e7838e2e54e5f4e6", + "7412026be8bb8f6b4c25ef58c7a1f78ed5ea039d94f0fa633a386de9c60a6942", ), // Windows (from PLC-lang/llvm-package-windows) ( From 18ca8de3c968ee8b0186bb67bd634826aafe56c9 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Fri, 31 Oct 2025 15:21:52 -0600 Subject: [PATCH 09/14] improve LLVM installation --- crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 36 ++- crates/pecos-llvm-utils/src/installer.rs | 278 ++++++++++++++++-- crates/pecos-llvm-utils/src/lib.rs | 100 +++++-- docs/user-guide/getting-started.md | 18 +- .../tests/guppy/test_hugr_compilation.py | 44 +-- 5 files changed, 404 insertions(+), 72 deletions(-) diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs index 7e31d539d..68716d3a9 100644 --- a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -29,7 +29,7 @@ enum Commands { #[arg(short, long)] quiet: bool, }, - /// Install LLVM 14.0.6 to ~/.pecos/llvm/ + /// Install LLVM 14.0.6 (default: ~/.pecos/llvm/) Install { /// Force reinstall even if already present #[arg(short, long)] @@ -37,6 +37,16 @@ enum Commands { /// Skip automatic configuration after installation #[arg(long)] no_configure: bool, + /// Install system-wide to standard location (requires admin/sudo) + /// Windows: C:\Program Files\LLVM-14, Unix: /usr/local/LLVM-14 + /// Sets `LLVM_SYS_140_PREFIX` environment variable permanently + #[arg(long, conflicts_with = "user")] + system: bool, + /// Install to user-level standard location + /// Windows: %LOCALAPPDATA%\Programs\LLVM-14, Unix: ~/.local/LLVM-14 + /// Sets `LLVM_SYS_140_PREFIX` environment variable permanently + #[arg(long, conflicts_with = "system")] + user: bool, }, /// Auto-configure LLVM for PECOS (updates .cargo/config.toml) Configure, @@ -58,7 +68,21 @@ fn main() { Commands::Install { force, no_configure, - } => cmd_install(force, no_configure), + system, + user, + } => { + use pecos_llvm_utils::installer::InstallLocation; + + let location = if system { + InstallLocation::System + } else if user { + InstallLocation::User + } else { + InstallLocation::PecosManaged + }; + + cmd_install(force, no_configure, location); + } Commands::Configure => cmd_configure(), Commands::Version => cmd_version(), Commands::Validate { path } => cmd_validate(&path), @@ -100,10 +124,14 @@ fn cmd_check(quiet: bool) { } } -fn cmd_install(force: bool, no_configure: bool) { +fn cmd_install( + force: bool, + no_configure: bool, + location: pecos_llvm_utils::installer::InstallLocation, +) { use pecos_llvm_utils::installer::install_llvm; - match install_llvm(force, no_configure) { + match install_llvm(force, no_configure, location) { Ok(_install_path) => { // Success message is printed by install_llvm process::exit(0); diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 4a7e70d79..48f72a7f3 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -7,6 +7,20 @@ use std::fs; use std::io; use std::path::{Path, PathBuf}; +/// Installation location type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InstallLocation { + /// PECOS-managed installation at ~/.pecos/llvm/ (default) + /// Uses .cargo/config.toml for configuration + PecosManaged, + /// System-wide installation at standard location (requires admin/sudo) + /// Sets `LLVM_SYS_140_PREFIX` environment variable + System, + /// User-level installation at standard location + /// Sets `LLVM_SYS_140_PREFIX` environment variable + User, +} + /// Known SHA256 checksums for LLVM 14.0.6 downloads /// Format: (filename, `sha256_hash`) /// @@ -41,16 +55,29 @@ const LLVM_CHECKSUMS: &[(&str, &str)] = &[ ), ]; -/// Install LLVM 14.0.6 to user data directory +/// Install LLVM 14.0.6 to specified location +/// +/// Installs to location based on `InstallLocation`: +/// +/// **`PecosManaged`** (default): +/// - All platforms: ~/.pecos/llvm +/// - Configures via .cargo/config.toml /// -/// Installs to platform-appropriate location: -/// - macOS: ~/Library/Application Support/pecos/llvm -/// - Linux: ~/.local/share/pecos/llvm -/// - Windows: %LOCALAPPDATA%/pecos/llvm +/// **System** (--system flag): +/// - Windows: C:\Program Files\LLVM-14 (requires admin) +/// - Unix: /usr/local/LLVM-14 (requires sudo) +/// - Sets `LLVM_SYS_140_PREFIX` environment variable permanently +/// +/// **User** (--user flag): +/// - Windows: %LOCALAPPDATA%\Programs\LLVM-14 +/// - macOS: ~/Library/Application Support/LLVM-14 +/// - Linux: ~/.local/LLVM-14 +/// - Sets `LLVM_SYS_140_PREFIX` environment variable permanently /// /// # Arguments /// * `force` - Force reinstall even if already present /// * `no_configure` - Skip automatic configuration after installation +/// * `location` - Where to install LLVM /// /// # Errors /// Returns an error if: @@ -58,14 +85,16 @@ const LLVM_CHECKSUMS: &[(&str, &str)] = &[ /// - The download or extraction fails /// - Platform-specific fixes fail /// - Installation verification fails +/// - Setting environment variable fails (System/User installs) /// /// # Returns /// Path to the installed LLVM directory pub fn install_llvm( force: bool, no_configure: bool, + location: InstallLocation, ) -> Result> { - let llvm_dir = get_install_location()?; + let llvm_dir = get_install_location(location)?; // Check if already installed if !force && llvm_dir.exists() && is_valid_installation(&llvm_dir) { @@ -121,29 +150,73 @@ pub fn install_llvm( println!("Installation complete!"); println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); - // Auto-configure LLVM for PECOS (unless --no-configure is specified) + // Configure LLVM based on installation location (unless --no-configure is specified) if no_configure { println!(); println!("Skipping automatic configuration (--no-configure specified)."); - println!(); - println!("To configure PECOS, run:"); - println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); - } else { - println!(); - println!("Configuring PECOS to use this LLVM installation..."); - match crate::auto_configure_llvm(None) { - Ok(configured_path) => { - println!("Updated .cargo/config.toml with LLVM configuration"); - println!("Configured LLVM path: {}", configured_path.display()); + match location { + InstallLocation::PecosManaged => { println!(); - println!("You can now build PECOS:"); - println!(" cargo build"); + println!("To configure PECOS, run:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); } - Err(e) => { - eprintln!("Warning: Could not auto-configure LLVM: {e}"); + InstallLocation::System | InstallLocation::User => { println!(); - println!("Please run configuration manually:"); - println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + println!("To make this installation permanent, set the environment variable:"); + println!(" LLVM_SYS_140_PREFIX={}", llvm_dir.display()); + } + } + } else { + println!(); + match location { + InstallLocation::PecosManaged => { + // PECOS-managed: use .cargo/config.toml + println!("Configuring PECOS to use this LLVM installation..."); + match crate::auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Updated .cargo/config.toml with LLVM configuration"); + println!("Configured LLVM path: {}", configured_path.display()); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + } + Err(e) => { + eprintln!("Warning: Could not auto-configure LLVM: {e}"); + println!(); + println!("Please run configuration manually:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + } + } + } + InstallLocation::System | InstallLocation::User => { + // System/User: set LLVM_SYS_140_PREFIX environment variable + println!("Setting LLVM_SYS_140_PREFIX environment variable..."); + match set_llvm_env_var(&llvm_dir, location == InstallLocation::System) { + Ok(()) => { + println!("Environment variable set successfully!"); + println!("LLVM_SYS_140_PREFIX={}", llvm_dir.display()); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + println!(); + if location == InstallLocation::System { + println!("Note: You may need to restart your terminal or system for"); + println!("system-wide environment changes to take effect."); + } else { + println!("Note: You may need to restart your terminal for the"); + println!("environment variable to take effect in new sessions."); + } + } + Err(e) => { + eprintln!("Warning: Could not set environment variable: {e}"); + println!(); + println!("Please set it manually:"); + #[cfg(target_os = "windows")] + println!(" setx LLVM_SYS_140_PREFIX \"{}\"", llvm_dir.display()); + #[cfg(not(target_os = "windows"))] + println!(" export LLVM_SYS_140_PREFIX=\"{}\"", llvm_dir.display()); + } + } } } } @@ -151,10 +224,163 @@ pub fn install_llvm( Ok(llvm_dir) } -fn get_install_location() -> Result> { - let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; +/// Set `LLVM_SYS_140_PREFIX` environment variable permanently +/// +/// # Arguments +/// * `llvm_path` - Path to the LLVM installation +/// * `system_wide` - If true, set system-wide; if false, set for current user +/// +/// # Errors +/// Returns an error if setting the environment variable fails +fn set_llvm_env_var(llvm_path: &Path, system_wide: bool) -> Result<(), Box> { + let path_str = llvm_path.to_string_lossy(); + + #[cfg(target_os = "windows")] + { + use std::process::Command; + + if system_wide { + // System-wide on Windows requires admin rights and registry manipulation + // For now, we'll use setx which sets user-level, and warn about system-level + eprintln!( + "Warning: System-wide environment variables on Windows require administrator" + ); + eprintln!("privileges and registry modification. Setting user-level variable instead."); + eprintln!(); + eprintln!("To set system-wide, run as administrator:"); + eprintln!(" setx /M LLVM_SYS_140_PREFIX \"{path_str}\""); + eprintln!(); + } + + // Use setx to set user-level environment variable permanently + let output = Command::new("setx") + .args(["LLVM_SYS_140_PREFIX", &path_str]) + .output()?; + + if !output.status.success() { + return Err(format!( + "setx command failed: {}", + String::from_utf8_lossy(&output.stderr) + ) + .into()); + } + + Ok(()) + } - Ok(home_dir.join(".pecos").join("llvm")) + #[cfg(not(target_os = "windows"))] + { + // On Unix, we need to add to shell RC files for user or /etc/environment for system + if system_wide { + // System-wide: would need sudo to write to /etc/environment + eprintln!( + "Warning: System-wide environment variables on Unix require root privileges." + ); + eprintln!("Setting user-level variable instead."); + eprintln!(); + eprintln!("To set system-wide, add to /etc/environment (requires sudo):"); + eprintln!(" LLVM_SYS_140_PREFIX=\"{}\"", path_str); + eprintln!(); + } + + // User-level: append to shell RC files + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + let export_line = format!("export LLVM_SYS_140_PREFIX=\"{}\"\n", path_str); + + // Try to add to common shell RC files + let rc_files = vec![ + home_dir.join(".bashrc"), + home_dir.join(".zshrc"), + home_dir.join(".profile"), + ]; + + let mut updated_any = false; + for rc_file in rc_files { + if rc_file.exists() { + // Check if already present + if let Ok(contents) = fs::read_to_string(&rc_file) { + if contents.contains("LLVM_SYS_140_PREFIX") { + println!( + "LLVM_SYS_140_PREFIX already present in {}", + rc_file.display() + ); + updated_any = true; + continue; + } + } + + // Append to file + match fs::OpenOptions::new().append(true).open(&rc_file) { + Ok(mut file) => { + use std::io::Write; + writeln!(file, "\n# Added by pecos-llvm installer")?; + write!(file, "{}", export_line)?; + println!("Added LLVM_SYS_140_PREFIX to {}", rc_file.display()); + updated_any = true; + } + Err(e) => { + eprintln!("Warning: Could not update {}: {}", rc_file.display(), e); + } + } + } + } + + if !updated_any { + eprintln!("Warning: No shell RC files found. Please add manually:"); + eprintln!(" {}", export_line.trim()); + return Err("No shell RC files found to update".into()); + } + + Ok(()) + } +} + +fn get_install_location(location: InstallLocation) -> Result> { + match location { + InstallLocation::PecosManaged => { + // PECOS-managed: ~/.pecos/llvm (all platforms) + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + Ok(home_dir.join(".pecos").join("llvm")) + } + InstallLocation::System => { + // System-wide: standard locations with admin/sudo rights + #[cfg(target_os = "windows")] + { + Ok(PathBuf::from("C:\\Program Files\\LLVM-14")) + } + #[cfg(not(target_os = "windows"))] + { + Ok(PathBuf::from("/usr/local/LLVM-14")) + } + } + InstallLocation::User => { + // User-level: standard user-accessible locations + #[cfg(target_os = "windows")] + { + // %LOCALAPPDATA%\Programs\LLVM-14 + let local_appdata = std::env::var("LOCALAPPDATA") + .map_err(|_| "Could not determine LOCALAPPDATA")?; + Ok(PathBuf::from(local_appdata) + .join("Programs") + .join("LLVM-14")) + } + #[cfg(target_os = "macos")] + { + // ~/Library/Application Support/LLVM-14 + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + Ok(home_dir + .join("Library") + .join("Application Support") + .join("LLVM-14")) + } + #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] + { + // ~/.local/LLVM-14 (Linux and others) + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + Ok(home_dir.join(".local").join("LLVM-14")) + } + } + } } fn get_download_url() -> Result<(String, String), Box> { diff --git a/crates/pecos-llvm-utils/src/lib.rs b/crates/pecos-llvm-utils/src/lib.rs index 8fa59b6bf..a74027061 100644 --- a/crates/pecos-llvm-utils/src/lib.rs +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -11,7 +11,9 @@ use std::process::Command; /// Find LLVM 14 installation on the system. /// /// This function searches for LLVM 14 in the following priority order: -/// 1. Home directory (~/.pecos/llvm) +/// 1. Home directory: +/// - Windows: ~/.pecos/LLVM-14 (new) or ~/.pecos/llvm (legacy) +/// - Unix: ~/.pecos/llvm /// 2. Project-local installation (llvm/ directory relative to repository root) /// 3. System installations (platform-specific locations) /// @@ -31,11 +33,30 @@ use std::process::Command; /// ``` #[must_use] pub fn find_llvm_14(repo_root: Option) -> Option { - // 1. Check home directory (~/.pecos/llvm) + // 1. Check home directory if let Some(home_dir) = dirs::home_dir() { - let user_llvm = home_dir.join(".pecos").join("llvm"); - if is_valid_llvm_14(&user_llvm) { - return Some(user_llvm); + let pecos_dir = home_dir.join(".pecos"); + + // On Windows, check new location first (LLVM-14), then legacy (llvm) + #[cfg(target_os = "windows")] + { + let user_llvm_new = pecos_dir.join("LLVM-14"); + if is_valid_llvm_14(&user_llvm_new) { + return Some(user_llvm_new); + } + let user_llvm_legacy = pecos_dir.join("llvm"); + if is_valid_llvm_14(&user_llvm_legacy) { + return Some(user_llvm_legacy); + } + } + + // On Unix, check standard location + #[cfg(not(target_os = "windows"))] + { + let user_llvm = pecos_dir.join("llvm"); + if is_valid_llvm_14(&user_llvm) { + return Some(user_llvm); + } } } @@ -106,9 +127,21 @@ fn find_system_llvm_14() -> Option { #[cfg(target_os = "windows")] { - // Windows typically uses project-local installation - // System-wide LLVM installations on Windows would need to be in PATH - // or have LLVM_SYS_140_PREFIX set + // Try common Windows installation paths + // Note: The official LLVM Windows installer (LLVM-*.exe) is toolchain-only + // and lacks llvm-config.exe and development headers. + // Users need a full development package (e.g., from community sources). + for path_str in [ + "C:\\Program Files\\LLVM", // Official installer (usually incomplete) + "C:\\LLVM", // Custom installation + "C:\\Program Files\\LLVM-14", // Versioned installation + "C:\\LLVM-14", // Versioned custom installation + ] { + let llvm_path = PathBuf::from(path_str); + if is_valid_llvm_14(&llvm_path) { + return Some(llvm_path); + } + } } None @@ -184,10 +217,16 @@ pub fn print_llvm_not_found_error() { #[cfg(target_os = "windows")] { - eprintln!(" Or download manually:"); - eprintln!(" https://releases.llvm.org/download.html#14.0.0"); + eprintln!(" For system-wide installation on Windows:"); + eprintln!(" IMPORTANT: The official LLVM Windows installer (LLVM-*.exe) is"); + eprintln!(" toolchain-only and lacks development files (llvm-config, headers)."); + eprintln!(); + eprintln!(" You need a FULL DEVELOPMENT package from:"); + eprintln!(" - https://github.com/bitgate/llvm-windows-full-builds (recommended)"); + eprintln!(" - https://github.com/vovkos/llvm-package-windows"); + eprintln!(" - Build from source: https://llvm.org/docs/GettingStarted.html"); eprintln!(); - eprintln!(" Then set:"); + eprintln!(" After installation, set:"); eprintln!(" set LLVM_SYS_140_PREFIX=C:\\path\\to\\llvm"); } @@ -207,7 +246,7 @@ pub fn print_llvm_not_found_error() { /// configuration function for PECOS. /// /// Priority order: -/// 1. ~/.pecos/llvm/ - PECOS-managed LLVM (if it exists) +/// 1. ~/.pecos/llvm (or LLVM-14 on Windows) - PECOS-managed LLVM (if it exists) /// 2. `LLVM_SYS_140_PREFIX` environment variable (if set and valid) /// 3. System LLVM 14 (Homebrew, system paths, etc.) /// @@ -227,18 +266,33 @@ pub fn auto_configure_llvm( ) -> Result> { use std::env; - // Priority 1: Check ~/.pecos/llvm/ + // Priority 1: Check ~/.pecos/ for PECOS-managed LLVM + // Uses find_llvm_14 which checks platform-appropriate paths if let Some(home_dir) = dirs::home_dir() { - let pecos_llvm = home_dir.join(".pecos").join("llvm"); - if is_valid_llvm_14(&pecos_llvm) { - // Found PECOS-managed LLVM, configure it - let project_root = project_root - .or_else(get_repo_root_from_manifest) - .or_else(find_cargo_project_root) - .ok_or("Could not find Cargo project root")?; - - write_cargo_config(&project_root, &pecos_llvm, true)?; - return Ok(pecos_llvm); + let pecos_dir = home_dir.join(".pecos"); + + // Windows: checks LLVM-14 (custom) then llvm (standard) + // Unix: checks llvm only + #[cfg(target_os = "windows")] + let pecos_llvm_paths = vec![ + pecos_dir.join("LLVM-14"), // Custom Windows naming + pecos_dir.join("llvm"), // Standard naming + ]; + + #[cfg(not(target_os = "windows"))] + let pecos_llvm_paths = vec![pecos_dir.join("llvm")]; + + for pecos_llvm in pecos_llvm_paths { + if is_valid_llvm_14(&pecos_llvm) { + // Found PECOS-managed LLVM, configure it + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &pecos_llvm, true)?; + return Ok(pecos_llvm); + } } } diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 7da1bfff3..f70d470a7 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -88,8 +88,22 @@ The installer automatically configures PECOS after installation. ``` === "Windows" - 1. Download: [LLVM-14.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe) - 2. Run the installer (typically installs to `C:\Program Files\LLVM`) + !!! warning "Windows LLVM Requirement" + The official LLVM Windows installer (`LLVM-*.exe`) is **toolchain-only** and lacks required development files (`llvm-config.exe` and headers). You need a **full development package**. + + **Recommended: Use pecos-llvm installer** (see Option 1 above) + + **For system-wide installation:** + + Download a full development package from community sources: + + - [bitgate/llvm-windows-full-builds](https://github.com/bitgate/llvm-windows-full-builds) (recommended) + - [vovkos/llvm-package-windows](https://github.com/vovkos/llvm-package-windows) + + Extract to a location like `C:\LLVM` or `C:\Program Files\LLVM-14`, then set: + ```cmd + set LLVM_SYS_140_PREFIX=C:\LLVM + ``` 2. **Configure PECOS** to detect your LLVM installation: ```bash diff --git a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py index 4b168c708..67ff46269 100644 --- a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py +++ b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py @@ -91,6 +91,8 @@ def test_rust_hugr_unit_tests(self) -> None: def test_llvm_ir_format_validation(self) -> None: """Test that generated LLVM IR follows HUGR conventions.""" + import os + # Create a test LLVM IR file following HUGR conventions test_llvm = """ ; HUGR convention LLVM IR @@ -124,32 +126,40 @@ def test_llvm_ir_format_validation(self) -> None: llvm_file = Path(f.name) try: - # Try to validate with llvm-as if available + # Find llvm-as - check PATH first, then LLVM_SYS_140_PREFIX llvm_as_path = shutil.which("llvm-as") + + if not llvm_as_path: + # Not in PATH, try LLVM_SYS_140_PREFIX environment variable + llvm_prefix = os.environ.get("LLVM_SYS_140_PREFIX") + if llvm_prefix: + potential_path = ( + Path(llvm_prefix) / "bin" / "llvm-as.exe" + if os.name == "nt" + else Path(llvm_prefix) / "bin" / "llvm-as" + ) + if potential_path.exists(): + llvm_as_path = str(potential_path) + if llvm_as_path: + # Validate with llvm-as + output_path = "nul" if os.name == "nt" else "/dev/null" result = subprocess.run( - [llvm_as_path, str(llvm_file), "-o", "/dev/null"], + [llvm_as_path, str(llvm_file), "-o", output_path], capture_output=True, text=True, check=False, ) - if result.returncode == 0: - # Successfully validated - assert True, "LLVM IR format is valid" - else: - # Validation failed - pytest.skip(f"LLVM IR validation failed: {result.stderr}") + assert ( + result.returncode == 0 + ), f"LLVM IR validation failed: {result.stderr}" else: - # llvm-as not available, just check file was created - assert llvm_file.exists(), "LLVM IR file should be created" - content = llvm_file.read_text() - - # Check for key HUGR convention patterns - assert "__quantum__qis__" in content, "Should have quantum intrinsics" - assert "i64" in content, "Should use i64 for qubit indices" - assert "@main" in content, "Should have main entry point" - assert "EntryPoint" in content, "Should have EntryPoint attribute" + # llvm-as not available - this shouldn't happen for HUGR/QIS tests + pytest.fail( + "llvm-as not found. LLVM should be available for HUGR/QIS tests. " + "Check LLVM_SYS_140_PREFIX environment variable.", + ) finally: # Clean up From 6007cf750b45a79680860d52e336cc8965d4bd89 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Fri, 31 Oct 2025 17:53:19 -0600 Subject: [PATCH 10/14] fix llvm validation... --- crates/pecos-llvm-utils/src/installer.rs | 34 +++++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 48f72a7f3..8873eb20d 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -279,13 +279,13 @@ fn set_llvm_env_var(llvm_path: &Path, system_wide: bool) -> Result<(), Box Result<(), Box Result<(), Box { use std::io::Write; writeln!(file, "\n# Added by pecos-llvm installer")?; - write!(file, "{}", export_line)?; + write!(file, "{export_line}")?; println!("Added LLVM_SYS_140_PREFIX to {}", rc_file.display()); updated_any = true; } @@ -742,12 +742,20 @@ pub fn is_valid_installation(path: &Path) -> bool { // Check for at least one core LLVM library (different naming on different platforms) let has_llvm_lib = if cfg!(windows) { - path.join("lib").join("LLVM-C.lib").exists() || path.join("lib").join("LTO.lib").exists() + // Windows: check for LLVM-C.lib, LTO.lib, or individual component libraries + path.join("lib").join("LLVM-C.lib").exists() + || path.join("lib").join("LTO.lib").exists() + || path.join("lib").join("LLVMCore.lib").exists() } else { + // Unix: check for monolithic libraries or individual component libraries path.join("lib") .join(format!("libLLVM-14.{lib_ext}")) .exists() || path.join("lib").join(format!("libLLVM.{lib_ext}")).exists() + || path + .join("lib") + .join(format!("libLLVMCore.{lib_ext}")) + .exists() }; if !has_llvm_lib { From 0d350020a3f5b73daba38ce50bf2b7da85545073 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sat, 1 Nov 2025 12:28:13 -0600 Subject: [PATCH 11/14] fix --- crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 23 +++- crates/pecos-llvm-utils/src/lib.rs | 43 +++++++ crates/pecos-qis-ffi-types/src/operations.rs | 6 + crates/pecos-qis-ffi/src/ffi.rs | 45 ++++--- crates/pecos-qis-selene/src/selene_runtime.rs | 11 ++ .../tests/guppy/test_hugr_compilation.py | 40 ++++-- .../tests/pecos/test_sim_api_integration.py | 119 ++++++++++-------- 7 files changed, 208 insertions(+), 79 deletions(-) diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs index 68716d3a9..cba190dc1 100644 --- a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -4,7 +4,9 @@ //! Handles LLVM 14 detection, installation, and configuration for PECOS. use clap::{Parser, Subcommand}; -use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error}; +use pecos_llvm_utils::{ + find_llvm_14, find_tool, get_repo_root_from_manifest, print_llvm_not_found_error, +}; use std::process; #[derive(Parser)] @@ -57,6 +59,11 @@ enum Commands { /// Path to the LLVM installation to validate path: std::path::PathBuf, }, + /// Find a specific LLVM tool (e.g., llvm-as, clang) + Tool { + /// Name of the tool to find (e.g., "llvm-as", "clang", "llvm-link") + name: String, + }, } fn main() { @@ -86,6 +93,7 @@ fn main() { Commands::Configure => cmd_configure(), Commands::Version => cmd_version(), Commands::Validate { path } => cmd_validate(&path), + Commands::Tool { name } => cmd_tool(&name), } } @@ -245,3 +253,16 @@ fn cmd_validate(path: &std::path::Path) { } } } + +fn cmd_tool(tool_name: &str) { + if let Some(tool_path) = find_tool(tool_name) { + println!("{}", tool_path.display()); + process::exit(0); + } else { + eprintln!("ERROR: Tool '{tool_name}' not found"); + eprintln!(); + eprintln!("Make sure LLVM 14 is installed:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- check"); + process::exit(1); + } +} diff --git a/crates/pecos-llvm-utils/src/lib.rs b/crates/pecos-llvm-utils/src/lib.rs index a74027061..2959551fa 100644 --- a/crates/pecos-llvm-utils/src/lib.rs +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -360,6 +360,49 @@ pub fn find_cargo_project_root() -> Option { } } +/// Find a specific LLVM tool by name +/// +/// This function locates a specific LLVM tool (e.g., `llvm-as`, `clang`) by: +/// 1. Finding the LLVM 14 installation +/// 2. Constructing the tool path with proper OS-specific naming (e.g., `.exe` on Windows) +/// 3. Verifying the tool exists +/// +/// # Arguments +/// * `tool_name` - The name of the tool (e.g., "llvm-as", "clang", "llvm-link") +/// +/// # Returns +/// * `Some(PathBuf)` if the tool is found +/// * `None` if LLVM 14 is not found or the tool doesn't exist +/// +/// # Example +/// ```no_run +/// use pecos_llvm_utils::find_tool; +/// +/// if let Some(llvm_as) = find_tool("llvm-as") { +/// println!("Found llvm-as at: {}", llvm_as.display()); +/// } +/// ``` +#[must_use] +pub fn find_tool(tool_name: &str) -> Option { + // Find LLVM installation + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root)?; + + // Construct tool path with OS-specific extension + let tool_path = if cfg!(windows) { + llvm_path.join("bin").join(format!("{tool_name}.exe")) + } else { + llvm_path.join("bin").join(tool_name) + }; + + // Verify the tool exists + if tool_path.exists() { + Some(tool_path) + } else { + None + } +} + /// Write or update .cargo/config.toml with LLVM configuration /// /// # Arguments diff --git a/crates/pecos-qis-ffi-types/src/operations.rs b/crates/pecos-qis-ffi-types/src/operations.rs index d8c491afc..165bc279c 100644 --- a/crates/pecos-qis-ffi-types/src/operations.rs +++ b/crates/pecos-qis-ffi-types/src/operations.rs @@ -20,6 +20,12 @@ pub enum Operation { /// Release a qubit ReleaseQubit { id: usize }, + /// Record output mapping from result ID to classical register name + RecordOutput { + result_id: usize, + register_name: String, + }, + /// Classical control flow marker Barrier, } diff --git a/crates/pecos-qis-ffi/src/ffi.rs b/crates/pecos-qis-ffi/src/ffi.rs index 6b5b5b2ed..743fd4d2d 100644 --- a/crates/pecos-qis-ffi/src/ffi.rs +++ b/crates/pecos-qis-ffi/src/ffi.rs @@ -678,27 +678,38 @@ pub unsafe extern "C" fn panic(code: i32, message: *const std::ffi::c_char) { /// This is typically used to record measurement results to classical registers /// /// # Safety -/// This function is safe to call from C/LLVM code. The `result_id` parameter must be a valid -/// non-negative result ID that fits in usize. The `register_name` pointer may be null or must -/// point to a valid null-terminated C string. Invalid IDs or pointers will cause undefined behavior. +/// This function is safe to call from C/LLVM code. The `result_ptr` parameter is an i8* pointer +/// that represents a result ID (typically from inttoptr i64 conversion in LLVM IR). +/// The `register_name` pointer may be null or must point to a valid null-terminated C string. +/// Invalid pointers will cause undefined behavior. #[unsafe(no_mangle)] pub unsafe extern "C" fn __quantum__rt__result_record_output( - result_id: i64, + result_ptr: *const std::ffi::c_void, register_name: *const std::ffi::c_char, ) { - // For now, this is a no-op since we're collecting operations rather than executing them - // In a real implementation, this would record the measurement result to the specified register - // The actual measurement results are handled by the runtime during execution - - // We could potentially add this as metadata to the interface if needed - // For debugging, we can at least validate the inputs - let _result_id = i64_to_usize(result_id); - - if !register_name.is_null() { - // Mark the unsafe operation explicitly - let _register = unsafe { std::ffi::CStr::from_ptr(register_name) }.to_string_lossy(); - // In the future, we might want to record this information - } + // Extract the result ID from the pointer + // HUGR generates: %result_ptr = inttoptr i64 %result_id to i8* + let result_id = result_ptr as usize; + + // Convert the C string to a Rust String + let register_name_str = if register_name.is_null() { + "unknown".to_string() + } else { + let c_str = unsafe { std::ffi::CStr::from_ptr(register_name) }; + c_str.to_str().unwrap_or("unknown").to_string() + }; + + log::trace!( + "Recording output mapping: result_id={result_id} -> register_name='{register_name_str}'" + ); + + // Queue the operation to record this output mapping + with_interface(|interface| { + interface.queue_operation(Operation::RecordOutput { + result_id, + register_name: register_name_str, + }); + }); } // ============================================================================= diff --git a/crates/pecos-qis-selene/src/selene_runtime.rs b/crates/pecos-qis-selene/src/selene_runtime.rs index c0435db75..fff370fcc 100644 --- a/crates/pecos-qis-selene/src/selene_runtime.rs +++ b/crates/pecos-qis-selene/src/selene_runtime.rs @@ -148,6 +148,17 @@ impl SeleneRuntime { let _ = id; // Just track it self.current_op_index += 1; } + Operation::RecordOutput { + result_id, + register_name, + } => { + trace!( + "Recording output: result_id={result_id}, register_name={register_name}" + ); + // Metadata operation - just advance the index + // The actual result mapping is handled by the runtime's results collection + self.current_op_index += 1; + } Operation::Barrier => { trace!("Barrier encountered"); // Barriers don't produce quantum ops but can break batches diff --git a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py index 67ff46269..574d1968e 100644 --- a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py +++ b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py @@ -126,20 +126,38 @@ def test_llvm_ir_format_validation(self) -> None: llvm_file = Path(f.name) try: - # Find llvm-as - check PATH first, then LLVM_SYS_140_PREFIX + # Find llvm-as - check PATH first, then use pecos-llvm-utils llvm_as_path = shutil.which("llvm-as") if not llvm_as_path: - # Not in PATH, try LLVM_SYS_140_PREFIX environment variable - llvm_prefix = os.environ.get("LLVM_SYS_140_PREFIX") - if llvm_prefix: - potential_path = ( - Path(llvm_prefix) / "bin" / "llvm-as.exe" - if os.name == "nt" - else Path(llvm_prefix) / "bin" / "llvm-as" - ) - if potential_path.exists(): - llvm_as_path = str(potential_path) + # Use pecos-llvm-utils to find the tool + cargo_path = shutil.which("cargo") + if cargo_path: + try: + result = subprocess.run( + [ + cargo_path, + "run", + "-q", + "--release", + "-p", + "pecos-llvm-utils", + "--bin", + "pecos-llvm", + "--", + "tool", + "llvm-as", + ], + capture_output=True, + text=True, + check=False, + timeout=30, + ) + if result.returncode == 0 and result.stdout.strip(): + llvm_as_path = result.stdout.strip() + except (subprocess.TimeoutExpired, Exception): # noqa: S110 + # Silently fall through to failure case - error will be reported below + pass if llvm_as_path: # Validate with llvm-as diff --git a/python/quantum-pecos/tests/pecos/test_sim_api_integration.py b/python/quantum-pecos/tests/pecos/test_sim_api_integration.py index 1a468d425..e3810719f 100644 --- a/python/quantum-pecos/tests/pecos/test_sim_api_integration.py +++ b/python/quantum-pecos/tests/pecos/test_sim_api_integration.py @@ -141,33 +141,44 @@ class TestLLVMSimulation: def test_sim_api_with_llvm_simple(self) -> None: """Test sim API with simple LLVM IR program.""" - # Proper QIR-compliant LLVM IR + # QIS format LLVM IR - uses i64 for qubit indices and qmain entry point + # This matches the format that PECOS HUGR compiler generates llvm_ir = """ ; ModuleID = 'quantum_test' + source_filename = "quantum_test" - %Qubit = type opaque - %Result = type opaque + @str_c = constant [2 x i8] c"c\\00" - declare void @__quantum__qis__h__body(%Qubit*) - declare %Result* @__quantum__qis__mz__body(%Qubit*) - declare %Qubit* @__quantum__rt__qubit_allocate() - declare void @__quantum__rt__qubit_release(%Qubit*) - declare void @__quantum__rt__result_record_output(%Result*, i8*) + ; Entry point using qmain(i64)->i64 protocol + define i64 @qmain(i64 %0) #0 { + entry: + ; Allocate qubit (returns i64) + %qubit = call i64 @__quantum__rt__qubit_allocate() - @0 = internal constant [2 x i8] c"c\\00" + ; Apply H gate (takes i64 qubit index) + call void @__quantum__qis__h__body(i64 %qubit) - define void @main() #0 { - entry: - %qubit = call %Qubit* @__quantum__rt__qubit_allocate() - call void @__quantum__qis__h__body(%Qubit* %qubit) - %result = call %Result* @__quantum__qis__mz__body(%Qubit* %qubit) - call void @__quantum__rt__result_record_output(%Result* %result, - i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) - call void @__quantum__rt__qubit_release(%Qubit* %qubit) - ret void + ; Allocate result handle + %result_id = call i64 @__quantum__rt__result_allocate() + + ; Measure qubit (returns i32 measurement) + %measurement = call i32 @__quantum__qis__m__body(i64 %qubit, i64 %result_id) + + ; Record output (convert i64 to i8* pointer for register name) + %result_ptr = inttoptr i64 %result_id to i8* + call void @__quantum__rt__result_record_output(i8* %result_ptr, + i8* getelementptr inbounds ([2 x i8], [2 x i8]* @str_c, i32 0, i32 0)) + + ret i64 0 } - attributes #0 = { "EntryPoint" "requiredQubits"="1" } + declare i64 @__quantum__rt__qubit_allocate() + declare void @__quantum__qis__h__body(i64) + declare i64 @__quantum__rt__result_allocate() + declare i32 @__quantum__qis__m__body(i64, i64) + declare void @__quantum__rt__result_record_output(i8*, i8*) + + attributes #0 = { "EntryPoint" } """ try: @@ -207,45 +218,53 @@ def test_sim_api_with_llvm_simple(self) -> None: def test_sim_api_with_llvm_bell_state(self) -> None: """Test sim API with Bell state in LLVM IR.""" - # Bell state in QIS format - uses i64 qubit indices (not QIR opaque pointers) - # This is the format PECOS actually supports (from HUGR compilation) + # QIS format LLVM IR - uses i64 for qubit indices and qmain entry point + # This matches the format that PECOS HUGR compiler generates llvm_ir = """ ; ModuleID = 'bell_state' + source_filename = "bell_state" - declare void @__quantum__qis__h__body(i64) - declare void @__quantum__qis__cnot__body(i64, i64) - declare i1 @__quantum__qis__mz__body(i64) - declare void @__quantum__rt__result_record_output(i64, i8*) - - @0 = internal constant [3 x i8] c"c0\\00" - @1 = internal constant [3 x i8] c"c1\\00" + @str_c0 = constant [3 x i8] c"c0\\00" + @str_c1 = constant [3 x i8] c"c1\\00" - define void @main() #0 { + ; Entry point using qmain(i64)->i64 protocol + define i64 @qmain(i64 %0) #0 { entry: - ; Apply H to qubit 0 - call void @__quantum__qis__h__body(i64 0) - - ; Apply CNOT(0, 1) - call void @__quantum__qis__cnot__body(i64 0, i64 1) - - ; Measure both qubits - %m0 = call i1 @__quantum__qis__mz__body(i64 0) - %m1 = call i1 @__quantum__qis__mz__body(i64 1) - - ; Convert i1 to i64 for result recording - %r0 = zext i1 %m0 to i64 - %r1 = zext i1 %m1 to i64 + ; Allocate qubits (returns i64) + %q0 = call i64 @__quantum__rt__qubit_allocate() + %q1 = call i64 @__quantum__rt__qubit_allocate() - ; Record results - call void @__quantum__rt__result_record_output(i64 %r0, - i8* getelementptr inbounds ([3 x i8], [3 x i8]* @0, i32 0, i32 0)) - call void @__quantum__rt__result_record_output(i64 %r1, - i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) - - ret void + ; Apply H to qubit 0 + call void @__quantum__qis__h__body(i64 %q0) + + ; Apply CX(q0, q1) + call void @__quantum__qis__cx__body(i64 %q0, i64 %q1) + + ; Measure qubit 0 + %result_id0 = call i64 @__quantum__rt__result_allocate() + %measurement0 = call i32 @__quantum__qis__m__body(i64 %q0, i64 %result_id0) + %result_ptr0 = inttoptr i64 %result_id0 to i8* + call void @__quantum__rt__result_record_output(i8* %result_ptr0, + i8* getelementptr inbounds ([3 x i8], [3 x i8]* @str_c0, i32 0, i32 0)) + + ; Measure qubit 1 + %result_id1 = call i64 @__quantum__rt__result_allocate() + %measurement1 = call i32 @__quantum__qis__m__body(i64 %q1, i64 %result_id1) + %result_ptr1 = inttoptr i64 %result_id1 to i8* + call void @__quantum__rt__result_record_output(i8* %result_ptr1, + i8* getelementptr inbounds ([3 x i8], [3 x i8]* @str_c1, i32 0, i32 0)) + + ret i64 0 } - attributes #0 = { "EntryPoint" "requiredQubits"="2" } + declare i64 @__quantum__rt__qubit_allocate() + declare void @__quantum__qis__h__body(i64) + declare void @__quantum__qis__cx__body(i64, i64) + declare i64 @__quantum__rt__result_allocate() + declare i32 @__quantum__qis__m__body(i64, i64) + declare void @__quantum__rt__result_record_output(i8*, i8*) + + attributes #0 = { "EntryPoint" } """ try: From 0e297a4b4e786e97efa5e9ae68c2ed1b3ab2f293 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sun, 2 Nov 2025 20:39:38 -0700 Subject: [PATCH 12/14] fix windows please --- .github/workflows/python-test.yml | 3 +++ .../tests/guppy/test_hugr_compilation.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index b6491a8ef..17748ebaf 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -116,6 +116,9 @@ jobs: # Also ensure LLVM bin is in PATH $llvmBinDir = Join-Path -Path $env:LLVM_SYS_140_PREFIX -ChildPath "bin" if (Test-Path $llvmBinDir) { + # Update PATH for current session (so pytest can find llvm-as) + $env:PATH = "$llvmBinDir;$env:PATH" + # Update PATH for future steps "$llvmBinDir" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append Write-Host "Added LLVM bin to PATH: $llvmBinDir" } diff --git a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py index 574d1968e..99589db99 100644 --- a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py +++ b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py @@ -128,12 +128,15 @@ def test_llvm_ir_format_validation(self) -> None: try: # Find llvm-as - check PATH first, then use pecos-llvm-utils llvm_as_path = shutil.which("llvm-as") + print(f"DEBUG: llvm-as in PATH: {llvm_as_path}") if not llvm_as_path: # Use pecos-llvm-utils to find the tool cargo_path = shutil.which("cargo") + print(f"DEBUG: cargo found at: {cargo_path}") if cargo_path: try: + print("DEBUG: Running cargo to find llvm-as...") result = subprocess.run( [ cargo_path, @@ -151,13 +154,20 @@ def test_llvm_ir_format_validation(self) -> None: capture_output=True, text=True, check=False, - timeout=30, + timeout=120, # Increased from 30s to account for compilation time on CI ) + print(f"DEBUG: cargo returncode: {result.returncode}") + print(f"DEBUG: cargo stdout: {result.stdout[:200]}") + print(f"DEBUG: cargo stderr: {result.stderr[:200]}") if result.returncode == 0 and result.stdout.strip(): llvm_as_path = result.stdout.strip() - except (subprocess.TimeoutExpired, Exception): # noqa: S110 - # Silently fall through to failure case - error will be reported below - pass + print(f"DEBUG: llvm-as found at: {llvm_as_path}") + except subprocess.TimeoutExpired as e: + print(f"DEBUG: cargo command timed out after {e.timeout}s") + except Exception as e: + print(f"DEBUG: cargo command failed with exception: {e}") + else: + print("DEBUG: cargo not found in PATH") if llvm_as_path: # Validate with llvm-as From faca699084ba9d1ba4f44a5d30b8619cd6a23e85 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Wed, 5 Nov 2025 19:48:44 -0700 Subject: [PATCH 13/14] pecos llvm install simplified... --- crates/pecos-llvm-utils/src/bin/pecos-llvm.rs | 34 +- crates/pecos-llvm-utils/src/installer.rs | 426 +++--------------- crates/pecos-qis-core/build.rs | 38 +- crates/pecos-qis-selene/build.rs | 37 ++ crates/pecos-qis-selene/src/executor.rs | 2 + 5 files changed, 135 insertions(+), 402 deletions(-) diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs index cba190dc1..31f1f943e 100644 --- a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -31,7 +31,7 @@ enum Commands { #[arg(short, long)] quiet: bool, }, - /// Install LLVM 14.0.6 (default: ~/.pecos/llvm/) + /// Install LLVM 14.0.6 to ~/.pecos/llvm/ Install { /// Force reinstall even if already present #[arg(short, long)] @@ -39,16 +39,6 @@ enum Commands { /// Skip automatic configuration after installation #[arg(long)] no_configure: bool, - /// Install system-wide to standard location (requires admin/sudo) - /// Windows: C:\Program Files\LLVM-14, Unix: /usr/local/LLVM-14 - /// Sets `LLVM_SYS_140_PREFIX` environment variable permanently - #[arg(long, conflicts_with = "user")] - system: bool, - /// Install to user-level standard location - /// Windows: %LOCALAPPDATA%\Programs\LLVM-14, Unix: ~/.local/LLVM-14 - /// Sets `LLVM_SYS_140_PREFIX` environment variable permanently - #[arg(long, conflicts_with = "system")] - user: bool, }, /// Auto-configure LLVM for PECOS (updates .cargo/config.toml) Configure, @@ -75,20 +65,8 @@ fn main() { Commands::Install { force, no_configure, - system, - user, } => { - use pecos_llvm_utils::installer::InstallLocation; - - let location = if system { - InstallLocation::System - } else if user { - InstallLocation::User - } else { - InstallLocation::PecosManaged - }; - - cmd_install(force, no_configure, location); + cmd_install(force, no_configure); } Commands::Configure => cmd_configure(), Commands::Version => cmd_version(), @@ -132,14 +110,10 @@ fn cmd_check(quiet: bool) { } } -fn cmd_install( - force: bool, - no_configure: bool, - location: pecos_llvm_utils::installer::InstallLocation, -) { +fn cmd_install(force: bool, no_configure: bool) { use pecos_llvm_utils::installer::install_llvm; - match install_llvm(force, no_configure, location) { + match install_llvm(force, no_configure) { Ok(_install_path) => { // Success message is printed by install_llvm process::exit(0); diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index 8873eb20d..d1ee0bf74 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -7,20 +7,6 @@ use std::fs; use std::io; use std::path::{Path, PathBuf}; -/// Installation location type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum InstallLocation { - /// PECOS-managed installation at ~/.pecos/llvm/ (default) - /// Uses .cargo/config.toml for configuration - PecosManaged, - /// System-wide installation at standard location (requires admin/sudo) - /// Sets `LLVM_SYS_140_PREFIX` environment variable - System, - /// User-level installation at standard location - /// Sets `LLVM_SYS_140_PREFIX` environment variable - User, -} - /// Known SHA256 checksums for LLVM 14.0.6 downloads /// Format: (filename, `sha256_hash`) /// @@ -55,46 +41,37 @@ const LLVM_CHECKSUMS: &[(&str, &str)] = &[ ), ]; -/// Install LLVM 14.0.6 to specified location -/// -/// Installs to location based on `InstallLocation`: -/// -/// **`PecosManaged`** (default): -/// - All platforms: ~/.pecos/llvm -/// - Configures via .cargo/config.toml +/// Install LLVM 14.0.6 to ~/.pecos/llvm/ /// -/// **System** (--system flag): -/// - Windows: C:\Program Files\LLVM-14 (requires admin) -/// - Unix: /usr/local/LLVM-14 (requires sudo) -/// - Sets `LLVM_SYS_140_PREFIX` environment variable permanently +/// Downloads and installs LLVM 14.0.6 pre-built binaries to a PECOS-managed +/// directory at ~/.pecos/llvm/. This ensures a clean, isolated installation +/// that PECOS can safely modify (e.g., fixing dylib references on macOS). /// -/// **User** (--user flag): -/// - Windows: %LOCALAPPDATA%\Programs\LLVM-14 -/// - macOS: ~/Library/Application Support/LLVM-14 -/// - Linux: ~/.local/LLVM-14 -/// - Sets `LLVM_SYS_140_PREFIX` environment variable permanently +/// After installation, run `pecos-llvm configure` to update .cargo/config.toml, +/// or set the `LLVM_SYS_140_PREFIX` environment variable to `~/.pecos/llvm` manually. /// /// # Arguments /// * `force` - Force reinstall even if already present /// * `no_configure` - Skip automatic configuration after installation -/// * `location` - Where to install LLVM /// /// # Errors /// Returns an error if: /// - LLVM is already installed and `force` is false /// - The download or extraction fails -/// - Platform-specific fixes fail /// - Installation verification fails -/// - Setting environment variable fails (System/User installs) +/// - Platform fixes fail (e.g., `install_name_tool` on macOS) /// /// # Returns -/// Path to the installed LLVM directory +/// Path to the installed LLVM directory (~/.pecos/llvm/) pub fn install_llvm( force: bool, no_configure: bool, - location: InstallLocation, ) -> Result> { - let llvm_dir = get_install_location(location)?; + // PECOS-managed installation: ~/.pecos/llvm + let llvm_dir = dirs::home_dir() + .ok_or("Could not determine home directory")? + .join(".pecos") + .join("llvm"); // Check if already installed if !force && llvm_dir.exists() && is_valid_installation(&llvm_dir) { @@ -135,7 +112,7 @@ pub fn install_llvm( // Cleanup LLVM temp directory only (not entire tmp directory) fs::remove_dir_all(&temp_dir)?; - // Apply platform-specific fixes + // Apply platform-specific fixes (e.g., fix libunwind on macOS) apply_platform_fixes(&llvm_dir)?; // Verify installation files @@ -150,73 +127,32 @@ pub fn install_llvm( println!("Installation complete!"); println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); - // Configure LLVM based on installation location (unless --no-configure is specified) + // Configure LLVM (unless --no-configure is specified) if no_configure { println!(); println!("Skipping automatic configuration (--no-configure specified)."); - match location { - InstallLocation::PecosManaged => { - println!(); - println!("To configure PECOS, run:"); - println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); - } - InstallLocation::System | InstallLocation::User => { - println!(); - println!("To make this installation permanent, set the environment variable:"); - println!(" LLVM_SYS_140_PREFIX={}", llvm_dir.display()); - } - } + println!(); + println!("To configure PECOS, run:"); + println!(" pecos-llvm configure"); + println!(); + println!("Or set the environment variable manually:"); + println!(" export LLVM_SYS_140_PREFIX=\"{}\"", llvm_dir.display()); } else { println!(); - match location { - InstallLocation::PecosManaged => { - // PECOS-managed: use .cargo/config.toml - println!("Configuring PECOS to use this LLVM installation..."); - match crate::auto_configure_llvm(None) { - Ok(configured_path) => { - println!("Updated .cargo/config.toml with LLVM configuration"); - println!("Configured LLVM path: {}", configured_path.display()); - println!(); - println!("You can now build PECOS:"); - println!(" cargo build"); - } - Err(e) => { - eprintln!("Warning: Could not auto-configure LLVM: {e}"); - println!(); - println!("Please run configuration manually:"); - println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); - } - } + println!("Configuring PECOS to use this LLVM installation..."); + match crate::auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Updated .cargo/config.toml with LLVM configuration"); + println!("Configured LLVM path: {}", configured_path.display()); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); } - InstallLocation::System | InstallLocation::User => { - // System/User: set LLVM_SYS_140_PREFIX environment variable - println!("Setting LLVM_SYS_140_PREFIX environment variable..."); - match set_llvm_env_var(&llvm_dir, location == InstallLocation::System) { - Ok(()) => { - println!("Environment variable set successfully!"); - println!("LLVM_SYS_140_PREFIX={}", llvm_dir.display()); - println!(); - println!("You can now build PECOS:"); - println!(" cargo build"); - println!(); - if location == InstallLocation::System { - println!("Note: You may need to restart your terminal or system for"); - println!("system-wide environment changes to take effect."); - } else { - println!("Note: You may need to restart your terminal for the"); - println!("environment variable to take effect in new sessions."); - } - } - Err(e) => { - eprintln!("Warning: Could not set environment variable: {e}"); - println!(); - println!("Please set it manually:"); - #[cfg(target_os = "windows")] - println!(" setx LLVM_SYS_140_PREFIX \"{}\"", llvm_dir.display()); - #[cfg(not(target_os = "windows"))] - println!(" export LLVM_SYS_140_PREFIX=\"{}\"", llvm_dir.display()); - } - } + Err(e) => { + eprintln!("Warning: Could not auto-configure LLVM: {e}"); + println!(); + println!("Please run configuration manually:"); + println!(" pecos-llvm configure"); } } } @@ -224,165 +160,6 @@ pub fn install_llvm( Ok(llvm_dir) } -/// Set `LLVM_SYS_140_PREFIX` environment variable permanently -/// -/// # Arguments -/// * `llvm_path` - Path to the LLVM installation -/// * `system_wide` - If true, set system-wide; if false, set for current user -/// -/// # Errors -/// Returns an error if setting the environment variable fails -fn set_llvm_env_var(llvm_path: &Path, system_wide: bool) -> Result<(), Box> { - let path_str = llvm_path.to_string_lossy(); - - #[cfg(target_os = "windows")] - { - use std::process::Command; - - if system_wide { - // System-wide on Windows requires admin rights and registry manipulation - // For now, we'll use setx which sets user-level, and warn about system-level - eprintln!( - "Warning: System-wide environment variables on Windows require administrator" - ); - eprintln!("privileges and registry modification. Setting user-level variable instead."); - eprintln!(); - eprintln!("To set system-wide, run as administrator:"); - eprintln!(" setx /M LLVM_SYS_140_PREFIX \"{path_str}\""); - eprintln!(); - } - - // Use setx to set user-level environment variable permanently - let output = Command::new("setx") - .args(["LLVM_SYS_140_PREFIX", &path_str]) - .output()?; - - if !output.status.success() { - return Err(format!( - "setx command failed: {}", - String::from_utf8_lossy(&output.stderr) - ) - .into()); - } - - Ok(()) - } - - #[cfg(not(target_os = "windows"))] - { - // On Unix, we need to add to shell RC files for user or /etc/environment for system - if system_wide { - // System-wide: would need sudo to write to /etc/environment - eprintln!( - "Warning: System-wide environment variables on Unix require root privileges." - ); - eprintln!("Setting user-level variable instead."); - eprintln!(); - eprintln!("To set system-wide, add to /etc/environment (requires sudo):"); - eprintln!(" LLVM_SYS_140_PREFIX=\"{path_str}\""); - eprintln!(); - } - - // User-level: append to shell RC files - let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; - let export_line = format!("export LLVM_SYS_140_PREFIX=\"{path_str}\"\n"); - - // Try to add to common shell RC files - let rc_files = vec![ - home_dir.join(".bashrc"), - home_dir.join(".zshrc"), - home_dir.join(".profile"), - ]; - - let mut updated_any = false; - for rc_file in rc_files { - if rc_file.exists() { - // Check if already present - if let Ok(contents) = fs::read_to_string(&rc_file) - && contents.contains("LLVM_SYS_140_PREFIX") - { - println!( - "LLVM_SYS_140_PREFIX already present in {}", - rc_file.display() - ); - updated_any = true; - continue; - } - - // Append to file - match fs::OpenOptions::new().append(true).open(&rc_file) { - Ok(mut file) => { - use std::io::Write; - writeln!(file, "\n# Added by pecos-llvm installer")?; - write!(file, "{export_line}")?; - println!("Added LLVM_SYS_140_PREFIX to {}", rc_file.display()); - updated_any = true; - } - Err(e) => { - eprintln!("Warning: Could not update {}: {}", rc_file.display(), e); - } - } - } - } - - if !updated_any { - eprintln!("Warning: No shell RC files found. Please add manually:"); - eprintln!(" {}", export_line.trim()); - return Err("No shell RC files found to update".into()); - } - - Ok(()) - } -} - -fn get_install_location(location: InstallLocation) -> Result> { - match location { - InstallLocation::PecosManaged => { - // PECOS-managed: ~/.pecos/llvm (all platforms) - let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; - Ok(home_dir.join(".pecos").join("llvm")) - } - InstallLocation::System => { - // System-wide: standard locations with admin/sudo rights - #[cfg(target_os = "windows")] - { - Ok(PathBuf::from("C:\\Program Files\\LLVM-14")) - } - #[cfg(not(target_os = "windows"))] - { - Ok(PathBuf::from("/usr/local/LLVM-14")) - } - } - InstallLocation::User => { - // User-level: standard user-accessible locations - #[cfg(target_os = "windows")] - { - // %LOCALAPPDATA%\Programs\LLVM-14 - let local_appdata = std::env::var("LOCALAPPDATA") - .map_err(|_| "Could not determine LOCALAPPDATA")?; - Ok(PathBuf::from(local_appdata) - .join("Programs") - .join("LLVM-14")) - } - #[cfg(target_os = "macos")] - { - // ~/Library/Application Support/LLVM-14 - let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; - Ok(home_dir - .join("Library") - .join("Application Support") - .join("LLVM-14")) - } - #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] - { - // ~/.local/LLVM-14 (Linux and others) - let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; - Ok(home_dir.join(".local").join("LLVM-14")) - } - } - } -} - fn get_download_url() -> Result<(String, String), Box> { let os = std::env::consts::OS; let arch = std::env::consts::ARCH; @@ -833,119 +610,52 @@ pub fn verify_llvm_runtime(llvm_dir: &Path) -> Result<(), Box Result<(), Box> { - #[cfg(target_os = "macos")] - { - print!("Applying macOS fixes... "); - io::Write::flush(&mut io::stdout())?; - - // Fix 1: Configure clang to use macOS SDK - // The pre-built LLVM doesn't know where system libraries are on macOS - configure_macos_sdk(llvm_dir)?; - - // Fix 2: Fix libunwind dylib references - // LLVM 14.0.6 libunwind libraries have @rpath references to themselves - fix_libunwind_references(llvm_dir)?; - - println!("Done"); - } - - #[cfg(not(target_os = "macos"))] - { - // Suppress unused parameter warning on non-macOS platforms - let _ = llvm_dir; - } - - Ok(()) -} - +/// Apply platform-specific fixes to the LLVM installation +/// +/// On macOS, fixes the libunwind dylib install name to use an absolute path +/// instead of @rpath, which prevents runtime linking issues. +/// +/// # Arguments +/// * `llvm_dir` - Path to the LLVM installation directory +/// +/// # Errors +/// Returns an error if `install_name_tool` fails to execute #[cfg(target_os = "macos")] -fn configure_macos_sdk(llvm_dir: &Path) -> Result<(), Box> { - use std::os::unix::fs::PermissionsExt; +fn apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box> { use std::process::Command; - // Get the SDK path from xcrun - let output = Command::new("xcrun").args(["--show-sdk-path"]).output()?; - - if !output.status.success() { - return Err("Failed to find macOS SDK. Is Xcode Command Line Tools installed?".into()); - } - - let sdk_path = String::from_utf8(output.stdout)?.trim().to_string(); + print!("Applying macOS platform fixes... "); + io::Write::flush(&mut io::stdout())?; - // Create wrapper scripts for clang and clang-14 that add SDK flags - let bin_dir = llvm_dir.join("bin"); + let lib_dir = llvm_dir.join("lib"); + let libunwind = lib_dir.join("libunwind.1.0.dylib"); - // Rename original binaries - let clang_orig = bin_dir.join("clang"); - let clang_real = bin_dir.join("clang-real"); - if clang_orig.exists() && !clang_real.exists() { - fs::rename(&clang_orig, &clang_real)?; + if !libunwind.exists() { + println!("Skipped (libunwind not found)"); + return Ok(()); } - let clang14_orig = bin_dir.join("clang-14"); - let clang14_real = bin_dir.join("clang-14-real"); - if clang14_orig.exists() && !clang14_real.exists() { - fs::rename(&clang14_orig, &clang14_real)?; - } + // Fix libunwind's install name from @rpath to absolute path + // This prevents "Library not loaded: @rpath/libunwind.1.dylib" errors + let new_install_name = lib_dir.join("libunwind.1.dylib"); - // Create wrapper script for clang - let wrapper_content = format!( - "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" - ); - fs::write(&clang_orig, &wrapper_content)?; - fs::set_permissions(&clang_orig, fs::Permissions::from_mode(0o755))?; - - // Create wrapper script for clang-14 - let wrapper14_content = format!( - "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-14-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" - ); - fs::write(&clang14_orig, &wrapper14_content)?; - fs::set_permissions(&clang14_orig, fs::Permissions::from_mode(0o755))?; - - // Also create wrapper for clang++ if it exists - let clangpp_orig = bin_dir.join("clang++"); - let clangpp_real = bin_dir.join("clang++-real"); - if clangpp_orig.exists() && !clangpp_real.exists() { - fs::rename(&clangpp_orig, &clangpp_real)?; - let wrapperpp_content = format!( - "#!/bin/bash\nexec \"$(dirname \"$0\")/clang++-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" - ); - fs::write(&clangpp_orig, &wrapperpp_content)?; - fs::set_permissions(&clangpp_orig, fs::Permissions::from_mode(0o755))?; + let status = Command::new("install_name_tool") + .arg("-id") + .arg(&new_install_name) + .arg(&libunwind) + .status()?; + + if !status.success() { + println!("FAILED"); + return Err("install_name_tool failed to fix libunwind".into()); } + println!("OK"); Ok(()) } -#[cfg(target_os = "macos")] -fn fix_libunwind_references(llvm_dir: &Path) -> Result<(), Box> { - use std::process::Command; - - let lib_dir = llvm_dir.join("lib"); - - // Find all libunwind dylibs - if let Ok(entries) = fs::read_dir(&lib_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - // Check for libunwind dylib files using case-insensitive extension check - if name.starts_with("libunwind") - && std::path::Path::new(name) - .extension() - .is_some_and(|ext| ext.eq_ignore_ascii_case("dylib")) - { - // Fix the install name to use absolute path instead of @rpath - let abs_path = path.to_string_lossy().to_string(); - Command::new("install_name_tool") - .args(["-id", &abs_path, &abs_path]) - .output()?; - } - } - } - } - +#[cfg(not(target_os = "macos"))] +fn apply_platform_fixes(_llvm_dir: &Path) -> Result<(), Box> { + // No platform fixes needed on non-macOS platforms Ok(()) } diff --git a/crates/pecos-qis-core/build.rs b/crates/pecos-qis-core/build.rs index 3eab7e590..8836205cc 100644 --- a/crates/pecos-qis-core/build.rs +++ b/crates/pecos-qis-core/build.rs @@ -93,26 +93,36 @@ fn print_llvm_not_found_error_extended() { #[cfg(target_os = "windows")] { - eprintln!("Option 2: Manual installation"); + eprintln!("Option 2: Manual installation (advanced)"); eprintln!(); - eprintln!(" 1. Download LLVM 14.0.6 for Windows:"); - eprintln!( - " https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe" - ); + eprintln!(" WARNING: The official LLVM installer lacks development files."); + eprintln!(" You need a FULL development package from community sources:"); eprintln!(); - eprintln!(" 2. Run the installer and note the installation directory"); - eprintln!(" (typically C:\\Program Files\\LLVM)"); + eprintln!(" Recommended sources:"); + eprintln!(" https://github.com/bitgate/llvm-windows-full-builds"); + eprintln!(" https://github.com/vovkos/llvm-package-windows"); eprintln!(); - eprintln!(" 3. Configure PECOS to use it:"); - eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); - eprintln!(); - eprintln!(" 4. Build PECOS:"); - eprintln!(" cargo build"); + eprintln!(" After extracting to C:\\LLVM (or similar):"); + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\LLVM"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(" cargo build"); eprintln!(); } - eprintln!("The 'configure' command will detect your LLVM installation and"); - eprintln!("update .cargo/config.toml automatically."); + eprintln!("Alternative: Set LLVM path manually"); + eprintln!(" Instead of 'configure', you can set environment variables:"); + eprintln!(); + #[cfg(target_os = "windows")] + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\path\\to\\llvm"); + #[cfg(not(target_os = "windows"))] + eprintln!(" export LLVM_SYS_140_PREFIX=/path/to/llvm"); + #[cfg(not(target_os = "windows"))] + eprintln!(" Or add llvm-config to PATH:"); + #[cfg(not(target_os = "windows"))] + eprintln!(" export PATH=\"/path/to/llvm/bin:$PATH\""); + eprintln!(); + eprintln!("For detailed instructions, see:"); + eprintln!(" https://github.com/CQCL/PECOS/blob/master/docs/user-guide/getting-started.md"); eprintln!(); eprintln!("Don't need LLVM IR support? Build without it:"); eprintln!(" cargo build --no-default-features"); diff --git a/crates/pecos-qis-selene/build.rs b/crates/pecos-qis-selene/build.rs index 3370ab505..3bfe4c2c4 100644 --- a/crates/pecos-qis-selene/build.rs +++ b/crates/pecos-qis-selene/build.rs @@ -115,6 +115,10 @@ fn build_shim_library(out_dir: &Path) -> PathBuf { cmd.arg(format!("-I{selene_include}")); } + // On macOS, add SDK flags to ensure compiler can find system headers/libraries + #[cfg(target_os = "macos")] + add_macos_sdk_flags(&mut cmd); + let output = cmd.output().expect("Failed to execute clang"); assert!( @@ -345,6 +349,10 @@ fn build_helios_from_cargo_dependency(out_dir: &Path) -> Result<(), String> { .arg(&interface_o) .arg(&interface_c); + // On macOS, add SDK flags to ensure compiler can find system headers/libraries + #[cfg(target_os = "macos")] + add_macos_sdk_flags(&mut compile_cmd); + let output = compile_cmd .output() .map_err(|e| format!("Failed to execute clang: {e}"))?; @@ -414,3 +422,32 @@ fn find_c_compiler() -> String { // If nothing works, return "cc" and let it fail with a better error "cc".to_string() } + +/// Get the macOS SDK path using xcrun +/// +/// Returns None if not on macOS or if xcrun fails +#[cfg(target_os = "macos")] +fn get_macos_sdk_path() -> Option { + let output = Command::new("xcrun") + .args(["--show-sdk-path"]) + .output() + .ok()?; + + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + None + } +} + +/// Add macOS SDK flags to a compiler command +/// +/// This ensures that the compiler can find system headers and libraries +/// when using pre-built LLVM that doesn't have SDK paths configured. +#[cfg(target_os = "macos")] +fn add_macos_sdk_flags(cmd: &mut Command) { + if let Some(sdk_path) = get_macos_sdk_path() { + cmd.arg(format!("-isysroot{sdk_path}")); + cmd.arg(format!("-L{sdk_path}/usr/lib")); + } +} diff --git a/crates/pecos-qis-selene/src/executor.rs b/crates/pecos-qis-selene/src/executor.rs index 3e2757575..9800864a9 100644 --- a/crates/pecos-qis-selene/src/executor.rs +++ b/crates/pecos-qis-selene/src/executor.rs @@ -327,6 +327,8 @@ impl QisHeliosInterface { debug!("SDK path: {sdk_path}"); clang_cmd.arg("-isysroot"); clang_cmd.arg(sdk_path); + // Add library search path so linker can find pthread, etc. + clang_cmd.arg(format!("-L{sdk_path}/usr/lib")); } else { warn!("xcrun output was not valid UTF-8"); } From 5850c81884fadb61a7142996754ddd0550aeb242 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Wed, 5 Nov 2025 20:35:59 -0700 Subject: [PATCH 14/14] lint --- crates/pecos-llvm-utils/src/installer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs index d1ee0bf74..7b2d49f0d 100644 --- a/crates/pecos-llvm-utils/src/installer.rs +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -655,6 +655,7 @@ fn apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box Result<(), Box> { // No platform fixes needed on non-macOS platforms Ok(())