freebuff 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/launcher.js +225 -37
- package/package.json +9 -9
package/launcher.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process')
|
|
3
|
+
const { execFileSync, spawn } = require('child_process')
|
|
4
4
|
const crypto = require('crypto')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const http = require('http')
|
|
@@ -146,6 +146,9 @@ function createLauncher(productConfig) {
|
|
|
146
146
|
// Tests only. os.homedir() ignores $HOME under `bun test`, so pointing HOME
|
|
147
147
|
// at a temp dir is not enough to keep a test off the real ~/.config.
|
|
148
148
|
configDir: configDirOverride = null,
|
|
149
|
+
// Tests only: stands in for the OS AVX2 probe (/proc/cpuinfo, sysctl),
|
|
150
|
+
// returning true, false, or null for "could not tell".
|
|
151
|
+
cpuAvx2Probe: cpuAvx2ProbeOverride = null,
|
|
149
152
|
} = productConfig
|
|
150
153
|
|
|
151
154
|
/**
|
|
@@ -282,6 +285,19 @@ function createLauncher(productConfig) {
|
|
|
282
285
|
const BUN_ILLEGAL_INSTRUCTION_PANIC =
|
|
283
286
|
/^panic(?:\([^)\n]*\))?: Illegal instruction at address 0x[0-9a-f]+/im
|
|
284
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Bun's `CPU:` header line lists the features it detected (`CPU: sse42
|
|
290
|
+
* popcnt avx avx2`). avx2 in that list is Bun's own CPUID saying the CPU has
|
|
291
|
+
* it, so an illegal instruction on such a machine is not a missing AVX2.
|
|
292
|
+
*/
|
|
293
|
+
const BUN_CPU_LINE = /^CPU:([^\n]*)$/m
|
|
294
|
+
|
|
295
|
+
function stderrReportsAvx2Present(stderrTail) {
|
|
296
|
+
if (!stderrTail) return false
|
|
297
|
+
const match = BUN_CPU_LINE.exec(sanitizeForReplay(stderrTail))
|
|
298
|
+
return Boolean(match && /(?:^|\s)avx2(?:\s|$)/i.test(match[1]))
|
|
299
|
+
}
|
|
300
|
+
|
|
285
301
|
function stderrReportsMissingCpuFeature(code, signal, stderrTail) {
|
|
286
302
|
if (!stderrTail || (code === 0 && !signal)) return false
|
|
287
303
|
const text = sanitizeForReplay(stderrTail)
|
|
@@ -442,12 +458,75 @@ function createLauncher(productConfig) {
|
|
|
442
458
|
return null
|
|
443
459
|
}
|
|
444
460
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
461
|
+
/**
|
|
462
|
+
* `/proc/cpuinfo` answers for the kernel's view of the CPU: true or false
|
|
463
|
+
* from its `flags` lines, null when it has none to read.
|
|
464
|
+
*/
|
|
465
|
+
function parseCpuinfoAvx2(text) {
|
|
466
|
+
if (typeof text !== 'string' || !/^flags\s*:/m.test(text)) return null
|
|
467
|
+
return /^flags\s*:.*\bavx2\b/m.test(text)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** `sysctl -n hw.optional.avx2_0` prints 1 or 0 on an Intel Mac. */
|
|
471
|
+
function parseSysctlAvx2(output) {
|
|
472
|
+
const value = typeof output === 'string' ? output.trim() : ''
|
|
473
|
+
if (value === '1') return true
|
|
474
|
+
if (value === '0') return false
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Where probeCpuAvx2() gets its answer, recorded as the evidence for a pin.
|
|
479
|
+
function cpuProbeSource() {
|
|
480
|
+
if (process.platform === 'linux') return 'cpuinfo'
|
|
481
|
+
if (process.platform === 'darwin') return 'sysctl'
|
|
482
|
+
return 'probe'
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let _cpuProbeCache
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Ask the OS whether this CPU has AVX2: true, false, or null for "could not
|
|
489
|
+
* tell". Linux reads /proc/cpuinfo. macOS asks sysctl, which costs a
|
|
490
|
+
* subprocess, so this runs only on the crash path, when re-checking an old
|
|
491
|
+
* pin, and when picking a download target for a baseline install, never on
|
|
492
|
+
* an ordinary launch. Windows has no probe: the one we had tripped Defender
|
|
493
|
+
* (see detectMachineHasAvx2), so there Bun's crash report is the evidence.
|
|
494
|
+
*/
|
|
495
|
+
function probeCpuAvx2() {
|
|
496
|
+
if (_cpuProbeCache === undefined) {
|
|
497
|
+
_cpuProbeCache = cpuAvx2ProbeOverride
|
|
498
|
+
? cpuAvx2ProbeOverride()
|
|
499
|
+
: probeCpuAvx2FromOs()
|
|
500
|
+
}
|
|
501
|
+
return _cpuProbeCache
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function probeCpuAvx2FromOs() {
|
|
505
|
+
if (process.arch !== 'x64') return null
|
|
506
|
+
if (process.platform === 'linux') {
|
|
507
|
+
try {
|
|
508
|
+
return parseCpuinfoAvx2(fs.readFileSync('/proc/cpuinfo', 'utf8'))
|
|
509
|
+
} catch {
|
|
510
|
+
return null
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (process.platform === 'darwin') {
|
|
514
|
+
// x64 node on Apple Silicon runs under Rosetta, and sysctl (arm64 only)
|
|
515
|
+
// then describes the host, not what Rosetta emulates. No answer there.
|
|
516
|
+
if (/\bApple\b/.test(os.cpus()[0]?.model || '')) return null
|
|
517
|
+
try {
|
|
518
|
+
return parseSysctlAvx2(
|
|
519
|
+
execFileSync('/usr/sbin/sysctl', ['-n', 'hw.optional.avx2_0'], {
|
|
520
|
+
encoding: 'utf8',
|
|
521
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
522
|
+
timeout: 2000,
|
|
523
|
+
}),
|
|
524
|
+
)
|
|
525
|
+
} catch {
|
|
526
|
+
return null
|
|
527
|
+
}
|
|
450
528
|
}
|
|
529
|
+
return null
|
|
451
530
|
}
|
|
452
531
|
|
|
453
532
|
let _hasAvx2Cache
|
|
@@ -464,17 +543,33 @@ function createLauncher(productConfig) {
|
|
|
464
543
|
return true
|
|
465
544
|
}
|
|
466
545
|
|
|
467
|
-
// A recorded
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
546
|
+
// A recorded verdict outranks everything below it. Every record now names
|
|
547
|
+
// its evidence: Bun's crash report, the OS probe, or 'crash' for an
|
|
548
|
+
// illegal instruction nothing could explain.
|
|
549
|
+
//
|
|
550
|
+
// A record WITHOUT evidence was written by an older launcher, which pinned
|
|
551
|
+
// baseline on any SIGILL, including a bug on a CPU that has AVX2. Re-check
|
|
552
|
+
// those against the OS once: a CPU with AVX2 gets its record dropped and
|
|
553
|
+
// the optimized build back on the next update (see getDownloadTargetKey).
|
|
554
|
+
const record = readCpuFeatureRecord()
|
|
555
|
+
if (record && (record.avx2 || record.evidence)) {
|
|
556
|
+
return record.avx2
|
|
557
|
+
}
|
|
558
|
+
if (record) {
|
|
559
|
+
const probed = probeCpuAvx2()
|
|
560
|
+
if (probed !== true) {
|
|
561
|
+
writeCpuFeatureRecord(
|
|
562
|
+
false,
|
|
563
|
+
probed === false ? cpuProbeSource() : 'crash',
|
|
564
|
+
)
|
|
565
|
+
return false
|
|
566
|
+
}
|
|
567
|
+
clearCpuFeatureRecord()
|
|
473
568
|
}
|
|
474
569
|
|
|
475
570
|
// Linux can just ask, and a file read is cheap enough not to cache.
|
|
476
571
|
if (process.platform === 'linux') {
|
|
477
|
-
return
|
|
572
|
+
return probeCpuAvx2() ?? true
|
|
478
573
|
}
|
|
479
574
|
|
|
480
575
|
// Everything else assumes AVX2 — true of every x64 CPU since ~2013.
|
|
@@ -492,42 +587,77 @@ function createLauncher(productConfig) {
|
|
|
492
587
|
return true
|
|
493
588
|
}
|
|
494
589
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
590
|
+
/**
|
|
591
|
+
* Positive evidence that this CPU runs the optimized build: a recorded
|
|
592
|
+
* verdict with evidence, or the OS probe. The optimistic default does not
|
|
593
|
+
* count. This is what lets a baseline install move back to the optimized
|
|
594
|
+
* build; without evidence, baseline stays put.
|
|
595
|
+
*/
|
|
596
|
+
function knownToHaveAvx2() {
|
|
597
|
+
if (!machineHasAvx2()) return false
|
|
598
|
+
const record = readCpuFeatureRecord()
|
|
599
|
+
if (record?.avx2 === true && record.evidence) return true
|
|
600
|
+
return probeCpuAvx2() === true
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Called from the illegal-instruction fallback, only when the CPU is shown
|
|
604
|
+
// to lack AVX2 or nothing can say either way (see tryFallbackToBaseline).
|
|
605
|
+
// Persisting here is what keeps the optimistic assumption above from costing
|
|
606
|
+
// more than a single crash — and it is deliberately separate from the
|
|
607
|
+
// metadata target, so a lost or rewritten metadata file can't resurrect the
|
|
608
|
+
// AVX2 build on a CPU that can't run it.
|
|
609
|
+
function recordMachineLacksAvx2(evidence = 'crash') {
|
|
500
610
|
_hasAvx2Cache = false
|
|
501
|
-
|
|
611
|
+
writeCpuFeatureRecord(false, evidence)
|
|
502
612
|
}
|
|
503
613
|
|
|
504
614
|
function getCpuFeatureCachePath() {
|
|
505
615
|
return path.join(CONFIG.configDir, 'cpu-features.json')
|
|
506
616
|
}
|
|
507
617
|
|
|
508
|
-
|
|
618
|
+
/** `{ avx2, evidence }`; evidence is null on a record from an older launcher. */
|
|
619
|
+
function readCpuFeatureRecord() {
|
|
509
620
|
try {
|
|
510
621
|
const cache = JSON.parse(
|
|
511
622
|
fs.readFileSync(getCpuFeatureCachePath(), 'utf8'),
|
|
512
623
|
)
|
|
513
|
-
|
|
624
|
+
if (typeof cache.avx2 !== 'boolean') return null
|
|
625
|
+
return {
|
|
626
|
+
avx2: cache.avx2,
|
|
627
|
+
evidence:
|
|
628
|
+
typeof cache.evidence === 'string' && cache.evidence
|
|
629
|
+
? cache.evidence
|
|
630
|
+
: null,
|
|
631
|
+
}
|
|
514
632
|
} catch {
|
|
515
633
|
return null
|
|
516
634
|
}
|
|
517
635
|
}
|
|
518
636
|
|
|
519
|
-
function
|
|
637
|
+
function readCachedAvx2() {
|
|
638
|
+
return readCpuFeatureRecord()?.avx2 ?? null
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function writeCpuFeatureRecord(value, evidence) {
|
|
520
642
|
try {
|
|
521
643
|
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
522
644
|
fs.writeFileSync(
|
|
523
645
|
getCpuFeatureCachePath(),
|
|
524
|
-
JSON.stringify({ avx2: value }),
|
|
646
|
+
JSON.stringify({ avx2: value, evidence }),
|
|
525
647
|
)
|
|
526
648
|
} catch {
|
|
527
649
|
// Best effort; we'll just re-probe next launch.
|
|
528
650
|
}
|
|
529
651
|
}
|
|
530
652
|
|
|
653
|
+
function clearCpuFeatureRecord() {
|
|
654
|
+
try {
|
|
655
|
+
fs.unlinkSync(getCpuFeatureCachePath())
|
|
656
|
+
} catch {
|
|
657
|
+
// Already gone, or unwritable; either way nothing pins baseline now.
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
531
661
|
function getDefaultTargetKey() {
|
|
532
662
|
const override = getTargetOverride()
|
|
533
663
|
if (override) {
|
|
@@ -580,6 +710,17 @@ function createLauncher(productConfig) {
|
|
|
580
710
|
|
|
581
711
|
const metadata = getCurrentMetadata()
|
|
582
712
|
if (metadata?.target && isTargetAllowedForThisMachine(metadata.target)) {
|
|
713
|
+
// An installed baseline keeps running (isTargetAllowedForThisMachine
|
|
714
|
+
// always allows it), but it is not a life sentence: once there is
|
|
715
|
+
// evidence the CPU has AVX2, the next download goes back to the optimized
|
|
716
|
+
// build. That is how a machine sent to baseline by a crash that was not a
|
|
717
|
+
// missing AVX2 re-probes on the next version.
|
|
718
|
+
if (
|
|
719
|
+
metadata.target === getBaselineFallbackTargetKey() &&
|
|
720
|
+
knownToHaveAvx2()
|
|
721
|
+
) {
|
|
722
|
+
return getDefaultTargetKey()
|
|
723
|
+
}
|
|
583
724
|
return metadata.target
|
|
584
725
|
}
|
|
585
726
|
|
|
@@ -1713,7 +1854,7 @@ function createLauncher(productConfig) {
|
|
|
1713
1854
|
// feature (see stderrReportsMissingCpuFeature), is proof this CPU cannot
|
|
1714
1855
|
// run this build; a Windows startup abort is a strong suspicion (see
|
|
1715
1856
|
// isStartupCpuFeatureCrash).
|
|
1716
|
-
|
|
1857
|
+
let confirmed =
|
|
1717
1858
|
isIllegalInstructionExit(code, signal) ||
|
|
1718
1859
|
stderrReportsMissingCpuFeature(code, signal, stderrTail)
|
|
1719
1860
|
if (!confirmed && !isStartupCpuFeatureCrash(code, signal, msAlive)) {
|
|
@@ -1736,16 +1877,35 @@ function createLauncher(productConfig) {
|
|
|
1736
1877
|
return false
|
|
1737
1878
|
}
|
|
1738
1879
|
|
|
1739
|
-
//
|
|
1740
|
-
//
|
|
1741
|
-
//
|
|
1742
|
-
//
|
|
1743
|
-
//
|
|
1744
|
-
//
|
|
1745
|
-
//
|
|
1746
|
-
if
|
|
1747
|
-
|
|
1748
|
-
|
|
1880
|
+
// An illegal instruction proves the binary used an instruction this
|
|
1881
|
+
// machine refused, not that the instruction was AVX2: a Bun or JIT bug
|
|
1882
|
+
// raises SIGILL too. So ask for evidence before pinning baseline, which
|
|
1883
|
+
// otherwise sticks for good.
|
|
1884
|
+
//
|
|
1885
|
+
// - The CPU lacks AVX2 (Bun's `Features: no_avx2`, or the OS probe):
|
|
1886
|
+
// record it. Persisting before the download is what caps the cost at
|
|
1887
|
+
// one crash: even if the download or the relaunch fails, we never
|
|
1888
|
+
// optimistically pick the AVX2 build again.
|
|
1889
|
+
// - The CPU has AVX2 (the OS probe, or avx2 in Bun's `CPU:` line): this
|
|
1890
|
+
// crash is something else. Run baseline for now, since it may still
|
|
1891
|
+
// start, and record the evidence so the next update goes back to the
|
|
1892
|
+
// optimized build (getDownloadTargetKey). At worst one crash per release.
|
|
1893
|
+
// - No evidence either way (Windows without Bun's report, an unreadable
|
|
1894
|
+
// /proc/cpuinfo, Rosetta): a confirmed crash is pinned as before.
|
|
1895
|
+
// A suspected crash records nothing: installing the baseline already keeps
|
|
1896
|
+
// this machine on it, and cpu-features.json should not assert a CPU
|
|
1897
|
+
// limitation we never observed.
|
|
1898
|
+
const verdict = crashAvx2Verdict(code, signal, stderrTail)
|
|
1899
|
+
if (verdict.avx2 === true) {
|
|
1900
|
+
writeCpuFeatureRecord(true, verdict.evidence)
|
|
1901
|
+
confirmed = false
|
|
1902
|
+
} else if (verdict.avx2 === false) {
|
|
1903
|
+
recordMachineLacksAvx2(verdict.evidence)
|
|
1904
|
+
confirmed = true
|
|
1905
|
+
} else if (confirmed) {
|
|
1906
|
+
recordMachineLacksAvx2('crash')
|
|
1907
|
+
}
|
|
1908
|
+
const cpuHasAvx2 = verdict.avx2 === true
|
|
1749
1909
|
|
|
1750
1910
|
let version = metadata?.version || null
|
|
1751
1911
|
let binaryChecksums = null
|
|
@@ -1763,9 +1923,11 @@ function createLauncher(productConfig) {
|
|
|
1763
1923
|
})
|
|
1764
1924
|
console.error('')
|
|
1765
1925
|
console.error(
|
|
1766
|
-
|
|
1767
|
-
? `${packageName}
|
|
1768
|
-
:
|
|
1926
|
+
cpuHasAvx2
|
|
1927
|
+
? `${packageName} crashed, but this CPU supports AVX2; using the older-CPU binary until the next update.`
|
|
1928
|
+
: confirmed
|
|
1929
|
+
? `${packageName} is switching to the older-CPU binary for this machine.`
|
|
1930
|
+
: `${packageName} crashed on startup; trying the older-CPU binary.`,
|
|
1769
1931
|
)
|
|
1770
1932
|
|
|
1771
1933
|
try {
|
|
@@ -1781,6 +1943,27 @@ function createLauncher(productConfig) {
|
|
|
1781
1943
|
return true
|
|
1782
1944
|
}
|
|
1783
1945
|
|
|
1946
|
+
/**
|
|
1947
|
+
* What the evidence says about AVX2 after a crash: `{ avx2, evidence }`,
|
|
1948
|
+
* avx2 null when nothing can tell. Bun's own verdict that AVX2 is missing
|
|
1949
|
+
* wins, then the OS probe, then avx2 in Bun's `CPU:` line.
|
|
1950
|
+
*/
|
|
1951
|
+
function crashAvx2Verdict(code, signal, stderrTail) {
|
|
1952
|
+
if (
|
|
1953
|
+
stderrTail &&
|
|
1954
|
+
!(code === 0 && !signal) &&
|
|
1955
|
+
BUN_NO_AVX2_FEATURE.test(sanitizeForReplay(stderrTail))
|
|
1956
|
+
) {
|
|
1957
|
+
return { avx2: false, evidence: 'bun-report' }
|
|
1958
|
+
}
|
|
1959
|
+
const probed = probeCpuAvx2()
|
|
1960
|
+
if (probed !== null) return { avx2: probed, evidence: cpuProbeSource() }
|
|
1961
|
+
if (stderrReportsAvx2Present(stderrTail)) {
|
|
1962
|
+
return { avx2: true, evidence: 'bun-report' }
|
|
1963
|
+
}
|
|
1964
|
+
return { avx2: null, evidence: null }
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1784
1967
|
function attachExitHandler(child, allowBaselineFallback = true) {
|
|
1785
1968
|
const exitListener = async (code, signal) => {
|
|
1786
1969
|
// A child we never watched (only reachable from a test) reports no age
|
|
@@ -1843,6 +2026,11 @@ function createLauncher(productConfig) {
|
|
|
1843
2026
|
detectMachineHasAvx2,
|
|
1844
2027
|
recordMachineLacksAvx2,
|
|
1845
2028
|
readCachedAvx2,
|
|
2029
|
+
readCpuFeatureRecord,
|
|
2030
|
+
parseCpuinfoAvx2,
|
|
2031
|
+
parseSysctlAvx2,
|
|
2032
|
+
stderrReportsAvx2Present,
|
|
2033
|
+
getDownloadTargetKey,
|
|
1846
2034
|
isIllegalInstructionExit,
|
|
1847
2035
|
isStartupCpuFeatureCrash,
|
|
1848
2036
|
stderrReportsMissingCpuFeature,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "freebuff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "The world's strongest free coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"provenance": false
|
|
42
42
|
},
|
|
43
43
|
"binaryChecksums": {
|
|
44
|
-
"linux-x64": "
|
|
45
|
-
"linux-x64-baseline": "
|
|
46
|
-
"linux-arm64": "
|
|
47
|
-
"darwin-x64": "
|
|
48
|
-
"darwin-x64-baseline": "
|
|
49
|
-
"darwin-arm64": "
|
|
50
|
-
"win32-x64": "
|
|
51
|
-
"win32-x64-baseline": "
|
|
44
|
+
"linux-x64": "02645c1354b0f72ae3ae6c138fd34b2f74afdf4552f17008713ff2f00f4af1ef",
|
|
45
|
+
"linux-x64-baseline": "5b758c46c3fb289fbd8ae04ce42c304a91e87a20ff5eab338e17cbc03e937831",
|
|
46
|
+
"linux-arm64": "9f40b2b4d76cacaa1f0d598cdfe3a5534e8f3e787803c604319dee569e08d923",
|
|
47
|
+
"darwin-x64": "ef2e71cd81b3166d6449e04221cc547a499bef90ba07c5087783650f63c72854",
|
|
48
|
+
"darwin-x64-baseline": "0d860e9b5cfb178f989fc5c23695d1c5f2328175683f2046775d59df77259f9c",
|
|
49
|
+
"darwin-arm64": "5c40f26c55f11b3395eae3d509ebbed9ffceaf1b06b929cfc90a4860cf64ac35",
|
|
50
|
+
"win32-x64": "826dd49fd9b412aaa87f023901a16872813832b84cda0bad0d97f14eb5ab63eb",
|
|
51
|
+
"win32-x64-baseline": "588cd19c2cdbfd9d323e1b1b737ae9e802404231293a2208a66494fc8ea93e5a"
|
|
52
52
|
}
|
|
53
53
|
}
|