From 110093e41c2d73e6c8ad4a212f931b1bd1d9f694 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Mon, 27 Nov 2023 10:18:02 -0700 Subject: [PATCH 01/11] Add job for Apple Silicon MacOS build --- .github/workflows/build.yml | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 714e388..193d916 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,3 +161,85 @@ jobs: with: name: macos path: ${{ github.workspace }}/artifacts/* + + build-macos-arm: + name: Build Apple-Silicon libquil.dylib + runs-on: macos-latest-xlarge + + steps: + + - uses: actions/checkout@v2 + with: + path: "libquil" + + - name: Cache SBCL + id: cache-sbcl-macos + uses: actions/cache@v3 + with: + path: | + /usr/local/lib/libsbcl.so + /usr/local/lib/sbcl + /usr/local/bin/sbcl + ~/quicklisp + ~/.sbclrc + key: ${{ runner.os }}-build-${{ hashFiles('**/macos-versions' )}} + + - uses: Homebrew/actions/setup-homebrew@master + name: Setup homebrew + + - if: steps.cache-sbcl-macos.outputs.cache-hit != 'true' + name: Install SBCL, libraries, and quicklisp + run: | + brew install sbcl git + git clone --branch sbcl-2.2.4 https://git.code.sf.net/p/sbcl/sbcl /tmp/sbcl + cd /tmp/sbcl && sudo sh make.sh --without-compact-instance-header --without-immobile-space --without-immobile-code && sudo sh make-shared-library.sh + brew remove -f sbcl + sudo sh install.sh + sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so + + brew install wget + wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" + sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' + echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc + rm -f /tmp/quicklisp.lisp + cat ~/.sbclrc + + - name: Pull Lisp dependencies + run: | + git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a + git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 + git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl + git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation + git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 + + - name: Install quilc dependencies + run: brew install lapack openblas libffi gfortran + + - name: Build libquil.dylib + run: | + cd $GITHUB_WORKSPACE/libquil + ls + sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' + sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' + make + + - name: ls + run: | + ls $GITHUB_WORKSPACE/libquil/libquil.dylib + + - name: Test + run: | + cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test + cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test + + - name: Package files + run: | + mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil + + - name: Store artifact + uses: actions/upload-artifact@v3 + with: + name: macos + path: ${{ github.workspace }}/artifacts/* + From 07373f92ae1b69a14467d73303e116fc84e51bf9 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 13:52:04 -0600 Subject: [PATCH 02/11] Claude gets it working, maybe --- .gitignore | 2 ++ Makefile | 60 ++++++++++++++++++++++++++++++++++-- README.md | 41 +++++++++++++++++++++++- src/build-image.lisp | 31 +++++++++++++++++-- src/quilc/api.lisp | 2 +- src/quilc/compile.lisp | 6 ++++ src/qvm/qvm-app-imports.lisp | 8 +++-- 7 files changed, 141 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 9e5309c..3387c30 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ libquil.c libquil.core libquil.h libquil.so +libquil.dylib +libquil.py diff --git a/Makefile b/Makefile index c2d9223..8720580 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ .PHONY: all clean -OS:=$(shell uname -s) +OS := $(shell uname -s) + +SBCL ?= sbcl + ifeq ($(OS), Darwin) LIBQUIL_TARGET = libquil.dylib CCFLAGS = -dynamiclib @@ -9,13 +12,64 @@ else CCFLAGS = -shared endif +# The library needs to embed the SBCL runtime. `make.sh` does not build a +# linkable runtime, so it has to be produced separately by running +# `make-shared-library.sh` in the SBCL source tree, and neither `install.sh` nor +# most package managers install the result. Look in the usual places for it, and +# let the user point at it directly with e.g. `make LIBSBCL=/path/to/libsbcl.a`. +# +# Which file to expect depends on the platform: on arm64 macOS the SBCL runtime +# is only built as a static archive (see SBCL's Config.arm64-darwin), so a +# shared libsbcl does not exist there at all. +SBCL_CORE_DIR := $(dir $(shell $(SBCL) --noinform --no-sysinit --no-userinit --non-interactive \ + --eval '(princ (namestring sb-ext:*core-pathname*))' 2>/dev/null)) +LIBSBCL_SEARCH_DIRS := $(SBCL_HOME) $(SBCL_CORE_DIR) /usr/local/lib /usr/lib /opt/homebrew/lib +LIBSBCL_CANDIDATES := \ + $(foreach dir,$(LIBSBCL_SEARCH_DIRS),\ + $(dir)/libsbcl.a $(dir)/libsbcl.dylib $(dir)/libsbcl.so) + +LIBSBCL ?= $(firstword $(wildcard $(LIBSBCL_CANDIDATES))) + +# A static runtime must be linked whole: the entry points the generated +# libquil.c calls are reached only through the core, so without this the linker +# drops most of the archive. Its own dependencies have to be named explicitly +# too, since an archive records none. A shared runtime carries both properties +# already and just needs to be linked normally. +SBCL_STATIC_DEPS ?= $(shell pkg-config --libs libzstd 2>/dev/null || echo -lzstd) -lm -ldl -lpthread + +ifeq ($(suffix $(LIBSBCL)), .a) +ifeq ($(OS), Darwin) + LIBSBCL_LDFLAGS = -Wl,-force_load,$(LIBSBCL) $(SBCL_STATIC_DEPS) +else + LIBSBCL_LDFLAGS = -Wl,--whole-archive $(LIBSBCL) -Wl,--no-whole-archive $(SBCL_STATIC_DEPS) +endif +else ifeq ($(OS), Darwin) +# Link by path rather than -lsbcl: make-shared-library.sh names its output +# libsbcl.so even on macOS, and the -l flag only ever looks for libsbcl.dylib or +# libsbcl.a. + LIBSBCL_LDFLAGS = $(LIBSBCL) +else + LIBSBCL_LDFLAGS = -L$(dir $(LIBSBCL)) -lsbcl +endif + all: $(LIBQUIL_TARGET) libquil.core libquil.c libquil.h libquil.py: src/libquil.lisp src/qvm/*.lisp src/quilc/*.lisp - sbcl --dynamic-space-size 8192 --load "src/build-image.lisp" + $(SBCL) --dynamic-space-size 8192 --load "src/build-image.lisp" $(LIBQUIL_TARGET): libquil.core libquil.c - gcc $(CCFLAGS) -o $@ libquil.c -lsbcl +ifeq ($(LIBSBCL),) + @echo "error: no linkable SBCL runtime found." >&2 + @echo "Searched for libsbcl.a, libsbcl.dylib and libsbcl.so in:" >&2 + @$(foreach dir,$(LIBSBCL_SEARCH_DIRS),echo " $(dir)" >&2;) + @echo "" >&2 + @echo "Build one from an SBCL source tree of the SAME version as $(SBCL):" >&2 + @echo " sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh" >&2 + @echo "then point make at the result, e.g.:" >&2 + @echo " make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.a" >&2 + @exit 1 +endif + $(CC) $(CCFLAGS) -o $@ libquil.c $(LIBSBCL_LDFLAGS) clean: rm -f libquil.so libquil.c libquil.h libquil.core libquil.py libquil.dylib example diff --git a/README.md b/README.md index 703950c..40f79e5 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ We provide pre-built binaries for the following systems - Linux x64 (tested specifically on Ubuntu) - macOS x64 -Note that ARM is not yet supported on any of the above. +Note that we do not publish pre-built binaries for ARM. ARM machines, including +Apple Silicon, are supported when building from source — see [Building from +source](#building-from-source). ## Requirements @@ -59,6 +61,43 @@ replacing `` with the desired version, e.g. `0.3.0`. If you would like to manually install the library (for example in the case where you want to install the library to a non-standard location), find the appropriate version and operating system from the [releases page](https://github.com/rigetti/libquil/releases). Within the `.zip` archive you will find the library and header files that are required to use the library. Move these into your file system. +## Building from source + +Building requires an SBCL that has a *linkable runtime* — a `libsbcl.a` or +`libsbcl.so`/`libsbcl.dylib` that gets embedded into the library. Neither +`make.sh` nor most package managers produce one (Homebrew's `sbcl` bottle, for +instance, does not), so it has to be built from an SBCL source tree of the same +version as the `sbcl` used for the rest of the build: + +```bash +sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh && sh install.sh +``` + +`install.sh` places the runtime in SBCL's home directory, where the `Makefile` +finds it automatically. To use one from elsewhere, pass it explicitly: + +```bash +make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.a +``` + +Note that on arm64 macOS, SBCL only builds its runtime as the static +`libsbcl.a`; there is no shared `libsbcl.dylib` on that platform. The `Makefile` +handles either form. + +The Lisp dependencies (`quilc`, `qvm`, `magicl`, `sbcl-librarian`) are expected +in your Quicklisp local-projects directory. Then: + +```bash +make +``` + +### Linear algebra backend on arm64 macOS + +Homebrew's reference `lapack` computes incorrect eigenvectors on arm64, which +surfaces as `Could not find diagonalizer for matrix ... after 16 attempts` +during compilation. Install OpenBLAS (`brew install openblas`) and ensure +`magicl` loads it in preference to `lapack`. + # C API Reference ## Libquil functions and types diff --git a/src/build-image.lisp b/src/build-image.lisp index 95833a5..0656060 100644 --- a/src/build-image.lisp +++ b/src/build-image.lisp @@ -1,7 +1,34 @@ (require '#:asdf) -(asdf:load-system '#:sbcl-librarian) -(asdf:load-system '#:libquil) +;; libquil depends on systems that come from Quicklisp (cffi, bordeaux-threads, +;; ...). ASDF alone will not fetch those, so prefer Quicklisp when it is +;; available and fall back to plain ASDF for setups that vendor the +;; dependencies themselves. +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) + (when (and (null (find-package '#:quicklisp)) + (probe-file quicklisp-init)) + (load quicklisp-init))) + +(defun load-system (system) + ;; libquil intentionally redefines some of the alien callables that + ;; SBCL-LIBRARIAN generates, in order to give them types SBCL-LIBRARIAN cannot + ;; express yet (see the definition of quilc_compile_protoquil). SBCL signals a + ;; continuable error for that; taking the CONTINUE restart installs the new + ;; definition, which is what an interactive build does. Without this the build + ;; drops into the debugger and cannot run unattended. + (handler-bind ((error + (lambda (condition) + (let ((restart (find-restart 'continue condition))) + (when (and restart + (search "redefine alien callable" + (princ-to-string condition))) + (invoke-restart restart)))))) + (if (find-package '#:quicklisp) + (funcall (read-from-string "quicklisp:quickload") system) + (asdf:load-system system)))) + +(load-system '#:sbcl-librarian) +(load-system '#:libquil) (in-package #:libquil) diff --git a/src/quilc/api.lisp b/src/quilc/api.lisp index 0941969..5bbf90f 100644 --- a/src/quilc/api.lisp +++ b/src/quilc/api.lisp @@ -135,7 +135,7 @@ (("parse_quil" cl-quil.frontend:safely-parse-quil) quil-program ((source :string))) (("program_memory_type" parsed-program-get-memory-region-type) :void ((program quil-program) (region-name :string) (region-type-ptr :pointer))) (("print_program" cl-quil.frontend:print-parsed-program) :void ((program quil-program))) - (("compile_quil" cl-quil:compiler-hook) quil-program ((program quil-program) (chip-spec chip-specification))) + (("compile_quil" compile-quil) quil-program ((program quil-program) (chip-spec chip-specification))) (("compilation_metadata_len" compilation-metadata-len) :int ((metadata compilation-metadata))) (("compilation_metadata_get_final_rewiring" compilation-metadata-get-final-rewiring) :void diff --git a/src/quilc/compile.lisp b/src/quilc/compile.lisp index 76f1196..751b7de 100644 --- a/src/quilc/compile.lisp +++ b/src/quilc/compile.lisp @@ -68,6 +68,12 @@ (coerce runtime 'double-float))) present-p)) +(defun compile-quil (parsed-program chip-specification) + ;; As in COMPILE-PROTOQUIL, the LAPACK library for macOS will sometimes hit a + ;; division-by-zero. Mask those interrupts so they can be handled in Lisp. + (magicl:with-blapack + (cl-quil:compiler-hook parsed-program chip-specification))) + (defun compile-protoquil (parsed-program chip-specification metadata-ptr) (multiple-value-bind (compiled-program metadata) (magicl:with-blapack (process-program parsed-program chip-specification :protoquil t)) diff --git a/src/qvm/qvm-app-imports.lisp b/src/qvm/qvm-app-imports.lisp index c601ffb..a918f6a 100644 --- a/src/qvm/qvm-app-imports.lisp +++ b/src/qvm/qvm-app-imports.lisp @@ -12,14 +12,18 @@ ) (defun compress-program-qubits (quil) + "Remap the qubits of QUIL to a minimal sequential set from 0 to (num-qubits-used - 1). Return two values: the processed Quil code and the mapping vector. + +The mapping vector V specifies that the qubit as specified in the program V[i] has been mapped to qubit i." (let* ((quil (cl-quil:copy-instance quil)) + (mapping (cl-quil::compute-qubit-mapping quil)) (trivial-mapping-p - (loop :for x :across (cl-quil::compute-qubit-mapping quil) + (loop :for x :across mapping :for i :from 0 :always (= x i)))) (unless trivial-mapping-p (cl-quil::transform 'cl-quil::compress-qubits quil)) - quil)) + (values quil mapping))) (defun get-random-state (arg) (etypecase arg From 32e35588fbb9d0d8825649afd6d96937edb6706b Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 15:05:43 -0600 Subject: [PATCH 03/11] fix README notes on aarch64 --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 40f79e5..2b1fd2a 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,7 @@ We provide pre-built binaries for the following systems - Linux x64 (tested specifically on Ubuntu) - macOS x64 - -Note that we do not publish pre-built binaries for ARM. ARM machines, including -Apple Silicon, are supported when building from source — see [Building from -source](#building-from-source). +- macOS aarch64 ## Requirements @@ -80,7 +77,7 @@ finds it automatically. To use one from elsewhere, pass it explicitly: make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.a ``` -Note that on arm64 macOS, SBCL only builds its runtime as the static +Note that on aarch64 macOS, SBCL only builds its runtime as the static `libsbcl.a`; there is no shared `libsbcl.dylib` on that platform. The `Makefile` handles either form. @@ -91,9 +88,9 @@ in your Quicklisp local-projects directory. Then: make ``` -### Linear algebra backend on arm64 macOS +### Linear algebra backend on aarch64 macOS -Homebrew's reference `lapack` computes incorrect eigenvectors on arm64, which +Homebrew's reference `lapack` computes incorrect eigenvectors on aarch64, which surfaces as `Could not find diagonalizer for matrix ... after 16 attempts` during compilation. Install OpenBLAS (`brew install openblas`) and ensure `magicl` loads it in preference to `lapack`. From 975064e9a5eb8b4aa4adfcdf29f34ca3b4cfb402 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 15:06:11 -0600 Subject: [PATCH 04/11] GHA fixes --- .github/workflows/build.yml | 26 ++++++++++++++------------ .github/workflows/release.yml | 6 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 193d916..e38296d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,13 +13,13 @@ jobs: - name: root suid tar run: sudo chown root /bin/tar && sudo chmod u+s /bin/tar - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: "libquil" - name: Cache SBCL id: cache-sbcl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | /usr/local/lib/libsbcl.so @@ -76,7 +76,7 @@ jobs: mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.so $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil - name: Store artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-amd64 path: ${{ github.workspace }}/artifacts/* @@ -87,13 +87,13 @@ jobs: steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: "libquil" - name: Cache SBCL id: cache-sbcl-macos - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | /usr/local/lib/libsbcl.so @@ -101,7 +101,7 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-build-${{ hashFiles('**/macos-versions' )}} + key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/macos-versions' )}} - uses: Homebrew/actions/setup-homebrew@master name: Setup homebrew @@ -157,7 +157,7 @@ jobs: mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil - name: Store artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: macos path: ${{ github.workspace }}/artifacts/* @@ -168,13 +168,13 @@ jobs: steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: "libquil" - name: Cache SBCL id: cache-sbcl-macos - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | /usr/local/lib/libsbcl.so @@ -182,7 +182,7 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-build-${{ hashFiles('**/macos-versions' )}} + key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/macos-versions' )}} - uses: Homebrew/actions/setup-homebrew@master name: Setup homebrew @@ -238,8 +238,10 @@ jobs: mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil - name: Store artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: macos + # Must differ from the x64 macOS job artifact name: upload-artifact + # v4 rejects two artifacts sharing a name within a single run. + name: macos-arm64 path: ${{ github.workspace }}/artifacts/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1dbdde4..9c4990a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,12 +25,12 @@ jobs: git config --global user.name "${{ github.triggering_actor }}" git config --global user.email "${{ github.triggering_actor }}@users.noreply.github.com" - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download linux artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-amd64 path: artifacts/ @@ -40,7 +40,7 @@ jobs: cd artifacts && zip linux-amd64.zip libquil/libsbcl.so libquil/libquil.so libquil/libquil.core libquil/libquil.h - name: Download macos artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: macos path: artifacts/ From b659ea153c31bc799351263011810c778c700270 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 15:12:16 -0600 Subject: [PATCH 05/11] Drop Intel macOS support Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 93 +++-------------------------------- .github/workflows/release.yml | 19 ++++--- README.md | 9 ++-- install.sh | 26 +++++++--- knope.toml | 2 +- 5 files changed, 45 insertions(+), 104 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e38296d..162aed4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,92 +79,13 @@ jobs: uses: actions/upload-artifact@v4 with: name: linux-amd64 - path: ${{ github.workspace }}/artifacts/* - - build-macos: - name: Build libquil.dylib - runs-on: macos-latest - - steps: - - - uses: actions/checkout@v4 - with: - path: "libquil" - - - name: Cache SBCL - id: cache-sbcl-macos - uses: actions/cache@v4 - with: - path: | - /usr/local/lib/libsbcl.so - /usr/local/lib/sbcl - /usr/local/bin/sbcl - ~/quicklisp - ~/.sbclrc - key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/macos-versions' )}} - - - uses: Homebrew/actions/setup-homebrew@master - name: Setup homebrew - - - if: steps.cache-sbcl-macos.outputs.cache-hit != 'true' - name: Install SBCL, libraries, and quicklisp - run: | - brew install sbcl git - git clone --branch x86-null-tn https://git.code.sf.net/p/sbcl/sbcl /tmp/sbcl - cd /tmp/sbcl && sudo sh make.sh --without-compact-instance-header --without-immobile-space --without-immobile-code && sudo sh make-shared-library.sh - brew remove -f sbcl - sudo sh install.sh - sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so - - brew install wget - wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" - sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' - echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc - rm -f /tmp/quicklisp.lisp - cat ~/.sbclrc - - - name: Pull Lisp dependencies - run: | - git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a - git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 - git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl - git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation - git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 - - - name: Install quilc dependencies - run: brew install lapack openblas libffi gfortran - - - name: Build libquil.dylib - run: | - cd $GITHUB_WORKSPACE/libquil - ls - sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' - sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' - make - - - name: ls - run: | - ls $GITHUB_WORKSPACE/libquil/libquil.dylib - - - name: Test - run: | - cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test - cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test - - - name: Package files - run: | - mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil - - - name: Store artifact - uses: actions/upload-artifact@v4 - with: - name: macos - path: ${{ github.workspace }}/artifacts/* + # A directory (not a glob): upload-artifact v4 preserves structure + # relative to it, keeping the libquil/ prefix the release job unzips. + path: ${{ github.workspace }}/artifacts build-macos-arm: name: Build Apple-Silicon libquil.dylib - runs-on: macos-latest-xlarge + runs-on: macos-latest steps: @@ -240,8 +161,8 @@ jobs: - name: Store artifact uses: actions/upload-artifact@v4 with: - # Must differ from the x64 macOS job artifact name: upload-artifact - # v4 rejects two artifacts sharing a name within a single run. name: macos-arm64 - path: ${{ github.workspace }}/artifacts/* + # A directory (not a glob): upload-artifact v4 preserves structure + # relative to it, keeping the libquil/ prefix the release job unzips. + path: ${{ github.workspace }}/artifacts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c4990a..033f220 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,31 +29,34 @@ jobs: with: fetch-depth: 0 + # Each artifact is downloaded into its own directory: the two macOS + # artifacts contain identically named files, so a shared directory would + # have one overwrite the other. - name: Download linux artifacts uses: actions/download-artifact@v4 with: name: linux-amd64 - path: artifacts/ + path: artifacts/linux-amd64/ - name: Archive linux artifacts run: | - cd artifacts && zip linux-amd64.zip libquil/libsbcl.so libquil/libquil.so libquil/libquil.core libquil/libquil.h + cd artifacts/linux-amd64 && zip ../linux-amd64.zip libquil/libsbcl.so libquil/libquil.so libquil/libquil.core libquil/libquil.h - - name: Download macos artifacts + - name: Download macos arm64 artifacts uses: actions/download-artifact@v4 with: - name: macos - path: artifacts/ + name: macos-arm64 + path: artifacts/macos-arm64/ - - name: Archive linux artifacts + - name: Archive macos arm64 artifacts run: | - cd artifacts && zip macos.zip libquil/libsbcl.so libquil/libquil.dylib libquil/libquil.core libquil/libquil.h + cd artifacts/macos-arm64 && zip ../macos-arm64.zip libquil/libsbcl.so libquil/libquil.dylib libquil/libquil.core libquil/libquil.h - name: List artifacts run: | ls -R artifacts/ unzip -l artifacts/linux-amd64.zip - unzip -l artifacts/macos.zip + unzip -l artifacts/macos-arm64.zip - name: Install Knope uses: knope-dev/action@v2.0.0 diff --git a/README.md b/README.md index 2b1fd2a..c2b83e5 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,11 @@ We provide pre-built binaries for the following systems - Linux x64 (tested specifically on Ubuntu) -- macOS x64 -- macOS aarch64 +- macOS aarch64 (Apple Silicon) + +Intel macOS binaries are no longer published. Other platforms, including Intel +macOS, can be built from source — see [Building from +source](#building-from-source). ## Requirements @@ -23,7 +26,7 @@ These libraries are required by `libquil`: On systems which use `apt` to install packages (e.g. Ubuntu), these libraries can be installed with the command ``` -sudo apt install libblas-dev libffi-dev libffi7 liblapack-dev libz-dev +sudo apt install libblas-dev libffi-dev liblapack-dev libz-dev ``` On systems which use `brew` to install packages (e.g macOS), these libraries can be installed with the command diff --git a/install.sh b/install.sh index 9012dcf..d4e4061 100755 --- a/install.sh +++ b/install.sh @@ -7,10 +7,6 @@ err() { exit 1 } -if [[ "$(uname -p)" =~ "arm" ]]; then - err "Unsupported CPU architecture: $(uname -p)." -fi - if [[ -n "${1-}" ]] then LIBQUIL_URL_PREFIX="https://github.com/rigetti/libquil/releases/download/v${1}" @@ -19,13 +15,31 @@ else fi OS="$(uname)" +ARCH="$(uname -m)" if [[ "${OS}" == "Linux" ]] then IS_LINUX=1 - LIBQUIL_RELEASE_FILE="linux-amd64.zip" + case "${ARCH}" in + x86_64 | amd64) + LIBQUIL_RELEASE_FILE="linux-amd64.zip" + ;; + *) + err "Unsupported CPU architecture for Linux: ${ARCH}. Only x86_64 is supported." \ + "You can build libquil from source; see https://github.com/rigetti/libquil#building-from-source" + ;; + esac elif [[ "${OS}" == "Darwin" ]] then - LIBQUIL_RELEASE_FILE="macos.zip" + case "${ARCH}" in + arm64 | aarch64) + LIBQUIL_RELEASE_FILE="macos-arm64.zip" + ;; + *) + err "Unsupported CPU architecture for macOS: ${ARCH}. Only Apple Silicon (arm64) is supported." \ + "Intel macOS builds are no longer published. You can build libquil from source; see" \ + "https://github.com/rigetti/libquil#building-from-source" + ;; + esac else err "Unsupported operating system. Supported operating systems are Linux and macOS." fi diff --git a/knope.toml b/knope.toml index 78193a2..366aa5d 100644 --- a/knope.toml +++ b/knope.toml @@ -7,7 +7,7 @@ changelog = "CHANGELOG.md" path = "artifacts/linux-amd64.zip" [[package.assets]] -path = "artifacts/macos.zip" +path = "artifacts/macos-arm64.zip" [[workflows]] name = "release" From ff1fb635676267ec31a37d659a9d5579eb42c216 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 16:32:50 -0600 Subject: [PATCH 06/11] small fixes --- .github/workflows/build.yml | 9 ++-- src/quilc/compile.lisp | 2 +- src/quilc/quilc-imports.lisp | 86 +++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 162aed4..00753bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,14 +49,17 @@ jobs: - name: Pull Lisp dependencies run: | - git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a + git clone https://github.com/quil-lang/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout 3d83b665b3addc33be2f696b1cdebc0c4c5a04f9 git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 - name: Install quilc dependencies - run: sudo apt update && sudo apt install -y libblas-dev libffi-dev libffi7 liblapack-dev libz-dev gfortran + # libffi-dev pulls in whichever libffi runtime the image ships; naming + # it explicitly breaks whenever the runner image moves (libffi7 does not + # exist on ubuntu 24.04, which ubuntu-latest now resolves to). + run: sudo apt update && sudo apt install -y libblas-dev libffi-dev liblapack-dev libz-dev gfortran - name: Build libquil.so run: | @@ -128,7 +131,7 @@ jobs: - name: Pull Lisp dependencies run: | - git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a + git clone https://github.com/quil-lang/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout 3d83b665b3addc33be2f696b1cdebc0c4c5a04f9 git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation diff --git a/src/quilc/compile.lisp b/src/quilc/compile.lisp index 751b7de..813d8ed 100644 --- a/src/quilc/compile.lisp +++ b/src/quilc/compile.lisp @@ -81,5 +81,5 @@ (let ((handle (sbcl-librarian::make-handle metadata))) (setf (sb-alien:deref metadata-ptr) handle))) - (cl-quil.frontend::transform 'cl-quil.frontend::process-protoquil compiled-program) + (cl-quil.frontend::transform 'process-protoquil compiled-program) compiled-program)) diff --git a/src/quilc/quilc-imports.lisp b/src/quilc/quilc-imports.lisp index 9095569..0aaf033 100644 --- a/src/quilc/quilc-imports.lisp +++ b/src/quilc/quilc-imports.lisp @@ -32,6 +32,90 @@ :documentation "The git hash of the quilc repo.") ;;; borrowed from quilc: app/src/entry-point.lisp +;;; +;;; PROCESS-PROTOQUIL and STRIP-FINAL-HALT-RESPECTING-REWIRINGS live in quilc's +;;; *application*, which we do not depend on, so they are vendored here rather +;;; than requiring a quilc fork that exposes them from the library. +;;; TODO(https://github.com/quil-lang/quilc/pull/933): remove these once the logic is upstreamed + +(cl-quil.frontend::define-transform process-protoquil (process-protoquil) + "Removes HALT, DEFCIRCUIT, and DEFGATE instructions.") + +(defun process-protoquil (parsed-program) + (setf (cl-quil.frontend::parsed-program-circuit-definitions parsed-program) nil + (cl-quil.frontend::parsed-program-gate-definitions parsed-program) nil) + + ;; if we're supposed to output protoQuil, we also need to + ;; strip the final HALT instructions from the output + (setf (cl-quil:parsed-program-executable-code parsed-program) + (strip-final-halt-respecting-rewirings parsed-program))) + +(defun strip-final-halt-respecting-rewirings (processed-program) + "Remove the final HALT instruction, if any, from PROCESSED-PROGRAM, retaining any attached rewiring comments." + (let* ((instructions (cl-quil:parsed-program-executable-code processed-program)) + (last-instruction (and (plusp (length instructions)) + (cl-quil::nth-instr 0 processed-program :from-end t))) + (penultimate-instruction (and (< 1 (length instructions)) + (cl-quil::nth-instr 1 processed-program :from-end t))) + (must-transfer-comment-p (and (not (null penultimate-instruction)) + (cl-quil.frontend::comment last-instruction)))) + + (unless (cl-quil::haltp last-instruction) + (return-from strip-final-halt-respecting-rewirings instructions)) + + (when must-transfer-comment-p + ;; Transfer the rewiring comment from LAST-INSTRUCTION to + ;; PENULTIMATE-INSTRUCTION. + (multiple-value-bind (last-entering last-exiting) + (cl-quil::instruction-rewirings last-instruction) + (multiple-value-bind (penultimate-entering penultimate-exiting) + (cl-quil::instruction-rewirings penultimate-instruction) + (flet ((assert-rewirings-compatible (rewiring-type last-rewiring penultimate-rewiring) + ;; This bit of hoop-jumping guards against the + ;; unlikely event that both PENULTIMATE-INSTRUCTION + ;; and LAST-INSTRUCTION have rewiring comments + ;; attached which might be incompatible. We check + ;; to ensure that either one of the rewirings is + ;; NULL, or else they are EQUALP and can safely be + ;; merged. + (assert (or (or (null last-rewiring) + (null penultimate-rewiring)) + (equalp last-rewiring penultimate-rewiring)) + () + "Failed to strip final HALT. Instructions have incompatible ~A rewirings:~@ + LAST: ~A ~A~@ + PREV: ~A ~A" + rewiring-type last-instruction last-rewiring + penultimate-instruction penultimate-rewiring))) + (assert-rewirings-compatible ':ENTERING last-entering penultimate-entering) + (assert-rewirings-compatible ':EXITING last-exiting penultimate-exiting)) + ;; Consider the following cases for the :ENTERING rewirings + ;; (the same case analysis applies to the :EXITING rewiring + ;; pair as well). + ;; + ;; 1) If both the rewirings are non-NIL, then the + ;; ASSERT-REWIRINGS-COMPATIBLE check above guarantees + ;; that they are EQUALP, and it doesn't matter which one + ;; we select. + ;; + ;; 2) If only one is non-NIL, the OR selects it. + ;; + ;; 3) If both are NIL, then MAKE-REWIRING-COMMENT just + ;; ignores that keyword argument, and returns an :EXITING + ;; rewiring. + ;; + ;; Finally, (COMMENT LAST-INSTRUCTION) is non-NIL (otherwise + ;; MUST-TRANSFER-COMMENT-P would be NIL), so at least one of + ;; LAST-ENTERING and LAST-EXITING is non-NIL, which means + ;; that at least one of the :ENTERING and :EXITING keyword + ;; args to MAKE-REWIRING-COMMENT is non-NIL and hence the + ;; call will produce a rewiring comment. + (setf (cl-quil.frontend::comment penultimate-instruction) + (cl-quil::make-rewiring-comment :entering (or last-entering penultimate-entering) + :exiting (or last-exiting penultimate-exiting)))))) + + ;; Strip the final HALT instruction. + (subseq instructions 0 (1- (length instructions))))) (defun process-program (program chip-specification &key @@ -63,7 +147,7 @@ Returns a values tuple (PROCESSED-PROGRAM, STATISTICS), where PROCESSED-PROGRAM (cl-quil:compiler-hook program chip-specification :protoquil protoquil :destructive t) (when protoquil - (cl-quil.frontend::transform 'cl-quil::process-protoquil processed-program) + (cl-quil.frontend::transform 'process-protoquil processed-program) ;; Compute statistics for protoquil program (compute-statistics processed-program chip-specification statistics :gate-whitelist gate-whitelist :gate-blacklist gate-blacklist) From 50f53b912a7d8e1a5a2af149385226ecc7a90f65 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 17:02:13 -0600 Subject: [PATCH 07/11] feat!: build against modern sbcl-librarian Moves off the 2023 sbcl-librarian pin, which also unblocks modern SBCL. libquil is now the generated C bindings only; the Lisp image lives in a core loaded by the libsbcl_librarian runtime, whose constructor initializes Lisp when it is loaded. The explicit init(core) call is gone, as is libquil's hand-rolled error handling. libquil keeps shipping a core rather than the FASL bundles that CREATE-FASL-LIBRARY-CMAKE-PROJECT produces. FASL bundles are re-loaded on every startup, re-running load-time code, and cl-quil resolves stdgates.quil through ASDF:SYSTEM-RELATIVE-PATHNAME at load time -- so a FASL build only runs where quilc's source tree sits at the path recorded when it was built. A core evaluates that once, at build time. build-image.lisp emits three things from one image: libquil's bindings, the runtime's bindings, and the core exporting both sets of callables. The Makefile builds the runtime too, so plain 'make' still yields a usable artifact. Errors move to the runtime's get_error_message/lisp_err_t. libquil redefines default-error-map so ordinary bad input reports as LISP_ERR_FAILURE rather than an internal bug, handling T rather than CL:ERROR because cl-quil signals conditions that are not subtypes of ERROR. REARCHITECTURE.md records the decisions. Needs three sbcl-librarian fixes, on its fix-secondary-system-bundles branch. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 2 + Makefile | 123 ++++++++++++++----------- REARCHITECTURE.md | 207 +++++++++++++++++++++++++++++++++++++++++++ src/build-image.lisp | 86 ++++++++++++++---- src/libquil.lisp | 50 ++++++----- src/quilc/api.lisp | 9 +- src/qvm/api.lisp | 2 +- 7 files changed, 384 insertions(+), 95 deletions(-) create mode 100644 REARCHITECTURE.md diff --git a/.gitignore b/.gitignore index 3387c30..9fad6e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ libquil.h libquil.so libquil.dylib libquil.py +build/ +runtime/ diff --git a/Makefile b/Makefile index 8720580..b2c8c37 100644 --- a/Makefile +++ b/Makefile @@ -1,75 +1,94 @@ -.PHONY: all clean +.PHONY: all clean runtime OS := $(shell uname -s) SBCL ?= sbcl +CC ?= cc ifeq ($(OS), Darwin) - LIBQUIL_TARGET = libquil.dylib - CCFLAGS = -dynamiclib + SHARED_SUFFIX = .dylib + SHARED_FLAGS = -dynamiclib else - LIBQUIL_TARGET = libquil.so - CCFLAGS = -shared + SHARED_SUFFIX = .so + SHARED_FLAGS = -shared endif -# The library needs to embed the SBCL runtime. `make.sh` does not build a -# linkable runtime, so it has to be produced separately by running -# `make-shared-library.sh` in the SBCL source tree, and neither `install.sh` nor -# most package managers install the result. Look in the usual places for it, and -# let the user point at it directly with e.g. `make LIBSBCL=/path/to/libsbcl.a`. +LIBQUIL_TARGET = libquil$(SHARED_SUFFIX) + +# libquil is built against modern sbcl-librarian (see REARCHITECTURE.md), which +# splits the work in two: +# +# runtime/libsbcl_librarian$(SHARED_SUFFIX) the SBCL runtime; a constructor +# initializes Lisp when it is loaded +# runtime/libquil.core the Lisp image, holding libquil and +# its dependencies +# libquil$(SHARED_SUFFIX) the generated C bindings # -# Which file to expect depends on the platform: on arm64 macOS the SBCL runtime -# is only built as a static archive (see SBCL's Config.arm64-darwin), so a -# shared libsbcl does not exist there at all. +# Both halves are built here, so `make` alone still produces a usable artifact. +RUNTIME_DIR := runtime +RUNTIME_LIB := $(RUNTIME_DIR)/libsbcl_librarian$(SHARED_SUFFIX) +CORE := $(RUNTIME_DIR)/libquil.core + +SBCL_LIBRARIAN_DIR := $(shell $(SBCL) --noinform --non-interactive \ + --eval '(require :asdf)' \ + --eval '(princ (namestring (asdf:system-source-directory "sbcl-librarian")))' 2>/dev/null) + +# The linkable SBCL runtime. `make.sh` does not build one and package managers do +# not ship it, so it comes from an SBCL source tree built with +# `make-shared-library.sh`; `install.sh` puts it in SBCL's home directory. Note +# that SBCL names it libsbcl.so on every platform, including macOS. SBCL_CORE_DIR := $(dir $(shell $(SBCL) --noinform --no-sysinit --no-userinit --non-interactive \ --eval '(princ (namestring sb-ext:*core-pathname*))' 2>/dev/null)) -LIBSBCL_SEARCH_DIRS := $(SBCL_HOME) $(SBCL_CORE_DIR) /usr/local/lib /usr/lib /opt/homebrew/lib -LIBSBCL_CANDIDATES := \ - $(foreach dir,$(LIBSBCL_SEARCH_DIRS),\ - $(dir)/libsbcl.a $(dir)/libsbcl.dylib $(dir)/libsbcl.so) +LIBSBCL_SEARCH_DIRS := $(SBCL_HOME) $(SBCL_CORE_DIR) $(SBCL_CORE_DIR).. \ + /usr/local/lib /usr/lib /opt/homebrew/lib +LIBSBCL ?= $(firstword $(wildcard \ + $(foreach dir,$(LIBSBCL_SEARCH_DIRS),$(dir)/libsbcl.so $(dir)/libsbcl.dylib))) -LIBSBCL ?= $(firstword $(wildcard $(LIBSBCL_CANDIDATES))) - -# A static runtime must be linked whole: the entry points the generated -# libquil.c calls are reached only through the core, so without this the linker -# drops most of the archive. Its own dependencies have to be named explicitly -# too, since an archive records none. A shared runtime carries both properties -# already and just needs to be linked normally. -SBCL_STATIC_DEPS ?= $(shell pkg-config --libs libzstd 2>/dev/null || echo -lzstd) -lm -ldl -lpthread - -ifeq ($(suffix $(LIBSBCL)), .a) -ifeq ($(OS), Darwin) - LIBSBCL_LDFLAGS = -Wl,-force_load,$(LIBSBCL) $(SBCL_STATIC_DEPS) -else - LIBSBCL_LDFLAGS = -Wl,--whole-archive $(LIBSBCL) -Wl,--no-whole-archive $(SBCL_STATIC_DEPS) -endif -else ifeq ($(OS), Darwin) -# Link by path rather than -lsbcl: make-shared-library.sh names its output -# libsbcl.so even on macOS, and the -l flag only ever looks for libsbcl.dylib or -# libsbcl.a. - LIBSBCL_LDFLAGS = $(LIBSBCL) -else - LIBSBCL_LDFLAGS = -L$(dir $(LIBSBCL)) -lsbcl -endif +# libsbcl needs zstd for core compression; pkg-config knows where it is on systems +# that install it outside the default search path (Homebrew, in particular). +ZSTD_LIBS ?= $(shell pkg-config --libs libzstd 2>/dev/null || echo -lzstd) all: $(LIBQUIL_TARGET) -libquil.core libquil.c libquil.h libquil.py: src/libquil.lisp src/qvm/*.lisp src/quilc/*.lisp - $(SBCL) --dynamic-space-size 8192 --load "src/build-image.lisp" +runtime: $(RUNTIME_LIB) + +# One image produces everything Lisp-side: libquil's bindings, the runtime's +# bindings, and the core that backs both. +$(CORE) libquil.c libquil.h $(RUNTIME_DIR)/sbcl_librarian.c: src/libquil.lisp src/qvm/*.lisp src/quilc/*.lisp src/build-image.lisp + mkdir -p $(RUNTIME_DIR) + $(SBCL) --dynamic-space-size 8192 --non-interactive --load "src/build-image.lisp" + # The core is named after the aggregate library that defines its exports + # (libquil-core); publish it beside the runtime under the name the runtime + # was compiled to look for. + mv libquil_core.core $(CORE) -$(LIBQUIL_TARGET): libquil.core libquil.c +# The runtime is told to load libquil.core rather than the stock +# sbcl_librarian.core, so that libquil's image is what comes up. +$(RUNTIME_LIB): $(RUNTIME_DIR)/sbcl_librarian.c ifeq ($(LIBSBCL),) - @echo "error: no linkable SBCL runtime found." >&2 - @echo "Searched for libsbcl.a, libsbcl.dylib and libsbcl.so in:" >&2 + @echo "error: no linkable SBCL runtime (libsbcl.so) found." >&2 + @echo "Searched:" >&2 @$(foreach dir,$(LIBSBCL_SEARCH_DIRS),echo " $(dir)" >&2;) - @echo "" >&2 - @echo "Build one from an SBCL source tree of the SAME version as $(SBCL):" >&2 - @echo " sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh" >&2 - @echo "then point make at the result, e.g.:" >&2 - @echo " make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.a" >&2 + @echo "Build one from an SBCL source tree of the same version as $(SBCL):" >&2 + @echo " sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh" >&2 + @echo "then re-run make, or pass LIBSBCL=/path/to/libsbcl.so" >&2 @exit 1 endif - $(CC) $(CCFLAGS) -o $@ libquil.c $(LIBSBCL_LDFLAGS) + mkdir -p $(RUNTIME_DIR) + cp $(LIBSBCL) $(RUNTIME_DIR)/libsbcl.so + cp "$(SBCL_LIBRARIAN_DIR)lib/sbcl_librarian_err.h" $(RUNTIME_DIR)/ + cd $(RUNTIME_DIR) && $(CC) $(SHARED_FLAGS) -o libsbcl_librarian$(SHARED_SUFFIX) \ + sbcl_librarian.c \ + "$(SBCL_LIBRARIAN_DIR)lib/entry_point.c" \ + -DLIBSBCL_LIBRARIAN_API_BUILD \ + -DSBCL_LIBRARIAN_CORE_NAME='"libquil.core"' \ + -I. -I"$(SBCL_LIBRARIAN_DIR)lib" -L. -lsbcl $(ZSTD_LIBS) + +$(LIBQUIL_TARGET): libquil.c $(CORE) $(RUNTIME_LIB) + $(CC) $(SHARED_FLAGS) -o $@ libquil.c \ + -I. -I$(RUNTIME_DIR) -I"$(SBCL_LIBRARIAN_DIR)lib" \ + -L$(RUNTIME_DIR) -lsbcl_librarian clean: - rm -f libquil.so libquil.c libquil.h libquil.core libquil.py libquil.dylib example + rm -rf $(RUNTIME_DIR) build + rm -f libquil.so libquil.dylib libquil.h libquil.c libquil.core libquil.py example diff --git a/REARCHITECTURE.md b/REARCHITECTURE.md new file mode 100644 index 0000000..23494b0 --- /dev/null +++ b/REARCHITECTURE.md @@ -0,0 +1,207 @@ +# libquil on modern sbcl-librarian + +Working notes and decision record for the `sbcl-librarian-runtime` branch, which +moves libquil from the 2023-era sbcl-librarian (pinned at `04f7e39`) onto current +`main`. Written as the work happened; decisions are recorded with their reasons so +they can be revisited. + +## Why this is not a small change + +The obvious motivation was adopting sbcl-librarian's built-in error handling and +unblocking modern SBCL. Both turned out to be gated on a redesign that landed +upstream in `2a12bd0` (2024-01-23, "Add libsbcl_librarian CMake project + Conda +recipe"). + +Two facts establish that there is no cheaper path: + +1. The `sb-int::int-sap` -> `sb-sys:int-sap` fix that modern SBCL requires landed + in `b8dc014` (2024-05-22), *after* the redesign. There is no version window + offering modern-SBCL compatibility with the old architecture. +2. On current `main`, the generated `init(char* core)` is dead code: it declares + its own `static int initialized`, shadowing the global that every generated API + wrapper checks. Calling it leaves every function returning + `LISP_ERR_NOT_INITIALIZED`. + +## The old model vs the new one + +Old (what libquil does on `main`): + +- `build-bindings` + `build-core-and-die` produce `libquil.c/.h` and `libquil.core`. +- `libquil.dylib` is self-contained, statically embedding the SBCL runtime. +- The consumer calls `init("/path/to/libquil.core")` explicitly. + +New (what this branch moves to): + +- A **runtime** is built once: `libsbcl_librarian.dylib` plus `sbcl_librarian.core`. + It initializes Lisp from a shared-library constructor at load time, locating its + core by name next to itself, with heap size from `SBCL_LIBRARIAN_HEAP_SIZE`. +- Each consumer is a **FASL library**: a small shared library holding the generated + C bindings, incbin-embedded FASL bundles for its ASDF system and dependencies, + and a constructor that loads those bundles into the already-running image. + `create-fasl-library-cmake-project` generates the whole CMake project. +- There is no per-consumer `.core`, and no explicit init call. + +## Decisions + +### D1: Follow the upstream FASL-library model rather than pinning a fork + +Rejected alternatives: (a) stay on `04f7e39` and carry the one-line `int-sap` +patch forever; (b) fork sbcl-librarian to keep `define-api :error-map` alive. + +Both work, but they permanently diverge from upstream and keep libquil's +hand-rolled error handling, which upstream has since absorbed. Since we are +re-architecting anyway, match upstream so future updates are merges rather than +rebases. + +Note (a) remains the correct *fallback* and is preserved on the +`build-apple-aarch64` branch, which builds and passes 22/22 today. + +### D2: Adopt sbcl-librarian's built-in error handling; accept the ABI break + +libquil's `*last-error*` / `libquil_error()` / `libquil_error_t` are replaced by +upstream's `get_error_message()` / `lisp_err_t`. This is a breaking change to +libquil's C ABI. + +Accepted because libquil is pre-1.0, the only known consumer is libquil-sys (which +we control and update in lockstep), and the replacement is strictly better: it +distinguishes failure from bug from fatal, and supports backtraces. + +`quilc_compile_protoquil`'s hand-written `define-alien-callable` (which exists +because sbcl-librarian cannot express `(:pointer :pointer)`) now records into +`sbcl-librarian::*error-message*` so it reports through the same channel. + +### D3: Ship a core, not FASL bundles + +Upstream's consumer path is `create-fasl-library-cmake-project`, which embeds a +FASL bundle per ASDF system into the library and loads them into the image at +startup. libquil was built that way first, and it worked -- but it is not +distributable: + +- FASL bundles re-run load-time code on every startup. `cl-quil` computes + `*default-standard-gates-file*` with `asdf:system-relative-pathname` and calls + `initialize-standard-gates` inside an `eval-when (:load-toplevel)`, so every + process that loaded libquil re-read `stdgates.quil` **from quilc's source tree**, + at the path baked in when the bundles were built. An installed artifact on a + machine without quilc's sources would fail. +- The same load-time chatter (`; loading standard gates from ...`) went to stdout + of every process that linked libquil, which among other things broke + `cargo nextest`'s test enumeration. + +Saving a core evaluates all of that once, at build time. So libquil keeps a core -- +what it always shipped -- and gets the modern runtime, init and error handling +around it. + +The core is loaded by the runtime rather than by an explicit `init(core)` call, +which is why D6 exists. + +### D4: Build the runtime manually rather than via its CMake project + +Upstream's `lib/CMakeLists.txt` invokes the generator with `sbcl --script`, which +skips `~/.sbclrc` and therefore Quicklisp — so `generate-bindings.lisp`'s +`(asdf:load-system :swank)` fails with `Component :SWANK not found`. + +Rather than patch upstream's CMake, `src/build-image.lisp` defines the runtime's +aggregate library itself and emits `runtime/sbcl_librarian.c` alongside libquil's +own bindings. One image therefore produces everything: libquil's bindings, the +runtime's bindings, and the core that backs both. That also avoids the swank +dependency and the second, discarded core that upstream's generator would build. + +### D5: Point the runtime at libquil's core + +`entry_point.c` hardcoded `sbcl_librarian.core`, looked up next to the runtime +library. Because libquil ships its own core (D3), the runtime has to load that +instead. Upstream now takes a compile-time `SBCL_LIBRARIAN_CORE_NAME`, defaulting +to the old name, and libquil builds the runtime with `-DSBCL_LIBRARIAN_CORE_NAME='"libquil.core"'`. + +Naming libquil's core `sbcl_librarian.core` would have avoided the patch, but two +sbcl-librarian consumers installed into the same directory would then overwrite +each other's core. + +### D6: Map ordinary errors to `LISP_ERR_FAILURE`, and handle `T` + +The stock `default-error-map` classifies any `cl:error` as `LISP_ERR_BUG`, prefixes +the message with "Internal lisp bug:" and attaches a backtrace. Nearly everything +libquil signals is user error -- malformed Quil, an unknown memory region -- so +libquil redefines the map for its own callables. `wrap-error-handling` is consulted +when a callable is compiled, so this affects only libquil's; the runtime's own APIs +keep upstream behaviour. + +The handler binds `T`, not `cl:error`. cl-quil signals conditions that are not +subtypes of `error` -- `invalid-instruction-condition` has no supertype at all -- +and with a `cl:error` handler those escape into the debugger and hang the calling +process. libquil's original error map bound `T` for the same reason. + +### D7: Keep shipping the SBCL runtime as `libsbcl.so`, even on macOS + +SBCL's `make-shared-library.sh` emits `libsbcl.so` on every platform, and that +string becomes the install name recorded in `libsbcl_librarian.dylib`. Renaming the +file to `.dylib` breaks loading unless the install name is rewritten too. Upstream's +own example Makefile notes the same quirk. We ship `libsbcl.so` and leave the name +alone. + +## What `make` produces + + libquil.dylib generated C bindings (~40 KB) + libquil.h its header + runtime/libsbcl_librarian.dylib SBCL runtime; initializes Lisp on load + runtime/libquil.core the Lisp image + runtime/libsbcl.so the linkable SBCL runtime + runtime/sbcl_librarian.h runtime API (get_error_message, handles) + runtime/sbcl_librarian_err.h lisp_err_t and the fatal-error plumbing + +All of `runtime/` must be installed together, and `libquil.core` must sit beside +`libsbcl_librarian`, since the runtime finds its core relative to its own location. + +## Prerequisite: SBCL with a linkable runtime + +Homebrew's bottle does not ship one, so SBCL must come from source: + + sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh && sh install.sh + +## Upstream sbcl-librarian changes this depends on + +Three fixes, on the `fix-secondary-system-bundles` branch: + +1. **Secondary systems lost their FASL bundles.** The output-translation pattern was + built from the flattened bundle name (`magicl--core--system.fasl`), but ASDF + writes `magicl/core--system.fasl`, turning the slash into a directory. Bundles + for `magicl/core`, `magicl/ext*` and `cl-quil/frontend` were silently left in the + ASDF cache and the generated CMake project referenced files that did not exist. + (Found while libquil was still on the FASL path; kept because it is a real bug.) +2. **`liblibquil`.** The generated CMake project cleared the library prefix only on + Windows, so a library whose name starts with `lib` built as `liblibquil.so` on + Unix. Upstream's own libcalc example links `-lcalc`, which only resolves with the + prefix cleared. +3. **`SBCL_LIBRARIAN_CORE_NAME`** (D5). + +## Status + +- [x] Consumer model determined +- [x] Runtime builds and self-initializes on arm64 macOS +- [x] libquil builds; `make` alone produces a usable artifact +- [x] Verified from C: parse, chip, compile, program string, and error reporting +- [x] libquil-sys updated to the new ABI -- 22/22 tests pass +- [x] Python bindings rebuilt and exercised +- [x] C examples updated and passing (`make test` in both `examples/` directories) +- [x] CI, `install.sh` and release archives updated for the new artifact set +- [x] Packaging verified by simulating package -> zip -> install -> build and + running the libquil-sys suite against the installed layout (22/22) + +## Open + +**CI cannot go green until the sbcl-librarian fixes are reachable.** `build.yml` +clones `$SBCL_LIBRARIAN_REPO` at `$SBCL_LIBRARIAN_REF`, currently +`quil-lang/sbcl-librarian` at `main`, which does not yet carry them. Point those at +a fork or a merged upstream ref. + +**CI's SBCL bump is unverified.** Both jobs now build `sbcl-$SBCL_VERSION` (2.6.7) +with `--with-sb-linkable-runtime`, replacing the 2.2.4 build that failed in +`make-host-1`. That combination is proven locally on arm64 macOS but has not run on +the Linux job. + +**Consumers with a non-/usr/local prefix** need `LIBQUIL_LIB_PATH` as well as +`LIBQUIL_SRC_PATH`, since headers and libraries then live in different directories. + +**Upstreaming.** The three sbcl-librarian fixes are worth PRs regardless of what +libquil does; the FASL-bundle one is a plain bug, and the library-prefix one breaks +upstream's own libcalc example on Unix. diff --git a/src/build-image.lisp b/src/build-image.lisp index 0656060..8cefa74 100644 --- a/src/build-image.lisp +++ b/src/build-image.lisp @@ -9,20 +9,28 @@ (probe-file quicklisp-init)) (load quicklisp-init))) +;; libquil intentionally redefines some of the alien callables that +;; SBCL-LIBRARIAN generates, in order to give them types SBCL-LIBRARIAN cannot +;; express yet (see the definition of quilc_compile_protoquil). SBCL signals a +;; continuable error for that; taking the CONTINUE restart installs the new +;; definition, which is what an interactive build does. Without this the build +;; drops into the debugger and cannot run unattended. +;; +;; The definitions are ordered so that libquil's override is installed last and +;; therefore wins; this applies equally when the systems are recompiled into FASL +;; bundles, so the whole build runs inside the handler. +(defmacro with-alien-redefinition-allowed (&body body) + `(handler-bind ((error + (lambda (condition) + (let ((restart (find-restart 'continue condition))) + (when (and restart + (search "redefine alien callable" + (princ-to-string condition))) + (invoke-restart restart)))))) + ,@body)) + (defun load-system (system) - ;; libquil intentionally redefines some of the alien callables that - ;; SBCL-LIBRARIAN generates, in order to give them types SBCL-LIBRARIAN cannot - ;; express yet (see the definition of quilc_compile_protoquil). SBCL signals a - ;; continuable error for that; taking the CONTINUE restart installs the new - ;; definition, which is what an interactive build does. Without this the build - ;; drops into the debugger and cannot run unattended. - (handler-bind ((error - (lambda (condition) - (let ((restart (find-restart 'continue condition))) - (when (and restart - (search "redefine alien callable" - (princ-to-string condition))) - (invoke-restart restart)))))) + (with-alien-redefinition-allowed (if (find-package '#:quicklisp) (funcall (read-from-string "quicklisp:quickload") system) (asdf:load-system system)))) @@ -32,11 +40,55 @@ (in-package #:libquil) +;;; Two aggregate libraries, because the C side and the Lisp side need different +;;; sets. +;;; +;;; The bindings we generate must cover only libquil's own APIs: the error, handle +;;; and diagnostic APIs already have C wrappers in libsbcl_librarian, and emitting +;;; them again would define those symbols twice. (sbcl-librarian:define-aggregate-library libquil (:function-linkage "QUILC_API") - common + quilc + qvm) + +;;; The runtime's own APIs. Their C wrappers live in libsbcl_librarian, and +;;; generating them here means the runtime can be built straight from this image; +;;; sbcl-librarian's own lib/generate-bindings.lisp would do it, but it pulls in +;;; swank and insists on saving a second core we would only discard. +;;; +;;; The name matters: it decides the generated file names, and entry_point.c +;;; includes sbcl_librarian.h. +(sbcl-librarian:define-aggregate-library sbcl-librarian + (:function-linkage "LIBSBCL_LIBRARIAN_API") + sbcl-librarian:diagnostics + sbcl-librarian:environment + sbcl-librarian:errors + sbcl-librarian:handles + sbcl-librarian:loader) + +;;; The core has to export the Lisp side of everything the process will call, +;;; libquil's APIs and the runtime's alike: libsbcl_librarian's C wrapper for +;;; get_error_message dispatches to an alien callable that exists only if this core +;;; exports it. +(sbcl-librarian:define-aggregate-library libquil-core (:function-linkage "QUILC_API") quilc qvm - sbcl-librarian:handles) + sbcl-librarian:diagnostics + sbcl-librarian:environment + sbcl-librarian:errors + sbcl-librarian:handles + sbcl-librarian:loader) -(sbcl-librarian:build-bindings libquil "." :initialize-lisp-args '("--dynamic-space-size" "8192")) -(sbcl-librarian:build-core-and-die libquil ".") +;;; libquil ships a core rather than the FASL bundles that sbcl-librarian's +;;; CREATE-FASL-LIBRARY-CMAKE-PROJECT produces. FASL bundles are re-loaded into the +;;; image on every startup, which re-runs load-time code: cl-quil reads +;;; stdgates.quil through ASDF:SYSTEM-RELATIVE-PATHNAME at load time, so a FASL +;;; build only works where quilc's source tree is present at the path recorded when +;;; it was built. Saving a core evaluates that once, at build time, and bakes the +;;; result into the image. +;;; +;;; The init function is omitted: initialization is the runtime's job, done from a +;;; constructor when libsbcl_librarian is loaded. +(cl-user::with-alien-redefinition-allowed + (sbcl-librarian:build-bindings libquil "." :omit-init-function t) + (sbcl-librarian:build-bindings sbcl-librarian "runtime/" :omit-init-function t) + (sbcl-librarian:build-core-and-die libquil-core ".")) diff --git a/src/libquil.lisp b/src/libquil.lisp index 538542b..12b9486 100644 --- a/src/libquil.lisp +++ b/src/libquil.lisp @@ -34,25 +34,31 @@ (sbcl-librarian:define-handle-type qvm-multishot-addresses "qvm_multishot_addresses") -(defvar *last-error* "") - -(defun libquil-last-error () - "Returns the most recent error raised by quilc. The error is then cleared." - (let ((last-error *last-error*)) - (setf *last-error* "") - last-error)) - -(sbcl-librarian:define-enum-type error-type "libquil_error_t" - ("LIBQUIL_ERROR_SUCCESS" 0) - ("LIBQUIL_ERROR_FAIL" 1)) - -(sbcl-librarian:define-error-map error-map error-type 0 - ((t (lambda (condition) - (setf *last-error* (format nil "~a" condition)) - (return-from error-map 1))))) - -(sbcl-librarian:define-api common (:error-map error-map - :function-prefix "libquil_") - (:type error-type) - (:function - (("error" libquil-last-error) :string ()))) +;;; Error reporting comes from SBCL-LIBRARIAN's built-in ERRORS api, which +;;; provides the `lisp_err_t' type, `get_error_message', and `enable_backtrace'. +;;; DEFINE-API always uses SBCL-LIBRARIAN's DEFAULT-ERROR-MAP, which records the +;;; condition into that message. +;;; +;;; We redefine that map for libquil's own APIs. The stock one classifies any +;;; plain CL:ERROR as an internal bug (LISP_ERR_BUG), attaching a backtrace and an +;;; "Internal lisp bug:" prefix. Almost everything libquil signals is a user error +;;; -- malformed Quil, an unknown memory region, an unsupported instruction -- so +;;; the stock mapping would report ordinary bad input as a libquil bug. Mapping +;;; CL:ERROR to LISP_ERR_FAILURE keeps the message clean and matches how libquil +;;; behaved before it adopted the built-in error handling. +;;; +;;; WRAP-ERROR-HANDLING is consulted when a callable is compiled, so this affects +;;; only the callables compiled after it -- libquil's own. The APIs already +;;; compiled into libsbcl_librarian keep the stock behaviour. +(sbcl-librarian:define-error-map sbcl-librarian::default-error-map + sbcl-librarian::error-type (:no-error 0 :fatal-error 3) + ;; Handle T, not CL:ERROR. cl-quil signals conditions that are not subtypes of + ;; ERROR -- INVALID-INSTRUCTION-CONDITION, raised for an unrecognized + ;; instruction, has no supertype at all -- so a handler bound to CL:ERROR lets + ;; them escape to the debugger and hang the calling process. libquil's original + ;; error map bound T for this reason. Warnings are passed over first so they do + ;; not abort the call. + ((cl:warning #'cl:continue) + (t (lambda (condition) + (setf sbcl-librarian::*error-message* (format nil "~a" condition)) + (return-from sbcl-librarian::default-error-map 1))))) diff --git a/src/quilc/api.lisp b/src/quilc/api.lisp index 5bbf90f..3952ea7 100644 --- a/src/quilc/api.lisp +++ b/src/quilc/api.lisp @@ -123,8 +123,7 @@ ("LIBQUIL_TYPE_INTEGER" 2) ("LIBQUIL_TYPE_REAL" 3)) -(sbcl-librarian:define-api quilc (:error-map error-map - :function-prefix "quilc_") +(sbcl-librarian:define-api quilc (:function-prefix "quilc_") (:literal "/* Quilc types */") (:type program-memory-type quil-program chip-specification quilc-version-info compilation-metadata) (:literal "/* Quilc functions */") @@ -213,10 +212,14 @@ (let ((program-handle (sbcl-librarian::dereference-handle program)) (chip-spec-handle (sbcl-librarian::dereference-handle chip-spec)) (metadata-ptr metata-ptr)) + ;; Mirrors what SBCL-LIBRARIAN's DEFAULT-ERROR-MAP does for generated + ;; callables: record the condition where GET-ERROR-MESSAGE can find it and + ;; return the failure code. (block error-map (handler-bind ((t (lambda (condition) - (setf *last-error* (format nil "~a" condition)) + (setf sbcl-librarian::*error-message* + (format nil "~a" condition)) (return-from error-map 1)))) (progn (setf (sb-alien:deref sbcl-librarian::result) diff --git a/src/qvm/api.lisp b/src/qvm/api.lisp index 387b6cc..a4c40d0 100644 --- a/src/qvm/api.lisp +++ b/src/qvm/api.lisp @@ -151,7 +151,7 @@ (setf (cffi:mem-aref (sb-alien:alien-sap results-ptr) :double i) probability)))) -(sbcl-librarian:define-api qvm (:error-map error-map :function-prefix "qvm_") +(sbcl-librarian:define-api qvm (:function-prefix "qvm_") (:literal "/* QVM types */") (:type qvm-multishot-addresses qvm-multishot-result qvm-version-info) (:literal "/* QVM functions */") From 95cdf17be39533bc51ebaaece723344d44af989b Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 17:02:13 -0600 Subject: [PATCH 08/11] build: update CI, packaging and examples for the new architecture - build.yml builds SBCL $SBCL_VERSION (2.6.7) with --with-sb-linkable-runtime, replacing the sbcl-2.2.4 build that failed in make-host-1 ('undefined variable: SB-VM::END') because a current host SBCL cannot cross-compile a 2022 target. sbcl-librarian's repo and ref are workflow inputs, since libquil needs three fixes that are not upstream yet. - The artifact is now the bindings library, its header and the whole runtime directory: libsbcl_librarian, libquil.core, libsbcl.so and the runtime headers. install.sh and the release archives follow, with libquil.core installed beside libsbcl_librarian because the runtime finds its core relative to itself. - The examples drop their init(core) calls and -pagezero_size, link the runtime, and use get_error_message/lisp_err_t. - The redundant quickload steps before make are gone; build-image.lisp does it. Verified by simulating package -> zip -> install and running the libquil-sys suite against the installed layout. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 55 ++++++++++++++------ .github/workflows/release.yml | 4 +- README.md | 36 ++++++++----- REARCHITECTURE.md | 8 +-- examples/quilc/Makefile | 11 ++-- examples/quilc/compile-protoquil.c | 25 +++++---- examples/quilc/conjugate-pauli-by-clifford.c | 7 ++- examples/quilc/error.h | 3 +- examples/quilc/generate-rb-sequence.c | 11 ++-- examples/quilc/version.c | 9 ++-- examples/qvm/Makefile | 11 ++-- examples/qvm/error.h | 3 +- examples/qvm/expectation.c | 11 ++-- examples/qvm/multishot-measure.c | 7 ++- examples/qvm/multishot.c | 33 ++++++------ examples/qvm/probabilities.c | 7 ++- examples/qvm/version.c | 9 ++-- examples/qvm/wavefunction.c | 7 ++- install.sh | 21 ++++---- 19 files changed, 150 insertions(+), 128 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00753bc..ecf1ad5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,15 @@ on: pull_request: workflow_call: +env: + # SBCL is built from source for its linkable runtime; see the build steps. + SBCL_VERSION: "2.6.7" + # libquil depends on three sbcl-librarian fixes (secondary-system FASL bundles, + # library prefix, configurable core name). They live on a branch of Rigetti's + # fork; move this back to quil-lang/sbcl-librarian once they are upstream. + SBCL_LIBRARIAN_REPO: "https://github.com/rigetti/sbcl-librarian.git" + SBCL_LIBRARIAN_REF: "fix-secondary-system-bundles" + jobs: build-linux: name: Build libquil.so @@ -33,10 +42,14 @@ jobs: name: Install SBCL, libraries, and quicklisp run: | sudo apt update && sudo apt install -y sbcl build-essential - sudo git clone --single-branch --branch sbcl-2.2.4 https://git.code.sf.net/p/sbcl/sbcl /usr/src/sbcl - cd /usr/src/sbcl && sudo sh make.sh && sudo sh make-shared-library.sh + # Build SBCL from source: a linkable runtime (libsbcl.so) is required and + # neither make.sh nor any package manager produces one. The version must be + # recent -- sbcl-librarian needs sb-sys:int-sap -- and cannot be much older + # than the host SBCL used to cross-compile it. + sudo git clone --single-branch --branch sbcl-$SBCL_VERSION https://git.code.sf.net/p/sbcl/sbcl /usr/src/sbcl + cd /usr/src/sbcl && sudo sh make.sh --with-sb-linkable-runtime && sudo sh make-shared-library.sh sudo apt remove -y sbcl - sudo sh install.sh + sudo sh install.sh sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so sudo apt install -y wget @@ -53,7 +66,7 @@ jobs: git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation - git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 + git clone $SBCL_LIBRARIAN_REPO $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout $SBCL_LIBRARIAN_REF - name: Install quilc dependencies # libffi-dev pulls in whichever libffi runtime the image ships; naming @@ -65,18 +78,20 @@ jobs: run: | cd $GITHUB_WORKSPACE/libquil ls - sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' - sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' make - name: Test run: | - cd $GITHUB_WORKSPACE/libquil/examples/qvm && LD_LIBRARY_PATH=../.. make test - cd $GITHUB_WORKSPACE/libquil/examples/quilc && LD_LIBRARY_PATH=../.. make test + cd $GITHUB_WORKSPACE/libquil/examples/qvm && LD_LIBRARY_PATH=../..:../../runtime make test + cd $GITHUB_WORKSPACE/libquil/examples/quilc && LD_LIBRARY_PATH=../..:../../runtime make test - name: Package files run: | - mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.so $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil + mkdir -p $GITHUB_WORKSPACE/artifacts/libquil + cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.so $GITHUB_WORKSPACE/artifacts/libquil + # The runtime directory holds libsbcl_librarian, the core it loads, + # libsbcl itself and the runtime headers; they must ship together. + cp $GITHUB_WORKSPACE/libquil/runtime/* $GITHUB_WORKSPACE/artifacts/libquil - name: Store artifact uses: actions/upload-artifact@v4 @@ -115,8 +130,12 @@ jobs: name: Install SBCL, libraries, and quicklisp run: | brew install sbcl git - git clone --branch sbcl-2.2.4 https://git.code.sf.net/p/sbcl/sbcl /tmp/sbcl - cd /tmp/sbcl && sudo sh make.sh --without-compact-instance-header --without-immobile-space --without-immobile-code && sudo sh make-shared-library.sh + # See the Linux job: a linkable runtime has to be built from source. Homebrew's + # sbcl is only the cross-compilation host, and its version must be close to the + # one being built -- building the old sbcl-2.2.4 with a current host fails in + # make-host-1 with "undefined variable: SB-VM::END". + git clone --branch sbcl-$SBCL_VERSION https://git.code.sf.net/p/sbcl/sbcl /tmp/sbcl + cd /tmp/sbcl && sudo sh make.sh --with-sb-linkable-runtime && sudo sh make-shared-library.sh brew remove -f sbcl sudo sh install.sh sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so @@ -135,7 +154,7 @@ jobs: git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation - git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 + git clone $SBCL_LIBRARIAN_REPO $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout $SBCL_LIBRARIAN_REF - name: Install quilc dependencies run: brew install lapack openblas libffi gfortran @@ -144,8 +163,6 @@ jobs: run: | cd $GITHUB_WORKSPACE/libquil ls - sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' - sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' make - name: ls @@ -154,12 +171,16 @@ jobs: - name: Test run: | - cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test - cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test + cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil:$GITHUB_WORKSPACE/libquil/runtime make test + cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil:$GITHUB_WORKSPACE/libquil/runtime make test - name: Package files run: | - mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil + mkdir -p $GITHUB_WORKSPACE/artifacts/libquil + cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/artifacts/libquil + # The runtime directory holds libsbcl_librarian, the core it loads, + # libsbcl itself and the runtime headers; they must ship together. + cp $GITHUB_WORKSPACE/libquil/runtime/* $GITHUB_WORKSPACE/artifacts/libquil - name: Store artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 033f220..8cddb3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: - name: Archive linux artifacts run: | - cd artifacts/linux-amd64 && zip ../linux-amd64.zip libquil/libsbcl.so libquil/libquil.so libquil/libquil.core libquil/libquil.h + cd artifacts/linux-amd64 && zip ../linux-amd64.zip libquil/libsbcl.so libquil/libsbcl_librarian.so libquil/libquil.so libquil/libquil.core libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h - name: Download macos arm64 artifacts uses: actions/download-artifact@v4 @@ -50,7 +50,7 @@ jobs: - name: Archive macos arm64 artifacts run: | - cd artifacts/macos-arm64 && zip ../macos-arm64.zip libquil/libsbcl.so libquil/libquil.dylib libquil/libquil.core libquil/libquil.h + cd artifacts/macos-arm64 && zip ../macos-arm64.zip libquil/libsbcl.so libquil/libsbcl_librarian.dylib libquil/libquil.dylib libquil/libquil.core libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h - name: List artifacts run: | diff --git a/README.md b/README.md index c2b83e5..b82286b 100644 --- a/README.md +++ b/README.md @@ -63,27 +63,21 @@ If you would like to manually install the library (for example in the case where ## Building from source -Building requires an SBCL that has a *linkable runtime* — a `libsbcl.a` or -`libsbcl.so`/`libsbcl.dylib` that gets embedded into the library. Neither -`make.sh` nor most package managers produce one (Homebrew's `sbcl` bottle, for -instance, does not), so it has to be built from an SBCL source tree of the same -version as the `sbcl` used for the rest of the build: +Building requires an SBCL with a *linkable runtime* (`libsbcl.so`). Neither +`make.sh` nor any package manager produces one — Homebrew's `sbcl` bottle, for +instance, does not — so SBCL has to be built from source: ```bash sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh && sh install.sh ``` -`install.sh` places the runtime in SBCL's home directory, where the `Makefile` -finds it automatically. To use one from elsewhere, pass it explicitly: +`install.sh` places it in SBCL's home directory, where the `Makefile` finds it +automatically. To use one from elsewhere, pass it explicitly: ```bash -make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.a +make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.so ``` -Note that on aarch64 macOS, SBCL only builds its runtime as the static -`libsbcl.a`; there is no shared `libsbcl.dylib` on that platform. The `Makefile` -handles either form. - The Lisp dependencies (`quilc`, `qvm`, `magicl`, `sbcl-librarian`) are expected in your Quicklisp local-projects directory. Then: @@ -91,6 +85,24 @@ in your Quicklisp local-projects directory. Then: make ``` +This produces the library and the runtime it needs: + +``` +libquil.dylib the C bindings +libquil.h its header +runtime/libsbcl_librarian.dylib the SBCL runtime; brings up Lisp when loaded +runtime/libquil.core the Lisp image +runtime/libsbcl.so the linkable SBCL runtime +runtime/sbcl_librarian*.h runtime headers +``` + +Everything under `runtime/` is installed alongside the library, and +`libquil.core` must sit next to `libsbcl_librarian` — the runtime finds its core +relative to its own location. There is no initialization call to make: loading +the library starts Lisp. + +See [REARCHITECTURE.md](REARCHITECTURE.md) for how this fits together and why. + ### Linear algebra backend on aarch64 macOS Homebrew's reference `lapack` computes incorrect eigenvectors on aarch64, which diff --git a/REARCHITECTURE.md b/REARCHITECTURE.md index 23494b0..0af1f3e 100644 --- a/REARCHITECTURE.md +++ b/REARCHITECTURE.md @@ -189,10 +189,10 @@ Three fixes, on the `fix-secondary-system-bundles` branch: ## Open -**CI cannot go green until the sbcl-librarian fixes are reachable.** `build.yml` -clones `$SBCL_LIBRARIAN_REPO` at `$SBCL_LIBRARIAN_REF`, currently -`quil-lang/sbcl-librarian` at `main`, which does not yet carry them. Point those at -a fork or a merged upstream ref. +**CI depends on a fork branch.** `build.yml` clones `$SBCL_LIBRARIAN_REPO` at +`$SBCL_LIBRARIAN_REF`, currently `rigetti/sbcl-librarian` at +`fix-secondary-system-bundles`. Move it back to `quil-lang/sbcl-librarian` once the +three fixes are upstream. **CI's SBCL bump is unverified.** Both jobs now build `sbcl-$SBCL_VERSION` (2.6.7) with `--with-sb-linkable-runtime`, replacing the 2.2.4 build that failed in diff --git a/examples/quilc/Makefile b/examples/quilc/Makefile index 8e49983..cb816cd 100644 --- a/examples/quilc/Makefile +++ b/examples/quilc/Makefile @@ -1,12 +1,11 @@ TEST_SRCS = $(wildcard *.c) TEST_OBJS = $(TEST_SRCS:.c=) BUILD_DIR = ../.. -CCFLAGS = -lsbcl -lquil -L$(BUILD_DIR) -I$(BUILD_DIR) - -OS:=$(shell uname -s) -ifeq ($(OS), Darwin) - CCFLAGS += -pagezero_size 0x100000 -endif +RUNTIME_DIR = $(BUILD_DIR)/runtime +# libquil is the generated bindings; libsbcl_librarian is the runtime that brings up +# the Lisp image (and supplies get_error_message). No -pagezero_size: the image is +# mapped by the runtime library, not by this executable. +CCFLAGS = -lquil -lsbcl_librarian -L$(BUILD_DIR) -L$(RUNTIME_DIR) -I$(BUILD_DIR) -I$(RUNTIME_DIR) .PHONY: all clean diff --git a/examples/quilc/compile-protoquil.c b/examples/quilc/compile-protoquil.c index e8737c0..021bb6a 100644 --- a/examples/quilc/compile-protoquil.c +++ b/examples/quilc/compile-protoquil.c @@ -2,20 +2,19 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program h, result; chip_specification chip; - if (quilc_parse_quil("CNOT 0 1", &h) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("CNOT 0 1", &h) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_build_nq_linear_chip(2, &chip) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_build_nq_linear_chip(2, &chip) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to build chip"); exit(1); } @@ -23,13 +22,13 @@ int main() { quilc_compilation_metadata metadata; if (quilc_compile_protoquil(h, chip, &metadata, &result) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to compile program"); exit(1); } int *final_rewiring, final_rewiring_len; - if (quilc_compilation_metadata_get_final_rewiring(metadata, &final_rewiring, &final_rewiring_len) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_final_rewiring(metadata, &final_rewiring, &final_rewiring_len) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to final rewiring from metadata"); exit(1); } @@ -41,7 +40,7 @@ int main() { int gate_depth, multiqubit_gate_depth, gate_volume, topological_swaps, present; double program_duration, program_fidelity, qpu_runtime_estimation; - if (quilc_compilation_metadata_get_gate_depth(metadata, &gate_depth, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_gate_depth(metadata, &gate_depth, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get gate depth"); exit(1); } @@ -49,7 +48,7 @@ int main() { printf("gate depth is %d\n", gate_depth); } - if (quilc_compilation_metadata_get_multiqubit_gate_depth(metadata, &multiqubit_gate_depth, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_multiqubit_gate_depth(metadata, &multiqubit_gate_depth, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get multiqubit gate depth"); exit(1); } @@ -57,7 +56,7 @@ int main() { printf("multiqubit gate depth is %d\n", gate_depth); } - if (quilc_compilation_metadata_get_gate_volume(metadata, &gate_volume, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_gate_volume(metadata, &gate_volume, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get gate volume"); exit(1); } @@ -65,7 +64,7 @@ int main() { printf("gate volume is %d\n", gate_volume); } - if (quilc_compilation_metadata_get_topological_swaps(metadata, &topological_swaps, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_topological_swaps(metadata, &topological_swaps, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get topological swaps"); exit(1); } @@ -73,7 +72,7 @@ int main() { printf("topological swaps is %d\n", topological_swaps); } - if (quilc_compilation_metadata_get_program_duration(metadata, &program_duration, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_program_duration(metadata, &program_duration, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get program duration"); exit(1); } @@ -81,7 +80,7 @@ int main() { printf("program duration is %f\n", program_duration); } - if (quilc_compilation_metadata_get_program_fidelity(metadata, &program_fidelity, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_program_fidelity(metadata, &program_fidelity, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get program fidelity "); exit(1); } @@ -89,7 +88,7 @@ int main() { printf("program fidelity is %f\n", program_fidelity); } - if (quilc_compilation_metadata_get_qpu_runtime_estimation(metadata, &qpu_runtime_estimation, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_qpu_runtime_estimation(metadata, &qpu_runtime_estimation, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get qpu runtime estimation "); exit(1); } diff --git a/examples/quilc/conjugate-pauli-by-clifford.c b/examples/quilc/conjugate-pauli-by-clifford.c index a037188..cd5bcc9 100644 --- a/examples/quilc/conjugate-pauli-by-clifford.c +++ b/examples/quilc/conjugate-pauli-by-clifford.c @@ -2,13 +2,12 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program clifford; - if (quilc_parse_quil("H 0", &clifford) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0", &clifford) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -20,7 +19,7 @@ int main() { if (quilc_conjugate_pauli_by_clifford( pauli_indices, 1, pauli_terms, 1, clifford, &result_phase, - &result_pauli) != LIBQUIL_ERROR_SUCCESS) { + &result_pauli) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_conjugate_by_pauli"); exit(1); } diff --git a/examples/quilc/error.h b/examples/quilc/error.h index 87e0bf5..f20816f 100644 --- a/examples/quilc/error.h +++ b/examples/quilc/error.h @@ -1,5 +1,4 @@ #define LIBQUIL_ERROR(msg) \ char* libquil_err; \ - libquil_error(&libquil_err); \ + get_error_message(&libquil_err); \ printf("%s: %s\n", msg, libquil_err); - diff --git a/examples/quilc/generate-rb-sequence.c b/examples/quilc/generate-rb-sequence.c index e295e67..0ca9555 100644 --- a/examples/quilc/generate-rb-sequence.c +++ b/examples/quilc/generate-rb-sequence.c @@ -2,23 +2,22 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program phase, h, y; int depth = 3, seed = 42, qubits = 1; - if (quilc_parse_quil("PHASE(pi/2) 0", &phase) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("PHASE(pi/2) 0", &phase) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("H 0", &h) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0", &h) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("Y 0", &y) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("Y 0", &y) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -31,7 +30,7 @@ int main() { if (quilc_generate_rb_sequence(depth, qubits, gateset, 3, &seed, &interleaver, &results, - result_lens) != LIBQUIL_ERROR_SUCCESS) { + result_lens) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to generate RB sequence"); exit(1); } diff --git a/examples/quilc/version.c b/examples/quilc/version.c index 5fb4c90..bd5671f 100644 --- a/examples/quilc/version.c +++ b/examples/quilc/version.c @@ -2,6 +2,7 @@ #include #include +#include "sbcl_librarian.h" #include "libquil.h" #include "error.h" @@ -11,22 +12,20 @@ void die(char *msg) { } int main() { - init("../../libquil.core"); - quilc_version_info version_info; - if (quilc_get_version_info(&version_info) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_get_version_info(&version_info) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_get_version_info"); exit(1); } char* version; char* githash; - if (quilc_version_info_version(version_info, &version) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_version_info_version(version_info, &version) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_version_info_version"); exit(1); } - if (quilc_version_info_githash(version_info, &githash) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_version_info_githash(version_info, &githash) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_version_info_githash"); exit(1); } diff --git a/examples/qvm/Makefile b/examples/qvm/Makefile index 8a051b8..c0b3e7d 100644 --- a/examples/qvm/Makefile +++ b/examples/qvm/Makefile @@ -1,12 +1,11 @@ TEST_SRCS = $(wildcard *.c) TEST_OBJS = $(TEST_SRCS:.c=) BUILD_DIR = ../.. -CCFLAGS = -lsbcl -lquil -L$(BUILD_DIR) -I$(BUILD_DIR) - -OS:=$(shell uname -s) -ifeq ($(OS), Darwin) - CCFLAGS += -pagezero_size 0x100000 -endif +RUNTIME_DIR = $(BUILD_DIR)/runtime +# libquil is the generated bindings; libsbcl_librarian is the runtime that brings up +# the Lisp image (and supplies get_error_message). No -pagezero_size: the image is +# mapped by the runtime library, not by this executable. +CCFLAGS = -lquil -lsbcl_librarian -L$(BUILD_DIR) -L$(RUNTIME_DIR) -I$(BUILD_DIR) -I$(RUNTIME_DIR) .PHONY: all clean diff --git a/examples/qvm/error.h b/examples/qvm/error.h index 87e0bf5..f20816f 100644 --- a/examples/qvm/error.h +++ b/examples/qvm/error.h @@ -1,5 +1,4 @@ #define LIBQUIL_ERROR(msg) \ char* libquil_err; \ - libquil_error(&libquil_err); \ + get_error_message(&libquil_err); \ printf("%s: %s\n", msg, libquil_err); - diff --git a/examples/qvm/expectation.c b/examples/qvm/expectation.c index 249976e..3aedd20 100644 --- a/examples/qvm/expectation.c +++ b/examples/qvm/expectation.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -15,7 +16,7 @@ double do_expectation(quil_program state_prep, quil_program operator) { double expectations[1] = {0}; if (qvm_expectation(state_prep, operators, 1, NULL, &expectations) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_expectation"); exit(1); } @@ -24,21 +25,19 @@ double do_expectation(quil_program state_prep, quil_program operator) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program i; quil_program z; quil_program x; - if (quilc_parse_quil("I 0", &i) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("I 0", &i) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("Z 0", &z) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("Z 0", &z) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("X 0", &x) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("X 0", &x) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } diff --git a/examples/qvm/multishot-measure.c b/examples/qvm/multishot-measure.c index 36359c8..72f9434 100644 --- a/examples/qvm/multishot-measure.c +++ b/examples/qvm/multishot-measure.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -11,13 +12,11 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; char *source = "X 0; X 2"; - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_parse_quil"); exit(1); } @@ -29,7 +28,7 @@ int main(int argc, char **argv) { int results[num_trials][n_qubits]; memset(results, 0, num_trials * n_qubits * sizeof(int)); if (qvm_multishot_measure(program, qubits, n_qubits, num_trials, NULL, - &results) != LIBQUIL_ERROR_SUCCESS) { + &results) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_measure"); exit(1); } diff --git a/examples/qvm/multishot.c b/examples/qvm/multishot.c index cd88eac..fd3c804 100644 --- a/examples/qvm/multishot.c +++ b/examples/qvm/multishot.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -26,20 +27,20 @@ void multishot_with_explicit_ro_indices() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } int indices[3] = {0, 1, 2}; if (qvm_multishot_addresses_set(addresses, "ro", indices, 3) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -47,7 +48,7 @@ void multishot_with_explicit_ro_indices() { qvm_multishot_result qvm_res; int num_trials = 10; if (qvm_multishot(program, addresses, num_trials, NULL, NULL, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -55,7 +56,7 @@ void multishot_with_explicit_ro_indices() { for (int i = 0; i < num_trials; i++) { char vals[3]; if (qvm_multishot_result_get(qvm_res, "ro", i, &vals) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get"); exit(1); } @@ -76,19 +77,19 @@ void multishot_with_implicit_ro_indices() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } if (qvm_multishot_addresses_set_all(addresses, "ro") != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -97,7 +98,7 @@ void multishot_with_implicit_ro_indices() { int num_trials = 10; double gate_noise[] = {0.0, 0.0, 0.0}; if (qvm_multishot(program, addresses, num_trials, NULL, NULL, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -107,7 +108,7 @@ void multishot_with_implicit_ro_indices() { char *vals; if (qvm_multishot_result_get_all(qvm_res, "ro", i, &vals, &len) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get_all"); exit(1); } @@ -131,20 +132,20 @@ void multishot_with_noise() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } int indices[3] = {0, 1, 2}; if (qvm_multishot_addresses_set(addresses, "ro", indices, 3) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -155,7 +156,7 @@ void multishot_with_noise() { double measurement_noise[] = {0.1, 0.0, 0.0}; if (qvm_multishot(program, addresses, num_trials, gate_noise, measurement_noise, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -163,7 +164,7 @@ void multishot_with_noise() { for (int i = 0; i < num_trials; i++) { char vals[3]; if (qvm_multishot_result_get(qvm_res, "ro", i, &vals) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get"); exit(1); } @@ -176,8 +177,6 @@ void multishot_with_noise() { } int main(int argc, char **argv) { - init("../../libquil.core"); - multishot_with_explicit_ro_indices(); multishot_with_implicit_ro_indices(); multishot_with_noise(); diff --git a/examples/qvm/probabilities.c b/examples/qvm/probabilities.c index 39f85da..af37995 100644 --- a/examples/qvm/probabilities.c +++ b/examples/qvm/probabilities.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -11,11 +12,9 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; - if (quilc_parse_quil("H 0; CNOT 0 1", &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0; CNOT 0 1", &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -25,7 +24,7 @@ int main(int argc, char **argv) { double wavefunction[n_probabilities]; memset(wavefunction, 0, n_probabilities * sizeof(double)); - if (qvm_probabilities(program, NULL, wavefunction) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_probabilities(program, NULL, wavefunction) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_probabilities"); exit(1); } diff --git a/examples/qvm/version.c b/examples/qvm/version.c index eeba2de..7fbe5af 100644 --- a/examples/qvm/version.c +++ b/examples/qvm/version.c @@ -2,26 +2,25 @@ #include #include +#include "sbcl_librarian.h" #include "libquil.h" #include "error.h" int main(int argc, char **argv) { - init("../../libquil.core"); - qvm_version_info version_info; - if (qvm_get_version_info(&version_info) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_get_version_info(&version_info) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_get_version_info"); exit(1); } char* version; char* githash; - if (qvm_version_info_version(version_info, &version) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_version_info_version(version_info, &version) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_version_info_version"); exit(1); } - if (qvm_version_info_githash(version_info, &githash) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_version_info_githash(version_info, &githash) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_version_info_githash"); exit(1); } diff --git a/examples/qvm/wavefunction.c b/examples/qvm/wavefunction.c index f59a37c..e618a7d 100644 --- a/examples/qvm/wavefunction.c +++ b/examples/qvm/wavefunction.c @@ -4,6 +4,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -12,11 +13,9 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; - if (quilc_parse_quil("X 0; I 1", &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("X 0; I 1", &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -25,7 +24,7 @@ int main(int argc, char **argv) { double *wavefunction; int seed = 0; if (qvm_wavefunction(program, &seed, &wavefunction, &wavefunction_len) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_wavefunction"); exit(1); } diff --git a/install.sh b/install.sh index d4e4061..1f0e295 100755 --- a/install.sh +++ b/install.sh @@ -53,20 +53,23 @@ pushd "${LIBQUIL_TEMP_DIR}" || exit curl -L "${LIBQUIL_RELEASE_URL}" -o "${LIBQUIL_RELEASE_FILE}" unzip "${LIBQUIL_RELEASE_FILE}" +# libquil.core must land in the same directory as libsbcl_librarian: the runtime +# locates its core relative to its own path. +sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" +sudo cp libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h "${LIBQUIL_INCLUDE_PREFIX}" +sudo cp libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" + if [[ -n "${IS_LINUX-}" ]] then - sudo cp libquil/libquil.so libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" - sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" - sudo cp libquil/libquil.h "${LIBQUIL_INCLUDE_PREFIX}" + sudo cp libquil/libquil.so libquil/libsbcl_librarian.so "${LIBQUIL_LIB_PREFIX}" sudo ldconfig else - sudo cp libquil/libquil.dylib libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" - sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" - sudo cp libquil/libquil.h "${LIBQUIL_INCLUDE_PREFIX}" + sudo cp libquil/libquil.dylib libquil/libsbcl_librarian.dylib "${LIBQUIL_LIB_PREFIX}" # This disables the "cannot open libquil.dylib from untrusted developer" dialog. # A better solution for this would be to properly codesign the files, but that # is a non-trivial amount of work. - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libquil.dylib - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libquil.core - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libsbcl.so + sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.dylib" + sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl_librarian.dylib" + sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.core" + sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl.so" fi From a44a84e86ba26fbf4bf832bbc285df7041747285 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 20:59:04 -0600 Subject: [PATCH 09/11] fix: load OpenBLAS into the image before magicl picks a backend magicl looks for Homebrew's reference LAPACK first and otherwise falls back to a bare liblapack.dylib, which on macOS resolves to Accelerate's. Neither works: the reference build returns incorrect eigenvectors on arm64, failing with 'Could not find diagonalizer for matrix ... after 16 attempts', and Accelerate's LAPACK predates 3.3 so routines quilc calls are missing, failing with 'The alien function zuncsd_ is undefined'. OpenBLAS is correct and complete. Loading it first means its symbols are the ones that resolve, and because SBCL records loaded shared objects in the core and reloads them at startup, the choice is baked into the artifact instead of depending on what the loader happens to find. Verified against a stock magicl checkout with Homebrew's lapack installed. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 22 ++++++++++++++++++---- Makefile | 4 ++-- README.md | 7 ++++++- src/build-image.lisp | 22 ++++++++++++++++++++++ versions | 11 +++++++---- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecf1ad5..798165c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,10 @@ env: # fork; move this back to quil-lang/sbcl-librarian once they are upstream. SBCL_LIBRARIAN_REPO: "https://github.com/rigetti/sbcl-librarian.git" SBCL_LIBRARIAN_REF: "fix-secondary-system-bundles" + # The Quicklisp dist supplying the Lisp dependencies. It has to be recent enough + # for current cl-quil: the long-standing 2022-04-01 pin predates clos-encounters + # and fails with SYSTEM-NOT-FOUND. + QUICKLISP_VERSION: "2026-01-01" jobs: build-linux: @@ -54,7 +58,7 @@ jobs: sudo apt install -y wget wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc rm -f /tmp/quicklisp.lisp @@ -121,7 +125,7 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/macos-versions' )}} + key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/versions')}} - uses: Homebrew/actions/setup-homebrew@master name: Setup homebrew @@ -142,7 +146,7 @@ jobs: brew install wget wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc rm -f /tmp/quicklisp.lisp @@ -157,7 +161,17 @@ jobs: git clone $SBCL_LIBRARIAN_REPO $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout $SBCL_LIBRARIAN_REF - name: Install quilc dependencies - run: brew install lapack openblas libffi gfortran + run: | + # Deliberately not installing lapack. magicl prefers Homebrew's reference + # LAPACK over everything else, and on arm64 it computes incorrect + # eigenvectors -- compilation then fails with "Could not find diagonalizer + # for matrix ... after 16 attempts". With it absent, magicl falls through to + # a bare liblapack.dylib, which these symlinks point at OpenBLAS. + brew uninstall --ignore-dependencies lapack || true + brew install openblas libffi gfortran + sudo mkdir -p /usr/local/lib + sudo ln -sf "$(brew --prefix openblas)/lib/libopenblas.dylib" /usr/local/lib/liblapack.dylib + sudo ln -sf "$(brew --prefix openblas)/lib/libopenblas.dylib" /usr/local/lib/libblas.dylib - name: Build libquil.dylib run: | diff --git a/Makefile b/Makefile index b2c8c37..a66d69c 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ endif mkdir -p $(RUNTIME_DIR) cp $(LIBSBCL) $(RUNTIME_DIR)/libsbcl.so cp "$(SBCL_LIBRARIAN_DIR)lib/sbcl_librarian_err.h" $(RUNTIME_DIR)/ - cd $(RUNTIME_DIR) && $(CC) $(SHARED_FLAGS) -o libsbcl_librarian$(SHARED_SUFFIX) \ + cd $(RUNTIME_DIR) && $(CC) $(SHARED_FLAGS) -fPIC -o libsbcl_librarian$(SHARED_SUFFIX) \ sbcl_librarian.c \ "$(SBCL_LIBRARIAN_DIR)lib/entry_point.c" \ -DLIBSBCL_LIBRARIAN_API_BUILD \ @@ -85,7 +85,7 @@ endif -I. -I"$(SBCL_LIBRARIAN_DIR)lib" -L. -lsbcl $(ZSTD_LIBS) $(LIBQUIL_TARGET): libquil.c $(CORE) $(RUNTIME_LIB) - $(CC) $(SHARED_FLAGS) -o $@ libquil.c \ + $(CC) $(SHARED_FLAGS) -fPIC -o $@ libquil.c \ -I. -I$(RUNTIME_DIR) -I"$(SBCL_LIBRARIAN_DIR)lib" \ -L$(RUNTIME_DIR) -lsbcl_librarian diff --git a/README.md b/README.md index b82286b..9cd7613 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,14 @@ sudo apt install libblas-dev libffi-dev liblapack-dev libz-dev On systems which use `brew` to install packages (e.g macOS), these libraries can be installed with the command ``` -brew install lapack openblas libffi +brew install openblas libffi ``` +> Note: do not install Homebrew's `lapack` on Apple Silicon. `magicl` prefers it +> over every other backend, and it computes incorrect eigenvectors there, which +> surfaces as `Could not find diagonalizer for matrix ... after 16 attempts` +> during compilation. + ## Automated installation A script is provided to automate installation of the library. It will detect the host operating system and install the library to an appropriate location. A version identifier can be provided to install a particular version of the library. If no version is provided, the latest version of the library will be installed. diff --git a/src/build-image.lisp b/src/build-image.lisp index 8cefa74..9a4039e 100644 --- a/src/build-image.lisp +++ b/src/build-image.lisp @@ -35,6 +35,28 @@ (funcall (read-from-string "quicklisp:quickload") system) (asdf:load-system system)))) +;; Load OpenBLAS before anything else pulls in a BLAS/LAPACK, so that its symbols +;; are the ones that resolve. +;; +;; magicl looks for Homebrew's reference LAPACK first and falls back to a bare +;; liblapack.dylib, which on macOS is Accelerate's. Neither is usable here: the +;; reference build returns incorrect eigenvectors on arm64 ("Could not find +;; diagonalizer for matrix ... after 16 attempts"), and Accelerate's LAPACK predates +;; 3.3, so routines quilc needs are simply missing ("The alien function zuncsd_ is +;; undefined"). OpenBLAS is correct and complete on both counts. +;; +;; SBCL records loaded shared objects in the core and reloads them at startup, so +;; this choice is baked into the artifact rather than left to the loader. +#+darwin +(let ((openblas (find-if #'probe-file + '("/opt/homebrew/opt/openblas/lib/libopenblas.dylib" + "/usr/local/opt/openblas/lib/libopenblas.dylib")))) + (if openblas + (sb-alien:load-shared-object openblas) + (warn "OpenBLAS not found; magicl may load a BLAS/LAPACK that miscomputes ~ + eigenvectors or lacks routines quilc needs. Install it with ~ + `brew install openblas'."))) + (load-system '#:sbcl-librarian) (load-system '#:libquil) diff --git a/versions b/versions index 17a62fd..cddad57 100644 --- a/versions +++ b/versions @@ -1,5 +1,8 @@ +# Versions this build pins. Keep in sync with the env block in +# .github/workflows/build.yml, which hashes this file for its cache keys. QVM_VERSION=4617625cb6053b1adfd3f7aea9cd2be328b225f6 -QUILC_VERSION=bffea7fdb972cc9b3a50b790246b6a3143c88c7a -SBCL_LIBRARIAN_VERSION=04f7e390c777084ac43b8df9e90593a041da2381 -SBCL_VERSION=sbcl-2.2.4 -QUICKLISP_VERSION=2022-04-01 +QUILC_VERSION=3d83b665b3addc33be2f696b1cdebc0c4c5a04f9 +SBCL_LIBRARIAN_VERSION=fix-secondary-system-bundles +SBCL_VERSION=sbcl-2.6.7 +# 2022-04-01 predates clos-encounters, which current cl-quil depends on. +QUICKLISP_VERSION=2026-01-01 From 62959f253c304e3214e688cca0e0d79cd4ef49a8 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 11 Aug 2026 21:19:56 -0600 Subject: [PATCH 10/11] ci: fix the release/prerelease pipeline Cutting a prerelease from a branch produced a tag pointing at the wrong code. knope 0.10/0.11 create the GitHub release against the default branch, so the version bump landed on the branch while the tag landed on main -- and the publish job, which checks out whatever that tag resolves to, then tried to republish main's already-released version: error: crate libquil-sys@0.4.2 already exists on crates.io index Adopt the setup rigetti-pyo3 arrived at (36a1850, 9b5235c, 3505971): - knope 0.23, which tags the ref being released and takes --prerelease-label, so the separate prerelease workflow in knope.toml is no longer needed; - the ref decides what gets cut: main releases, anything else prereleases; - check out ${{ github.ref }} so a release acts on the dispatched branch; - pass the token to the Release step through GITHUB_TOKEN, which is how it authenticates; - dry-run the release on pull requests, so a broken config is visible before it is dispatched. The publish job now checks out the release tag explicitly, so it always publishes exactly what was tagged rather than whatever the target commitish points at. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 50 +++++++++++++++++++++++----------- .github/workflows/release.yml | 33 ++++++++++++++++------- REARCHITECTURE.md | 51 ++++++++++++++++++++++++++++------- install.sh | 10 ++++--- versions | 2 ++ 5 files changed, 109 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 798165c..bb3ed08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,11 @@ on: env: # SBCL is built from source for its linkable runtime; see the build steps. SBCL_VERSION: "2.6.7" + # Host used to cross-compile SBCL_VERSION on Linux. It cannot be apt's (22.04 + # ships 2.1.11, too old) nor SBCL_VERSION's own binary release, which is linked + # against glibc 2.38 and will not run on 22.04. 2.5.0's binary needs only glibc + # 2.34 and is recent enough to build 2.6.7. + SBCL_BOOTSTRAP_VERSION: "2.5.0" # libquil depends on three sbcl-librarian fixes (secondary-system FASL bundles, # library prefix, configurable core name). They live on a branch of Rigetti's # fork; move this back to quil-lang/sbcl-librarian once they are upstream. @@ -20,7 +25,10 @@ env: jobs: build-linux: name: Build libquil.so - runs-on: ubuntu-latest + # Pinned rather than ubuntu-latest: the artifact links libsbcl, so it inherits + # the glibc of whatever built it, and a build on 24.04 fails to load on 22.04 + # with "version `GLIBC_2.38' not found". + runs-on: ubuntu-22.04 steps: - name: root suid tar @@ -40,26 +48,35 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-build-${{ hashFiles('**/versions') }} + key: ${{ env.ImageOS }}-${{ runner.arch }}-build-${{ hashFiles('**/versions') }} - if: steps.cache-sbcl.outputs.cache-hit != 'true' name: Install SBCL, libraries, and quicklisp run: | - sudo apt update && sudo apt install -y sbcl build-essential - # Build SBCL from source: a linkable runtime (libsbcl.so) is required and - # neither make.sh nor any package manager produces one. The version must be - # recent -- sbcl-librarian needs sb-sys:int-sap -- and cannot be much older - # than the host SBCL used to cross-compile it. + sudo apt update && sudo apt install -y build-essential bzip2 wget + # SBCL is built from source because a linkable runtime (libsbcl.so) is + # required and neither make.sh nor any package manager produces one. + # + # The host is a binary release of SBCL_BOOTSTRAP_VERSION rather than apt's: + # cross compiling a current SBCL needs a host of roughly the same vintage, + # and 22.04 ships 2.1.11, which fails in make-host-1 with "FAILURE-P was set + # when creating genesis.fasl". 22.04 is used deliberately, since the + # artifact inherits the glibc of whatever builds it. + wget -q "https://downloads.sourceforge.net/project/sbcl/sbcl/$SBCL_BOOTSTRAP_VERSION/sbcl-$SBCL_BOOTSTRAP_VERSION-x86-64-linux-binary.tar.bz2" -O /tmp/sbcl-bin.tar.bz2 + mkdir -p /tmp/sbcl-bin && tar xf /tmp/sbcl-bin.tar.bz2 -C /tmp/sbcl-bin --strip-components=1 + cd /tmp/sbcl-bin && sudo sh install.sh sudo git clone --single-branch --branch sbcl-$SBCL_VERSION https://git.code.sf.net/p/sbcl/sbcl /usr/src/sbcl cd /usr/src/sbcl && sudo sh make.sh --with-sb-linkable-runtime && sudo sh make-shared-library.sh - sudo apt remove -y sbcl sudo sh install.sh + sudo mkdir -p /usr/local/lib sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so sudo apt install -y wget - wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" - sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' + if [ ! -f ~/quicklisp/setup.lisp ]; then + wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" + sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' + fi echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc rm -f /tmp/quicklisp.lisp cat ~/.sbclrc @@ -125,7 +142,7 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-${{ runner.arch }}-build-${{ hashFiles('**/versions')}} + key: ${{ env.ImageOS }}-${{ runner.arch }}-build-${{ hashFiles('**/versions') }} - uses: Homebrew/actions/setup-homebrew@master name: Setup homebrew @@ -142,12 +159,15 @@ jobs: cd /tmp/sbcl && sudo sh make.sh --with-sb-linkable-runtime && sudo sh make-shared-library.sh brew remove -f sbcl sudo sh install.sh + sudo mkdir -p /usr/local/lib sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so brew install wget - wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" - sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' + if [ ! -f ~/quicklisp/setup.lisp ]; then + wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/$QUICKLISP_VERSION/distinfo.txt\")" + sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' + fi echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc rm -f /tmp/quicklisp.lisp cat ~/.sbclrc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8cddb3b..3a03fd2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,9 @@ name: Release libquil on: + # Dispatching on main cuts a release; dispatching on any other branch cuts a + # prerelease, so libquil-sys can be built against a libquil that is not yet stable. workflow_dispatch: - inputs: - type: - description: Bump versions and trigger a new release. - required: true - default: release - options: - - release jobs: build: @@ -28,6 +23,8 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + # Release the ref this was dispatched on, not the default branch. + ref: ${{ github.ref }} # Each artifact is downloaded into its own directory: the two macOS # artifacts contain identically named files, so a shared directory would @@ -59,9 +56,25 @@ jobs: unzip -l artifacts/macos-arm64.zip - name: Install Knope - uses: knope-dev/action@v2.0.0 + uses: knope-dev/action@v2.1.2 with: - version: 0.11.0 + # 0.23 is needed for `--prerelease-label` and because older versions tag + # the default branch rather than the ref being released, which produces a + # tag pointing at the wrong code when releasing from a branch. + version: 0.23.0 # Test before updating, breaking changes likely: https://github.com/knope-dev/action#install-latest-version - name: Release - run: knope release -v + env: + # The Release step authenticates with this rather than an argument. + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + set -euo pipefail + + # A release off main is a real one; anywhere else it can only be a + # prerelease, which is how libquil gets published for testing while an ABI + # change is still under review. + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then + knope release -v + else + knope release -v --prerelease-label=rc + fi diff --git a/REARCHITECTURE.md b/REARCHITECTURE.md index 0af1f3e..f0e8e30 100644 --- a/REARCHITECTURE.md +++ b/REARCHITECTURE.md @@ -30,20 +30,28 @@ Old (what libquil does on `main`): - `libquil.dylib` is self-contained, statically embedding the SBCL runtime. - The consumer calls `init("/path/to/libquil.core")` explicitly. -New (what this branch moves to): +Upstream's current model: -- A **runtime** is built once: `libsbcl_librarian.dylib` plus `sbcl_librarian.core`. - It initializes Lisp from a shared-library constructor at load time, locating its - core by name next to itself, with heap size from `SBCL_LIBRARIAN_HEAP_SIZE`. +- A **runtime** is built: `libsbcl_librarian`, which initializes Lisp from a + shared-library constructor at load time, locating a core by name next to itself, + with heap size from `SBCL_LIBRARIAN_HEAP_SIZE`. - Each consumer is a **FASL library**: a small shared library holding the generated C bindings, incbin-embedded FASL bundles for its ASDF system and dependencies, - and a constructor that loads those bundles into the already-running image. + and a constructor that loads those bundles into the running image. `create-fasl-library-cmake-project` generates the whole CMake project. -- There is no per-consumer `.core`, and no explicit init call. +- There is no per-consumer core, and no explicit init call. + +New (what this branch actually does): + +- The runtime, as above — but built with `SBCL_LIBRARIAN_CORE_NAME=libquil.core` + so it brings up libquil's image (D5). +- `libquil.dylib` is the generated C bindings only, linked against the runtime. +- The Lisp side stays a **core**, not FASL bundles, for the reasons in D3. +- No explicit init call: loading the runtime starts Lisp. ## Decisions -### D1: Follow the upstream FASL-library model rather than pinning a fork +### D1: Follow upstream's current model rather than pinning a fork Rejected alternatives: (a) stay on `04f7e39` and carry the one-line `int-sap` patch forever; (b) fork sbcl-librarian to keep `define-api :error-map` alive. @@ -131,7 +139,26 @@ subtypes of `error` -- `invalid-instruction-condition` has no supertype at all - and with a `cl:error` handler those escape into the debugger and hang the calling process. libquil's original error map bound `T` for the same reason. -### D7: Keep shipping the SBCL runtime as `libsbcl.so`, even on macOS +### D7: Choose the BLAS/LAPACK backend at build time, not load time + +`src/build-image.lisp` loads OpenBLAS before anything else can pull in a +BLAS/LAPACK. magicl otherwise picks one by searching: Homebrew's reference LAPACK +first, then a bare `liblapack.dylib`. On arm64 macOS both are wrong, in different +ways: + +- Homebrew's reference LAPACK returns incorrect eigenvectors, which surfaces as + `Could not find diagonalizer for matrix ... after 16 attempts`. (Confirmed + directly: `eig` violated the trace invariant by ~5.8 for n>=3, while OpenBLAS + agreed to ~1e-15.) +- A bare `liblapack.dylib` resolves to Accelerate's, which predates LAPACK 3.3 and + is missing routines quilc calls: `The alien function "zuncsd_" is undefined`. + +Because SBCL records loaded shared objects in the core and reloads them at startup, +loading OpenBLAS at build time bakes the choice into the artifact rather than +leaving it to whatever the loader finds on the user's machine. Patching magicl was +the alternative; this keeps the fix inside libquil. + +### D8: Keep shipping the SBCL runtime as `libsbcl.so`, even on macOS SBCL's `make-shared-library.sh` emits `libsbcl.so` on every platform, and that string becomes the install name recorded in `libsbcl_librarian.dylib`. Renaming the @@ -204,4 +231,10 @@ the Linux job. **Upstreaming.** The three sbcl-librarian fixes are worth PRs regardless of what libquil does; the FASL-bundle one is a plain bug, and the library-prefix one breaks -upstream's own libcalc example on Unix. +upstream's own libcalc example on Unix. They are open as +quil-lang/sbcl-librarian#91. + +**Release ordering.** libquil-sys cannot build against a released libquil older +than this change, so the two have to be released together: cut a libquil +prerelease (`knope prerelease`, added for this reason), point libquil-sys's +`LIBQUIL_VERSION` at it, then release both for real. diff --git a/install.sh b/install.sh index 1f0e295..95662b8 100755 --- a/install.sh +++ b/install.sh @@ -7,11 +7,15 @@ err() { exit 1 } +# Which repository to fetch releases from. Override to install from a fork, which +# is how a prerelease can be tested before it is published from the main repository. +LIBQUIL_RELEASE_REPO="${LIBQUIL_RELEASE_REPO:-rigetti/libquil}" + if [[ -n "${1-}" ]] then - LIBQUIL_URL_PREFIX="https://github.com/rigetti/libquil/releases/download/v${1}" + LIBQUIL_URL_PREFIX="https://github.com/${LIBQUIL_RELEASE_REPO}/releases/download/v${1}" else - LIBQUIL_URL_PREFIX="https://github.com/rigetti/libquil/releases/latest/download" + LIBQUIL_URL_PREFIX="https://github.com/${LIBQUIL_RELEASE_REPO}/releases/latest/download" fi OS="$(uname)" @@ -55,7 +59,7 @@ unzip "${LIBQUIL_RELEASE_FILE}" # libquil.core must land in the same directory as libsbcl_librarian: the runtime # locates its core relative to its own path. -sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" +sudo mkdir -p "${LIBQUIL_LIB_PREFIX}" "${LIBQUIL_INCLUDE_PREFIX}" sudo cp libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h "${LIBQUIL_INCLUDE_PREFIX}" sudo cp libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" diff --git a/versions b/versions index cddad57..a147726 100644 --- a/versions +++ b/versions @@ -4,5 +4,7 @@ QVM_VERSION=4617625cb6053b1adfd3f7aea9cd2be328b225f6 QUILC_VERSION=3d83b665b3addc33be2f696b1cdebc0c4c5a04f9 SBCL_LIBRARIAN_VERSION=fix-secondary-system-bundles SBCL_VERSION=sbcl-2.6.7 +# Linux cross-compilation host; see build.yml. +SBCL_BOOTSTRAP_VERSION=2.5.0 # 2022-04-01 predates clos-encounters, which current cl-quil depends on. QUICKLISP_VERSION=2026-01-01 From b163826bca1c6a3fc6875a2398f59ee418babeb3 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Wed, 12 Aug 2026 11:35:23 -0600 Subject: [PATCH 11/11] fix: check for BLAS and LAPACK, and install without sudo when already root Two problems with installing a release: magicl loads BLAS and LAPACK at runtime under their unversioned names, libblas.so and liblapack.so, so a missing one does not surface at install or link time: it surfaces much later, in the middle of compiling a program. Check for them before anything is downloaded, and name the ones that are missing. The unversioned names are also why a runtime-only package is not enough -- Debian's libblas3 provides libblas.so.3 and no unversioned symlink -- so say that in the requirements, which is the part users get wrong. Drop libz from the requirements while there: nothing in the release artifacts refers to it. Container images commonly run as root with no sudo installed, where every sudo call here fails with "sudo: command not found" even though nothing needs elevating. That is how CI installs libquil, so the installer was unusable there. Resolve sudo once: empty when already root, sudo when available, and a clear error when neither. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 8 +++-- install.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9cd7613..2a58c10 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,16 @@ These libraries are required by `libquil`: - `BLAS`, `LAPACK` for linear algebra routines - `libffi` for cross-language execution -- `libz` for compression + +`magicl` loads `BLAS` and `LAPACK` at runtime under their unversioned names, +`libblas.so` and `liblapack.so` (`.dylib` on macOS). Distributions ship those names in +their development packages, so installing only a runtime package such as Debian's +`libblas3` — which provides `libblas.so.3` and no unversioned symlink — is not enough. On systems which use `apt` to install packages (e.g. Ubuntu), these libraries can be installed with the command ``` -sudo apt install libblas-dev libffi-dev liblapack-dev libz-dev +sudo apt install libblas-dev libffi-dev liblapack-dev ``` On systems which use `brew` to install packages (e.g macOS), these libraries can be installed with the command diff --git a/install.sh b/install.sh index 95662b8..d26af0c 100755 --- a/install.sh +++ b/install.sh @@ -48,32 +48,99 @@ else err "Unsupported operating system. Supported operating systems are Linux and macOS." fi +# magicl dlopens BLAS and LAPACK under their unversioned names once libquil is in use, +# so a missing one is not a link error at install time but a failure much later, in the +# middle of compiling a program. Check for them up front instead. +if [[ "${OS}" == "Darwin" ]] +then + LIBQUIL_LIB_SUFFIX="dylib" +else + LIBQUIL_LIB_SUFFIX="so" +fi + +library_is_available() { + local soname="lib${1}.${LIBQUIL_LIB_SUFFIX}" + + # The loader's own cache is authoritative where it exists. + if [[ -z "${IS_LINUX-}" ]] + then + # dyld has no queryable cache; check the paths it searches by default, plus the + # Homebrew prefixes that are not on it. Counting the latter keeps a normal + # `brew install openblas` from being reported as missing, at the cost of not + # catching the case where magicl ends up unable to load a keg-only install. + local dir + for dir in /usr/local/lib /usr/lib /opt/homebrew/lib /opt/homebrew/opt/openblas/lib + do + [[ -e "${dir}/${soname}" ]] && return 0 + done + return 1 + elif command -v ldconfig >/dev/null 2>&1 + then + ldconfig -p | grep -q "[[:space:]]${soname}[[:space:]]" && return 0 + fi + + local dir + for dir in /usr/local/lib /usr/lib /usr/lib64 /lib /lib64 + do + [[ -e "${dir}/${soname}" ]] && return 0 + done + return 1 +} + +LIBQUIL_MISSING=() +for lib in blas lapack +do + library_is_available "${lib}" || LIBQUIL_MISSING+=("lib${lib}.${LIBQUIL_LIB_SUFFIX}") +done + +if [[ "${#LIBQUIL_MISSING[@]}" -gt 0 ]] +then + err "Missing required libraries: ${LIBQUIL_MISSING[*]}" \ + "" \ + "libquil loads these at runtime under exactly these unversioned names, so a" \ + "runtime-only package that provides a versioned name is not sufficient." \ + "See https://github.com/rigetti/libquil#requirements" +fi + LIBQUIL_RELEASE_URL="${LIBQUIL_URL_PREFIX}/${LIBQUIL_RELEASE_FILE}" LIBQUIL_TEMP_DIR="$(mktemp -d)" LIBQUIL_LIB_PREFIX="/usr/local/lib" LIBQUIL_INCLUDE_PREFIX="/usr/local/include/libquil" +# Installing into /usr/local needs root. Container images commonly run as root without +# sudo installed, where calling it would fail even though nothing needs elevating. +if [[ "$(id -u)" -eq 0 ]] +then + SUDO="" +elif command -v sudo >/dev/null 2>&1 +then + SUDO="sudo" +else + err "This installer needs root to write to ${LIBQUIL_LIB_PREFIX} and ${LIBQUIL_INCLUDE_PREFIX}," \ + "but it is not running as root and sudo is not available." +fi + pushd "${LIBQUIL_TEMP_DIR}" || exit curl -L "${LIBQUIL_RELEASE_URL}" -o "${LIBQUIL_RELEASE_FILE}" unzip "${LIBQUIL_RELEASE_FILE}" # libquil.core must land in the same directory as libsbcl_librarian: the runtime # locates its core relative to its own path. -sudo mkdir -p "${LIBQUIL_LIB_PREFIX}" "${LIBQUIL_INCLUDE_PREFIX}" -sudo cp libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h "${LIBQUIL_INCLUDE_PREFIX}" -sudo cp libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" +${SUDO} mkdir -p "${LIBQUIL_LIB_PREFIX}" "${LIBQUIL_INCLUDE_PREFIX}" +${SUDO} cp libquil/libquil.h libquil/sbcl_librarian.h libquil/sbcl_librarian_err.h "${LIBQUIL_INCLUDE_PREFIX}" +${SUDO} cp libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" if [[ -n "${IS_LINUX-}" ]] then - sudo cp libquil/libquil.so libquil/libsbcl_librarian.so "${LIBQUIL_LIB_PREFIX}" - sudo ldconfig + ${SUDO} cp libquil/libquil.so libquil/libsbcl_librarian.so "${LIBQUIL_LIB_PREFIX}" + ${SUDO} ldconfig else - sudo cp libquil/libquil.dylib libquil/libsbcl_librarian.dylib "${LIBQUIL_LIB_PREFIX}" + ${SUDO} cp libquil/libquil.dylib libquil/libsbcl_librarian.dylib "${LIBQUIL_LIB_PREFIX}" # This disables the "cannot open libquil.dylib from untrusted developer" dialog. # A better solution for this would be to properly codesign the files, but that # is a non-trivial amount of work. - sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.dylib" - sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl_librarian.dylib" - sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.core" - sudo xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl.so" + ${SUDO} xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.dylib" + ${SUDO} xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl_librarian.dylib" + ${SUDO} xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libquil.core" + ${SUDO} xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/libsbcl.so" fi