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..17748ebaf 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' @@ -86,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" } @@ -227,7 +260,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 +326,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..1cc902967 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,20 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "pecos-llvm-utils" +version = "0.1.1" +dependencies = [ + "clap", + "dirs", + "flate2", + "reqwest", + "sevenz-rust", + "sha2", + "tar", + "xz2", +] + [[package]] name = "pecos-phir" version = "0.1.1" @@ -2586,6 +2671,7 @@ dependencies = [ "log", "pecos-core", "pecos-engines", + "pecos-llvm-utils", "pecos-programs", "pecos-qis-ffi-types", "pecos-qis-selene", @@ -3654,6 +3740,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 +5073,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..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" } @@ -118,6 +123,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-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 de3bc9c40..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 @@ -44,9 +49,12 @@ 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 + } + + // 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"); 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/Cargo.toml b/crates/pecos-llvm-utils/Cargo.toml new file mode 100644 index 000000000..bac2bf488 --- /dev/null +++ b/crates/pecos-llvm-utils/Cargo.toml @@ -0,0 +1,29 @@ +[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" +sha2.workspace = true + +[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..31f1f943e --- /dev/null +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -0,0 +1,242 @@ +#!/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, find_tool, 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, + /// Validate an LLVM installation at a specific path + Validate { + /// 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() { + 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(), + Commands::Validate { path } => cmd_validate(&path), + Commands::Tool { name } => cmd_tool(&name), + } +} + +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); + } +} + +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()); + println!(); + + // Check if path exists + if !path.exists() { + eprintln!("ERROR: 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!("ERROR: 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!("ERROR: 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); + } + } +} + +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/installer.rs b/crates/pecos-llvm-utils/src/installer.rs new file mode 100644 index 000000000..7b2d49f0d --- /dev/null +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -0,0 +1,662 @@ +//! LLVM 14.0.6 installation functionality +//! +//! 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}; + +/// 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", + "e6cc6b8279661fd4452c2847cb8e55ce1e54e1faf4ab497b37c85ffdb6685e7c", + ), + // macOS Apple Silicon + ( + "clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz", + "82f4f7607a16c9aaf7314b945bde6a4639836ec9d2b474ebb3a31dee33e3c15a", + ), + // Linux x86_64 + ( + "clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz", + "7412026be8bb8f6b4c25ef58c7a1f78ed5ea039d94f0fa633a386de9c60a6942", + ), + // Linux aarch64 + ( + "clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz", + "7412026be8bb8f6b4c25ef58c7a1f78ed5ea039d94f0fa633a386de9c60a6942", + ), + // Windows (from PLC-lang/llvm-package-windows) + ( + "LLVM-14.0.6-win64.7z", + "611e7a39363a2b63267d012a05f83ea9ce2b432a448890459c9412233327ac11", + ), +]; + +/// Install LLVM 14.0.6 to ~/.pecos/llvm/ +/// +/// 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). +/// +/// 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 +/// +/// # Errors +/// Returns an error if: +/// - LLVM is already installed and `force` is false +/// - The download or extraction fails +/// - Installation verification fails +/// - Platform fixes fail (e.g., `install_name_tool` on macOS) +/// +/// # Returns +/// Path to the installed LLVM directory (~/.pecos/llvm/) +pub fn install_llvm( + force: bool, + no_configure: bool, +) -> Result> { + // 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) { + 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)?; + + // Verify checksum + verify_checksum(&archive_path, &archive_name)?; + + // 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 (e.g., fix libunwind on macOS) + apply_platform_fixes(&llvm_dir)?; + + // Verify installation files + if !is_valid_installation(&llvm_dir) { + return Err("Installation completed but file verification failed".into()); + } + + // Verify runtime functionality + verify_llvm_runtime(&llvm_dir)?; + + println!(); + println!("Installation complete!"); + println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); + + // Configure LLVM (unless --no-configure is specified) + if no_configure { + println!(); + println!("Skipping automatic configuration (--no-configure specified)."); + 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!(); + 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!(" pecos-llvm configure"); + } + } + } + + Ok(llvm_dir) +} + +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 verify_checksum( + file_path: &PathBuf, + archive_name: &str, +) -> 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!(" WARNING: 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())?; + + // 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 + })?; + + // Check if LLVM was extracted directly to extract_to (no wrapper directory) + // This is the case for some Windows 7z archives + let llvm_config = if cfg!(windows) { + extract_to.join("bin").join("llvm-config.exe") + } else { + extract_to.join("bin").join("llvm-config") + }; + + if llvm_config.exists() { + // LLVM was extracted directly to extract_to, move it to dest + fs::create_dir_all(dest)?; + for entry in fs::read_dir(extract_to)? { + let entry = entry?; + let entry_path = entry.path(); + // Skip the dest directory itself and the tmp directory + if entry_path == *dest || entry.file_name() == "tmp" { + continue; + } + let dest_path = dest.join(entry.file_name()); + fs::rename(entry_path, dest_path)?; + } + } else { + // The archive extracts to a directory like LLVM-14.0.6-win64 + // Find the extracted directory + let mut extracted_dir = None; + let mut found_dirs = Vec::new(); + + for entry in fs::read_dir(extract_to)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() + && let Some(name) = path.file_name().and_then(|n| n.to_str()) + { + found_dirs.push(name.to_string()); + // Case-insensitive search for "LLVM" in directory name + if name.to_uppercase().contains("LLVM") { + extracted_dir = Some(path); + break; + } + } + } + + // If we found a subdirectory with "LLVM" in the name, use it + if let Some(extracted_dir) = extracted_dir { + // 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)?; + } + } else { + // No subdirectory found with "LLVM" in name + // Check if there's only one directory - it might be the LLVM directory with a different name + if found_dirs.len() == 1 { + // Assume this single directory is the LLVM installation + let single_dir = extract_to.join(&found_dirs[0]); + if dest.exists() { + // Move contents + for entry in fs::read_dir(&single_dir)? { + let entry = entry?; + let dest_path = dest.join(entry.file_name()); + fs::rename(entry.path(), dest_path)?; + } + fs::remove_dir(&single_dir)?; + } else { + fs::rename(&single_dir, dest)?; + } + } else { + return Err(format!( + "Could not find extracted LLVM directory. Expected directory with 'LLVM' in name or bin/llvm-config. Found directories: {found_dirs:?}" + ) + .into()); + } + } + } + + Ok(()) +} + +/// 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 +#[must_use] +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 { "a" }; + + // Check for at least one core LLVM library (different naming on different platforms) + let has_llvm_lib = if cfg!(windows) { + // 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 { + 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 +/// +/// # 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())?; + + let llvm_config = if cfg!(windows) { + llvm_dir.join("bin").join("llvm-config.exe") + } else { + llvm_dir.join("bin").join("llvm-config") + }; + + // 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()) + } + } +} + +/// 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 apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box> { + use std::process::Command; + + print!("Applying macOS platform fixes... "); + io::Write::flush(&mut io::stdout())?; + + let lib_dir = llvm_dir.join("lib"); + let libunwind = lib_dir.join("libunwind.1.0.dylib"); + + if !libunwind.exists() { + println!("Skipped (libunwind not found)"); + return Ok(()); + } + + // 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"); + + 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(not(target_os = "macos"))] +#[allow(clippy::unnecessary_wraps)] +fn apply_platform_fixes(_llvm_dir: &Path) -> Result<(), Box> { + // No platform fixes needed on non-macOS platforms + 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..2959551fa --- /dev/null +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -0,0 +1,541 @@ +//! 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: +/// - 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) +/// +/// # 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 + if let Some(home_dir) = dirs::home_dir() { + 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); + } + } + } + + // 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() + && 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")] + { + // 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 +} + +/// 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!(" 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!(" After installation, 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 (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.) +/// +/// # 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/ for PECOS-managed LLVM + // Uses find_llvm_14 which checks platform-appropriate paths + if let Some(home_dir) = dirs::home_dir() { + 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); + } + } + } + + // 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()?; + } +} + +/// 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 +/// * `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)?; + + // Convert path to forward slashes for TOML compatibility (Windows accepts forward slashes) + let llvm_path_str = llvm_path.to_string_lossy().replace('\\', "/"); + + // 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-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-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..8836205cc --- /dev/null +++ b/crates/pecos-qis-core/build.rs @@ -0,0 +1,130 @@ +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 (advanced)"); + eprintln!(); + eprintln!(" WARNING: The official LLVM installer lacks development files."); + eprintln!(" You need a FULL development package from community sources:"); + eprintln!(); + eprintln!(" Recommended sources:"); + eprintln!(" https://github.com/bitgate/llvm-windows-full-builds"); + eprintln!(" https://github.com/vovkos/llvm-package-windows"); + eprintln!(); + 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!("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"); + 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-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/build.rs b/crates/pecos-qis-selene/build.rs index 7683accda..3bfe4c2c4 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); @@ -109,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!( @@ -339,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}"))?; @@ -408,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 2a90b9a90..9800864a9 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: @@ -279,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"); } @@ -343,28 +393,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 +425,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 +481,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 +504,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-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/crates/pecos-quest/build_quest.rs b/crates/pecos-quest/build_quest.rs index 4b50ef6ac..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 @@ -575,11 +576,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..12f31139c 100644 --- a/crates/pecos-qulacs/build.rs +++ b/crates/pecos-qulacs/build.rs @@ -197,23 +197,42 @@ 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 + 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..f70d470a7 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -43,27 +43,90 @@ 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" + !!! 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 + 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/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 d5ee15ca3..021348d75 100644 --- a/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py +++ b/python/quantum-pecos/src/pecos/foreign_objects/wasmtime.py @@ -167,6 +167,20 @@ 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") + 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. diff --git a/python/quantum-pecos/tests/guppy/test_hugr_compilation.py b/python/quantum-pecos/tests/guppy/test_hugr_compilation.py index 4b168c708..99589db99 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,68 @@ 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 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, + "run", + "-q", + "--release", + "-p", + "pecos-llvm-utils", + "--bin", + "pecos-llvm", + "--", + "tool", + "llvm-as", + ], + capture_output=True, + text=True, + check=False, + 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() + 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 + 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 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: 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