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.
Files changed (2) hide show
  1. package/launcher.js +225 -37
  2. 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
- function linuxCpuHasAvx2() {
446
- try {
447
- return /\bavx2\b/i.test(fs.readFileSync('/proc/cpuinfo', 'utf8'))
448
- } catch {
449
- return true
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 illegal-instruction crash outranks everything below it: the
468
- // binary actually failed on this machine, which beats any inference we
469
- // could make about the CPU.
470
- const recorded = readCachedAvx2()
471
- if (recorded !== null) {
472
- return recorded
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 linuxCpuHasAvx2()
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
- // Called from the illegal-instruction fallback. Persisting here is what keeps
496
- // the optimistic assumption above from costing more than a single crash — and
497
- // it is deliberately separate from the metadata target, so a lost or rewritten
498
- // metadata file can't resurrect the AVX2 build on a CPU that can't run it.
499
- function recordMachineLacksAvx2() {
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
- writeCachedAvx2(false)
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
- function readCachedAvx2() {
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
- return typeof cache.avx2 === 'boolean' ? cache.avx2 : null
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 writeCachedAvx2(value) {
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
- const confirmed =
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
- // Only a confirmed illegal instruction gets written down. Persisting it
1740
- // before the download is what caps the cost at one crash: even if the
1741
- // download or the relaunch fails, we never optimistically pick the AVX2
1742
- // build again. A suspected crash records nothing — installing the baseline
1743
- // already keeps this machine on it, so a guess that turns out to be wrong
1744
- // costs the slower build instead of leaving cpu-features.json asserting a
1745
- // CPU limitation we never observed.
1746
- if (confirmed) {
1747
- recordMachineLacksAvx2()
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
- confirmed
1767
- ? `${packageName} is switching to the older-CPU binary for this machine.`
1768
- : `${packageName} crashed on startup; trying the older-CPU binary.`,
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.2",
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": "2a149ec595d8f27ec8654b5ab072c22020d5e4f4bd0a0d2c0a1a334b6a063f71",
45
- "linux-x64-baseline": "22b13efa9eb52d9a074ed2e67feb6474a551796e22d0fa0e7a9a3081943ef16b",
46
- "linux-arm64": "ef43f44264cae55d86f5d0df45f4c948543d0bf92525c1b296b910badc2f0c71",
47
- "darwin-x64": "cba39ca908625a8b43335942879fa6c9bd91a2a623f4a26d8a9e0d3e39b080e3",
48
- "darwin-x64-baseline": "8be9ca67c635dd4bac21d1e726353c24df5d421d598a2c94fff43540a8062369",
49
- "darwin-arm64": "529b4b2352690c9d3cfc8436e8b15649bc043eb58e07781d6986c6f0f2577f4e",
50
- "win32-x64": "fc97aa41e2a59304afa49e27bb2f22aa8d7b47f39d182a3abd99c5ca33c5ddd2",
51
- "win32-x64-baseline": "d280003c00ce1d91b19cbeee3ead6fee516f9aba665c175a8e19553d0a6dfe8c"
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
  }