Skip to content

Use sbcl-librarian runtime - #57

Closed
BatmanAoD wants to merge 12 commits into
rigetti:mainfrom
BatmanAoD:sbcl-librarian-runtime
Closed

Use sbcl-librarian runtime#57
BatmanAoD wants to merge 12 commits into
rigetti:mainfrom
BatmanAoD:sbcl-librarian-runtime

Conversation

@BatmanAoD

@BatmanAoD BatmanAoD commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

more comprehensive alternative to #56 (close that if this is merged)

@BatmanAoD
BatmanAoD force-pushed the sbcl-librarian-runtime branch from 0c5c26c to 5fb2a7d Compare August 12, 2026 03:10
BatmanAoD and others added 2 commits August 11, 2026 21:14
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) <noreply@anthropic.com>
- 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) <noreply@anthropic.com>
@BatmanAoD
BatmanAoD force-pushed the sbcl-librarian-runtime branch 2 times, most recently from 3297949 to 8fec83f Compare August 12, 2026 03:18
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) <noreply@anthropic.com>
@BatmanAoD
BatmanAoD force-pushed the sbcl-librarian-runtime branch 8 times, most recently from 47329b2 to ccce870 Compare August 12, 2026 04:04
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) <noreply@anthropic.com>
@BatmanAoD
BatmanAoD force-pushed the sbcl-librarian-runtime branch 2 times, most recently from 85ca44b to b785c52 Compare August 13, 2026 15:22
… 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) <noreply@anthropic.com>
@BatmanAoD
BatmanAoD force-pushed the sbcl-librarian-runtime branch from b785c52 to b163826 Compare August 13, 2026 15:23
@BatmanAoD BatmanAoD changed the title Sbcl librarian runtime Use sbcl-librarian runtime Aug 18, 2026
@BatmanAoD

Copy link
Copy Markdown
Contributor Author

Superseded by #58

@BatmanAoD BatmanAoD closed this Aug 18, 2026
BatmanAoD added a commit to rigetti/qcs-sdk-rust that referenced this pull request Aug 31, 2026
* style: apply rustfmt to random.rs

Formatting only, from running cargo fmt while working on the libquil build; no
behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* build: install the libquil prerelease that carries the sbcl-librarian runtime

libquil-sys 0.5 links against the sbcl-librarian runtime that libquil is built on, so it
needs a libquil that installs that runtime and its headers alongside libquil.h. Releases
up to and including 0.3.2 ship neither, and CI was installing 0.3.0 with the installer
from rigetti/libquil's default branch, which does not install the runtime either.

That build exists so far only as a prerelease from a fork, so pin both the version and
the repository it comes from, and take the installer from the tag being installed rather
than from a branch. TODO(github.com/rigetti/libquil#57) tracks removing the pin.

scripts/install-libquil now installs only the prerequisites -- libquil's own BLAS and
LAPACK, plus libclang for bindgen -- and hands off to libquil's installer instead of
duplicating what it does. It covers macOS via Homebrew as well as apt-based Linux, and
stops with an explanation elsewhere. The libffi.so.7 compat symlink is gone: the current
build does not link libffi at all, let alone that soname.

BLAS and LAPACK come from the -dev packages deliberately. magicl loads them at runtime
under their unversioned names, so Debian's libblas3, which provides only libblas.so.3,
is not enough.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test with libquil instead of qvm/quil-clients

* ci: release from the ref a prerelease is dispatched on

Cutting a prerelease from a branch tagged the wrong code. knope creates the GitHub
release against the default branch on the version this repository pins, 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 -- would then try to republish main's
already-released version.

Adopt what libquil-sys arrived at (545d05d):

- knope 0.23, which tags the ref being released;
- check out ${{ github.ref }} in prepare-release, so a dispatched release acts on the
  branch it was dispatched from;
- check out the release tag in release-library, so it publishes exactly what was tagged.

Verified against this branch with knope 0.23 locally: it now tags lib/v0.26.4-rc.0 at the
branch head rather than at main.

Add a changeset for the libquil-sys 0.5 dependency, which none of the branch's commit
types would have released on their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: prepare new release(s) [skip ci]

* ci: build what was tagged when releasing the Python package

release-python.yml checks out without naming a ref, so every job builds the release
event's target commitish rather than the tagged commit. A release cut from a branch would
build main's code and publish it under the branch's version, the same way release-library
would have republished main's version before it was fixed.

Point all seven checkouts at the release tag, falling back to the dispatched ref for a
manual run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* build: install libquil from rigetti/libquil

The prerelease carrying the sbcl-librarian runtime is now published from rigetti/libquil
itself (v0.4.0-rc.0, built by its own CI), so the fork it was coming from is going away.
Drop LIBQUIL_RELEASE_REPO and fetch the installer straight from the tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop two merge artifacts from the main merge

The merge of main kept this branch's side of two hunks that
"feat!: update dependencies (including PyO3) (#592)" had changed, breaking
two CI jobs:

  Run Python Checks failed at syphar/restore-virtualenv@v1 with "could not
  find requirement-files". #592 migrated to uv and removed that action in
  three places; the merge resurrected one, because this branch had added the
  adjacent "Install libquil" step. The same hunk also lost the sudo main uses
  for that step.

  publish-docs failed with stub_gen rejecting
  "Module 'qcs_sdk.qpu.experimental.random' ... is not under the PyO3 module
  path 'qcs_sdk._qcs_sdk'". This branch's rustfmt commit reformatted the
  pyclass attribute on PrngSeedValue, and the merge kept that version over
  main's fix, which moves it under _qcs_sdk and adds from_py_object.

Both files are now byte-identical to main, which is the check that they
carry no leftover divergence.

* update install-script path

* build: call libquil's installer directly, dropping the wrapper script

scripts/install-libquil installed libquil's prerequisites and then handed off to
libquil's own installer, because that installer could not install them itself. It
now can: --install-deps installs BLAS, LAPACK and libffi with apt or Homebrew,
checks that one of them is present, and reports what to install by hand
otherwise. The wrapper had become a reimplementation of what it wrapped.

libclang stays at the call sites: bindgen needs it to generate libquil-sys's
bindings, which makes it a consumer build dependency rather than a libquil
requirement. So do unzip and curl, which libquil's installer needs but cannot
bootstrap.

The version pin moves to a Dockerfile ARG and a workflow env, and the README now
shows the command rather than pointing at a script.

Also corrects the README's claim that omitting the version is fine: libquil's
latest release is 0.3.2, which ships neither the sbcl-librarian runtime nor its
headers, so the version has to be passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* build: pin libquil 0.4.0 now that it is released

Bumps LIBQUIL_VERSION from the 0.4.0-rc.1 prerelease to 0.4.0 in CI, the
devcontainer image and the README, and drops the README's warning about
omitting the version, which described the world before 0.4.0 existed.

The libquil-sys dependency stays at 0.5.0-rc.2: no stable 0.5.0 is published
yet, and it is the crate that has to pick up libquil 0.4.0 first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* pin to 0.4.1

* cleanup - delete unnecessary checkout defensiveness

* chore: prepare new release(s) [skip ci]

* chore: update lockfile

* chore: prepare new release(s) [skip ci]

* chore: remove prerelease from changelog

* chore: upgrade quil-rs and libquil-sys; removes paste from dependency tree

* chore: remove unused 'allow's from cargo-deny config

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant