Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/baseline-cpu-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bunny.net/cli": patch
---

fix(install): ship a baseline (non-AVX2) build for older x64 CPUs that crashed with "Illegal instruction" — the installer auto-selects it, and the npm wrapper falls back to it on SIGILL
5 changes: 5 additions & 0 deletions .changeset/bsql-baseline-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bunny.net/database-shell": patch
---

fix(bsql): fall back to a baseline (non-AVX2) binary on older x64 CPUs that crashed with "Illegal instruction"
91 changes: 91 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,43 @@ jobs:
path: packages/${{ matrix.npm-pkg }}/
if-no-files-found: error

build-cli-baseline:
name: Build ${{ matrix.binary }}
needs: version
if: needs.version.outputs.cli-version
strategy:
matrix:
include:
- target: bun-linux-x64-baseline
os: ubuntu-latest
binary: bunny-linux-x64-baseline
- target: bun-darwin-x64-baseline
os: macos-latest
binary: bunny-darwin-x64-baseline

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"
- run: bun install

- name: Build database studio
run: bun run --filter @bunny.net/database-studio build:client

# Baseline targets avoid AVX2, so the binary runs on pre-Haswell x64 CPUs that crash the default build with SIGILL.
- name: Build binary
run: bun build packages/cli/src/index.ts --compile --minify --sourcemap --target=${{ matrix.target }} --outfile ${{ matrix.binary }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.binary }}
path: ${{ matrix.binary }}
if-no-files-found: error

build-database-shell-binaries:
name: Build ${{ matrix.npm-pkg }}
needs: version
Expand Down Expand Up @@ -178,12 +215,47 @@ jobs:
path: packages/${{ matrix.npm-pkg }}/
if-no-files-found: error

build-database-shell-baseline:
name: Build ${{ matrix.binary }}
needs: version
if: needs.version.outputs.database-shell-version
strategy:
matrix:
include:
- target: bun-linux-x64-baseline
os: ubuntu-latest
binary: bsql-linux-x64-baseline
- target: bun-darwin-x64-baseline
os: macos-latest
binary: bsql-darwin-x64-baseline

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"
- run: bun install

# Baseline targets avoid AVX2, so the binary runs on pre-Haswell x64 CPUs that crash the default build with SIGILL.
- name: Build binary
run: bun build packages/database-shell/src/cli.ts --compile --minify --sourcemap --target=${{ matrix.target }} --outfile ${{ matrix.binary }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.binary }}
path: ${{ matrix.binary }}
if-no-files-found: error

publish-cli:
name: Publish CLI
runs-on: ubuntu-latest
needs:
- version
- build-cli-binaries
- build-cli-baseline
steps:
- uses: actions/checkout@v5

Expand All @@ -203,12 +275,20 @@ jobs:
cp -r npm-artifacts/$pkg/* packages/$pkg/
done

# x64 packages also ship a baseline binary the wrapper falls back to on AVX2-less CPUs.
- name: Copy baseline binaries into x64 npm packages
run: |
cp npm-artifacts/bunny-linux-x64-baseline/bunny-linux-x64-baseline packages/cli-linux-x64/bunny-baseline
cp npm-artifacts/bunny-darwin-x64-baseline/bunny-darwin-x64-baseline packages/cli-darwin-x64/bunny-baseline

- name: Set execute permissions
run: |
chmod +x packages/cli-linux-x64/bunny
chmod +x packages/cli-linux-arm64/bunny
chmod +x packages/cli-darwin-x64/bunny
chmod +x packages/cli-darwin-arm64/bunny
chmod +x packages/cli-linux-x64/bunny-baseline
chmod +x packages/cli-darwin-x64/bunny-baseline

- name: Publish platform packages
run: |
Expand Down Expand Up @@ -247,6 +327,8 @@ jobs:
npm-artifacts/cli-darwin-x64/bunny-darwin-x64
npm-artifacts/cli-darwin-arm64/bunny-darwin-arm64
npm-artifacts/cli-windows-x64/bunny-windows-x64.exe
npm-artifacts/bunny-linux-x64-baseline/bunny-linux-x64-baseline
npm-artifacts/bunny-darwin-x64-baseline/bunny-darwin-x64-baseline
fail_on_unmatched_files: true

publish-database-shell:
Expand All @@ -255,6 +337,7 @@ jobs:
needs:
- version
- build-database-shell-binaries
- build-database-shell-baseline
steps:
- uses: actions/checkout@v5

Expand All @@ -274,12 +357,20 @@ jobs:
cp -r npm-artifacts/$pkg/* packages/$pkg/
done

# x64 packages also ship a baseline binary the wrapper falls back to on AVX2-less CPUs.
- name: Copy baseline binaries into x64 npm packages
run: |
cp npm-artifacts/bsql-linux-x64-baseline/bsql-linux-x64-baseline packages/database-shell-linux-x64/bsql-baseline
cp npm-artifacts/bsql-darwin-x64-baseline/bsql-darwin-x64-baseline packages/database-shell-darwin-x64/bsql-baseline

- name: Set execute permissions
run: |
chmod +x packages/database-shell-linux-x64/bsql
chmod +x packages/database-shell-linux-arm64/bsql
chmod +x packages/database-shell-darwin-x64/bsql
chmod +x packages/database-shell-darwin-arm64/bsql
chmod +x packages/database-shell-linux-x64/bsql-baseline
chmod +x packages/database-shell-darwin-x64/bsql-baseline

- name: Publish platform packages
run: |
Expand Down
17 changes: 16 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ get_arch() {
esac
}

has_avx2() {
case "$OS" in
linux) grep -qw avx2 /proc/cpuinfo 2>/dev/null ;;
darwin) sysctl -n machdep.cpu.leaf7_features 2>/dev/null | grep -qiw avx2 ;;
*) return 1 ;;
esac
}

OS=$(get_os)
ARCH=$(get_arch)

Expand All @@ -30,7 +38,14 @@ if [ "$OS" = "unsupported" ] || [ "$ARCH" = "unsupported" ]; then
exit 1
fi

BINARY="bunny-${OS}-${ARCH}"
# The default Bun-compiled binary uses AVX2; pre-Haswell (~2013) x64 CPUs lack it and crash with "Illegal instruction", so fall back to the baseline build.
VARIANT=""
if [ "$ARCH" = "x64" ] && ! has_avx2; then
VARIANT="-baseline"
echo "AVX2 not detected; using baseline build."
fi

BINARY="bunny-${OS}-${ARCH}${VARIANT}"

# Pinned version uses the tagged release URL; otherwise use the `latest`
# redirect so we don't hit api.github.com (rate-limited to 60 req/hr).
Expand Down
3 changes: 2 additions & 1 deletion packages/cli-darwin-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"x64"
],
"files": [
"bunny"
"bunny",
"bunny-baseline"
],
"publishConfig": {
"access": "public"
Expand Down
3 changes: 2 additions & 1 deletion packages/cli-linux-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"x64"
],
"files": [
"bunny"
"bunny",
"bunny-baseline"
],
"publishConfig": {
"access": "public"
Expand Down
60 changes: 52 additions & 8 deletions packages/cli/bin/bunny.cjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
#!/usr/bin/env node

const { execFileSync } = require("node:child_process");
const { existsSync } = require("node:fs");
const { existsSync, readFileSync } = require("node:fs");
const path = require("node:path");

// Only x64 binaries use AVX2; assume it's present on other arches or when detection fails, letting the SIGILL fallback catch any mistake.
function hasAvx2() {
if (process.arch !== "x64") return true;
try {
if (process.platform === "linux") {
return readFileSync("/proc/cpuinfo", "utf8").includes("avx2");
}
if (process.platform === "darwin") {
const features = execFileSync(
"sysctl",
["-n", "machdep.cpu.leaf7_features"],
{ encoding: "utf8" },
);
return /avx2/i.test(features);
}
} catch {
return true;
}
return true;
}

const PLATFORMS = {
"darwin-arm64": "@bunny.net/cli-darwin-arm64",
"darwin-x64": "@bunny.net/cli-darwin-x64",
Expand Down Expand Up @@ -45,12 +66,35 @@ if (!existsSync(binPath)) {
process.exit(1);
}

try {
execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
} catch (err) {
if (err.status != null) {
process.exit(err.status);
// The default binary uses AVX2; on pre-Haswell x64 CPUs it dies with SIGILL, so fall back to the baseline build shipped alongside it.
const baselinePath = path.join(path.dirname(binPath), "bunny-baseline");
const candidates = [binPath];
if (existsSync(baselinePath)) {
candidates.push(baselinePath);
}
// Run the baseline first on x64 CPUs without AVX2, so we don't spawn the default binary just to catch its crash on every invocation.
if (candidates.length > 1 && !hasAvx2()) {
candidates.reverse();
}

for (let i = 0; i < candidates.length; i++) {
try {
execFileSync(candidates[i], process.argv.slice(2), { stdio: "inherit" });
process.exit(0);
} catch (err) {
if (err.status != null) {
process.exit(err.status);
}
if (err.signal === "SIGILL" && i < candidates.length - 1) {
continue;
}
if (err.signal === "SIGILL") {
console.error(
"bunny crashed with an illegal instruction; this CPU is not supported.",
);
process.exit(1);
}
console.error(`Failed to execute bunny binary: ${err.message}`);
process.exit(1);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
console.error(`Failed to execute bunny binary: ${err.message}`);
process.exit(1);
}
3 changes: 2 additions & 1 deletion packages/database-shell-darwin-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"x64"
],
"files": [
"bsql"
"bsql",
"bsql-baseline"
],
"publishConfig": {
"access": "public"
Expand Down
3 changes: 2 additions & 1 deletion packages/database-shell-linux-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"x64"
],
"files": [
"bsql"
"bsql",
"bsql-baseline"
],
"publishConfig": {
"access": "public"
Expand Down
60 changes: 52 additions & 8 deletions packages/database-shell/bin/bsql.cjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
#!/usr/bin/env node

const { execFileSync } = require("node:child_process");
const { existsSync } = require("node:fs");
const { existsSync, readFileSync } = require("node:fs");
const path = require("node:path");

// Only x64 binaries use AVX2; assume it's present on other arches or when detection fails, letting the SIGILL fallback catch any mistake.
function hasAvx2() {
if (process.arch !== "x64") return true;
try {
if (process.platform === "linux") {
return readFileSync("/proc/cpuinfo", "utf8").includes("avx2");
}
if (process.platform === "darwin") {
const features = execFileSync(
"sysctl",
["-n", "machdep.cpu.leaf7_features"],
{ encoding: "utf8" },
);
return /avx2/i.test(features);
}
} catch {
return true;
}
return true;
}

const PLATFORMS = {
"darwin-arm64": "@bunny.net/database-shell-darwin-arm64",
"darwin-x64": "@bunny.net/database-shell-darwin-x64",
Expand Down Expand Up @@ -45,12 +66,35 @@ if (!existsSync(binPath)) {
process.exit(1);
}

try {
execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
} catch (err) {
if (err.status != null) {
process.exit(err.status);
// The default binary uses AVX2; on pre-Haswell x64 CPUs it dies with SIGILL, so fall back to the baseline build shipped alongside it.
const baselinePath = path.join(path.dirname(binPath), "bsql-baseline");
const candidates = [binPath];
if (existsSync(baselinePath)) {
candidates.push(baselinePath);
}
// Run the baseline first on x64 CPUs without AVX2, so we don't spawn the default binary just to catch its crash on every invocation.
if (candidates.length > 1 && !hasAvx2()) {
candidates.reverse();
}

for (let i = 0; i < candidates.length; i++) {
try {
execFileSync(candidates[i], process.argv.slice(2), { stdio: "inherit" });
process.exit(0);
} catch (err) {
if (err.status != null) {
process.exit(err.status);
}
if (err.signal === "SIGILL" && i < candidates.length - 1) {
continue;
}
if (err.signal === "SIGILL") {
console.error(
"bsql crashed with an illegal instruction; this CPU is not supported.",
);
process.exit(1);
}
console.error(`Failed to execute bsql binary: ${err.message}`);
process.exit(1);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
console.error(`Failed to execute bsql binary: ${err.message}`);
process.exit(1);
}