pinokiod 8.0.45 → 8.0.46
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/kernel/gpu/amd_gfx_targets.json +3 -1
- package/kernel/gpu/amd_pci.js +184 -0
- package/kernel/gpu/amd_pci_targets.json +3243 -0
- package/kernel/sysinfo.js +6 -1
- package/kernel/vault/constants.js +2 -1
- package/kernel/vault/hash_worker.js +13 -1
- package/kernel/vault/index.js +71 -20
- package/kernel/vault/sweeper.js +15 -23
- package/package.json +2 -1
- package/script/update-amd-gfx-targets.js +289 -13
- package/server/lib/app_log_report.js +45 -3
- package/server/lib/log_redaction.js +26 -14
- package/server/public/nav.js +1 -1
- package/server/public/style.css +0 -1
- package/server/public/vault.js +24 -28
- package/server/views/vault.ejs +2 -0
- package/test/amd-gfx-generator.test.js +75 -0
- package/test/amd-gpu-target.test.js +108 -10
- package/test/amd-pci-target.test.js +159 -0
- package/test/app-log-report.test.js +67 -0
- package/test/header-collapse.test.js +10 -5
- package/test/sysinfo-gpu-driver.test.js +4 -0
- package/test/vault-engine.test.js +60 -1
- package/test/vault-sweep.test.js +50 -31
- package/test/vault-ui.test.js +97 -14
package/kernel/sysinfo.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const nvidia = require("./gpu/nvidia")
|
|
5
5
|
const amd = require("./gpu/amd")
|
|
6
|
+
const amd_pci = require("./gpu/amd_pci")
|
|
6
7
|
class Sysinfo {
|
|
7
8
|
async init(kernel) {
|
|
8
9
|
this.kernel = kernel
|
|
@@ -139,7 +140,11 @@ class Sysinfo {
|
|
|
139
140
|
if (detected.is_nvidia) {
|
|
140
141
|
return await nvidia.resolve_cuda_sm_target(detected.primaryController)
|
|
141
142
|
} else if (detected.is_amd) {
|
|
142
|
-
|
|
143
|
+
let target = await amd.resolve_gpu_target(
|
|
144
|
+
detected.gpu_model,
|
|
145
|
+
detected.cpu_brand || (() => this.cpu_brand())
|
|
146
|
+
)
|
|
147
|
+
return target || await amd_pci.resolve_gpu_target(detected.primaryController)
|
|
143
148
|
} else {
|
|
144
149
|
return null
|
|
145
150
|
}
|
|
@@ -6,20 +6,32 @@ const fs = require('fs')
|
|
|
6
6
|
// events. This changes only the read granularity; every byte still feeds the
|
|
7
7
|
// same sha256 digest, one file at a time.
|
|
8
8
|
const HASH_READ_SIZE = 1024 * 1024
|
|
9
|
+
const HASH_PROGRESS_INTERVAL_MS = 1000
|
|
9
10
|
|
|
10
11
|
parentPort.on('message', ({ id, filePath }) => {
|
|
11
12
|
const hash = crypto.createHash('sha256')
|
|
12
13
|
let size = 0
|
|
14
|
+
let lastProgressAt = 0
|
|
15
|
+
let complete = false
|
|
13
16
|
const flags = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0)
|
|
14
17
|
const stream = fs.createReadStream(filePath, { flags, highWaterMark: HASH_READ_SIZE })
|
|
15
18
|
stream.on('data', (chunk) => {
|
|
16
19
|
size += chunk.length
|
|
17
20
|
hash.update(chunk)
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
if (now - lastProgressAt >= HASH_PROGRESS_INTERVAL_MS) {
|
|
23
|
+
lastProgressAt = now
|
|
24
|
+
parentPort.postMessage({ id, bytes_read: size })
|
|
25
|
+
}
|
|
18
26
|
})
|
|
19
27
|
stream.on('error', (error) => {
|
|
20
|
-
|
|
28
|
+
if (complete) return
|
|
29
|
+
complete = true
|
|
30
|
+
parentPort.postMessage({ id, error: error.message, code: error.code })
|
|
21
31
|
})
|
|
22
32
|
stream.on('end', () => {
|
|
33
|
+
if (complete) return
|
|
34
|
+
complete = true
|
|
23
35
|
parentPort.postMessage({ id, hash: hash.digest('hex'), size })
|
|
24
36
|
})
|
|
25
37
|
})
|
package/kernel/vault/index.js
CHANGED
|
@@ -6,7 +6,10 @@ const Registry = require('./registry')
|
|
|
6
6
|
const Sweeper = require('./sweeper')
|
|
7
7
|
const { walkBatches, statMany } = require('./walker')
|
|
8
8
|
const { fileSnapshot, sameSnapshot, sameContentState } = require('./snapshot')
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
SIZE_THRESHOLD, TMP_SUFFIX, SHA256_RE, DIR_CONCURRENCY, STAT_CONCURRENCY,
|
|
11
|
+
HASH_INACTIVITY_MS
|
|
12
|
+
} = require('./constants')
|
|
10
13
|
|
|
11
14
|
// Shared model store engine (spec/requirements/shared-model-store.md).
|
|
12
15
|
// Store + registry + volume probe + hashing + adopt/convert/verify, plus the
|
|
@@ -81,6 +84,7 @@ class Vault {
|
|
|
81
84
|
this.workerSeq = 0
|
|
82
85
|
this.workerIdleTimer = null
|
|
83
86
|
this.workerIdleMs = 750
|
|
87
|
+
this.hashInactivityMs = HASH_INACTIVITY_MS
|
|
84
88
|
this.statConcurrency = STAT_CONCURRENCY
|
|
85
89
|
this.dirConcurrency = DIR_CONCURRENCY
|
|
86
90
|
this.sizeThreshold = SIZE_THRESHOLD
|
|
@@ -675,7 +679,21 @@ class Vault {
|
|
|
675
679
|
this.volumeModes.set(dev, mode)
|
|
676
680
|
return mode
|
|
677
681
|
}
|
|
678
|
-
|
|
682
|
+
failHashWorker(worker, error, terminate = false) {
|
|
683
|
+
if (this.worker === worker) {
|
|
684
|
+
if (this.workerIdleTimer) clearTimeout(this.workerIdleTimer)
|
|
685
|
+
this.workerIdleTimer = null
|
|
686
|
+
this.worker = null
|
|
687
|
+
}
|
|
688
|
+
for (const [id, job] of [...this.workerJobs]) {
|
|
689
|
+
if (job.worker !== worker) continue
|
|
690
|
+
clearTimeout(job.inactivityTimer)
|
|
691
|
+
this.workerJobs.delete(id)
|
|
692
|
+
job.reject(error)
|
|
693
|
+
}
|
|
694
|
+
if (terminate) worker.terminate().catch(() => {})
|
|
695
|
+
}
|
|
696
|
+
async hashFile(filePath, options = {}) {
|
|
679
697
|
if (this.workerIdleTimer) {
|
|
680
698
|
clearTimeout(this.workerIdleTimer)
|
|
681
699
|
this.workerIdleTimer = null
|
|
@@ -684,12 +702,24 @@ class Vault {
|
|
|
684
702
|
const worker = new Worker(path.resolve(__dirname, "hash_worker.js"))
|
|
685
703
|
this.worker = worker
|
|
686
704
|
worker.unref()
|
|
687
|
-
worker.on("message", ({ id, hash, size, error }) => {
|
|
705
|
+
worker.on("message", ({ id, hash, size, bytes_read: bytesRead, error, code }) => {
|
|
688
706
|
const job = this.workerJobs.get(id)
|
|
689
|
-
if (!job) return
|
|
707
|
+
if (!job || job.worker !== worker) return
|
|
708
|
+
if (Number.isFinite(bytesRead)) {
|
|
709
|
+
job.resetInactivity()
|
|
710
|
+
job.reportProgress(bytesRead)
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
clearTimeout(job.inactivityTimer)
|
|
690
714
|
this.workerJobs.delete(id)
|
|
691
|
-
if (error)
|
|
692
|
-
|
|
715
|
+
if (error) {
|
|
716
|
+
const failure = new Error(error)
|
|
717
|
+
if (code) failure.code = code
|
|
718
|
+
job.reject(failure)
|
|
719
|
+
} else {
|
|
720
|
+
job.reportProgress(size)
|
|
721
|
+
job.resolve({ hash, size })
|
|
722
|
+
}
|
|
693
723
|
// Keep the worker warm across the scan queue. Starting one worker per
|
|
694
724
|
// file costs ~30ms and adds up quickly on a first scan.
|
|
695
725
|
if (this.workerJobs.size === 0 && this.worker === worker) {
|
|
@@ -705,27 +735,46 @@ class Vault {
|
|
|
705
735
|
})
|
|
706
736
|
worker.on("error", (error) => {
|
|
707
737
|
if (this.worker !== worker) return
|
|
708
|
-
|
|
709
|
-
this.workerIdleTimer = null
|
|
710
|
-
for (const job of this.workerJobs.values()) job.reject(error)
|
|
711
|
-
this.workerJobs.clear()
|
|
712
|
-
this.worker = null
|
|
738
|
+
this.failHashWorker(worker, error)
|
|
713
739
|
})
|
|
714
740
|
worker.on("exit", (code) => {
|
|
715
741
|
if (this.worker !== worker) return
|
|
716
|
-
|
|
717
|
-
this.workerIdleTimer = null
|
|
718
|
-
for (const job of this.workerJobs.values()) {
|
|
719
|
-
job.reject(new Error(`hash worker exited with code ${code}`))
|
|
720
|
-
}
|
|
721
|
-
this.workerJobs.clear()
|
|
722
|
-
this.worker = null
|
|
742
|
+
this.failHashWorker(worker, new Error(`hash worker exited with code ${code}`))
|
|
723
743
|
})
|
|
724
744
|
}
|
|
745
|
+
const worker = this.worker
|
|
725
746
|
const id = ++this.workerSeq
|
|
726
747
|
return new Promise((resolve, reject) => {
|
|
727
|
-
|
|
728
|
-
|
|
748
|
+
const onProgress = typeof options.onProgress === "function" ? options.onProgress : null
|
|
749
|
+
const job = {
|
|
750
|
+
worker,
|
|
751
|
+
resolve,
|
|
752
|
+
reject,
|
|
753
|
+
reportProgress: (bytes) => {
|
|
754
|
+
if (!onProgress) return
|
|
755
|
+
// Progress is observational and must never fail a content hash.
|
|
756
|
+
try { onProgress(bytes) } catch (_) {}
|
|
757
|
+
},
|
|
758
|
+
inactivityTimer: null,
|
|
759
|
+
resetInactivity: null
|
|
760
|
+
}
|
|
761
|
+
job.resetInactivity = () => {
|
|
762
|
+
clearTimeout(job.inactivityTimer)
|
|
763
|
+
const inactivityMs = Math.max(1, Number(this.hashInactivityMs) || HASH_INACTIVITY_MS)
|
|
764
|
+
job.inactivityTimer = setTimeout(() => {
|
|
765
|
+
const failure = new Error(`Timed out while reading ${path.basename(filePath)}`)
|
|
766
|
+
failure.code = "ETIMEDOUT"
|
|
767
|
+
this.failHashWorker(worker, failure, true)
|
|
768
|
+
}, inactivityMs)
|
|
769
|
+
if (job.inactivityTimer.unref) job.inactivityTimer.unref()
|
|
770
|
+
}
|
|
771
|
+
this.workerJobs.set(id, job)
|
|
772
|
+
job.resetInactivity()
|
|
773
|
+
try {
|
|
774
|
+
worker.postMessage({ id, filePath })
|
|
775
|
+
} catch (error) {
|
|
776
|
+
this.failHashWorker(worker, error, true)
|
|
777
|
+
}
|
|
729
778
|
})
|
|
730
779
|
}
|
|
731
780
|
async refreshLinkSnapshots(hash, dev, ino) {
|
|
@@ -1798,6 +1847,8 @@ class Vault {
|
|
|
1798
1847
|
: sweeper.state
|
|
1799
1848
|
return Object.assign({}, state, {
|
|
1800
1849
|
current_file: sweeper.currentHash ? path.basename(sweeper.currentHash.path) : null,
|
|
1850
|
+
current_file_bytes: sweeper.currentHash ? sweeper.currentHash.bytes : null,
|
|
1851
|
+
current_file_size: sweeper.currentHash ? sweeper.currentHash.size : null,
|
|
1801
1852
|
pending,
|
|
1802
1853
|
error: this.scanError
|
|
1803
1854
|
})
|
package/kernel/vault/sweeper.js
CHANGED
|
@@ -54,12 +54,11 @@ class Sweeper {
|
|
|
54
54
|
idleState() {
|
|
55
55
|
return {
|
|
56
56
|
active: false, phase: "idle", dirs: 0, files: 0, bytes_total: 0,
|
|
57
|
-
counted_dirs: 0, counted_files: 0, total_files: null,
|
|
57
|
+
counted_dirs: 0, counted_files: 0, total_files: null,
|
|
58
58
|
home_bytes_total: 0, source_bytes: {}, source_files: {}, source_hash_failures: {}, scope_id: null,
|
|
59
59
|
candidates: 0, hashed: 0, hash_total: 0, hash_bytes: 0, queued: 0,
|
|
60
60
|
inode_reuses: 0, unstable_hashes: 0, hash_failures: 0,
|
|
61
61
|
inaccessible: 0, inaccessible_paths: [],
|
|
62
|
-
estimated_count_weight: 0.4, estimated_walk_weight: 0.5,
|
|
63
62
|
started: null, duration_ms: null,
|
|
64
63
|
count_duration_ms: null, walk_duration_ms: null, hash_wait_duration_ms: null,
|
|
65
64
|
hash_duration_ms: 0
|
|
@@ -84,24 +83,6 @@ class Sweeper {
|
|
|
84
83
|
if (!scopeId) this.vault.reconcileConfiguredSources()
|
|
85
84
|
const scanRoots = this.vault.scanRoots(scopeId)
|
|
86
85
|
if (!scanRoots.length) throw new Error("That scan location is no longer available.")
|
|
87
|
-
const previous = this.vault.registry.scanFor(scopeId)
|
|
88
|
-
if (previous) {
|
|
89
|
-
if (Number.isFinite(previous.files) && previous.files > 0) {
|
|
90
|
-
this.state.count_estimate_files = previous.files
|
|
91
|
-
}
|
|
92
|
-
const duration = Math.max(0, Number(previous.duration_ms) || 0)
|
|
93
|
-
const walkDuration = Math.max(0, Number(previous.walk_duration_ms) || 0)
|
|
94
|
-
const waitDuration = Math.max(0, Number(previous.hash_wait_duration_ms) || 0)
|
|
95
|
-
const countDuration = Number.isFinite(previous.count_duration_ms)
|
|
96
|
-
? Math.max(0, previous.count_duration_ms)
|
|
97
|
-
: Math.max(0, duration - walkDuration - waitDuration)
|
|
98
|
-
const finishDuration = Math.max(1, duration - countDuration - walkDuration)
|
|
99
|
-
const measuredDuration = countDuration + walkDuration + finishDuration
|
|
100
|
-
if (measuredDuration > 1) {
|
|
101
|
-
this.state.estimated_count_weight = countDuration / measuredDuration
|
|
102
|
-
this.state.estimated_walk_weight = walkDuration / measuredDuration
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
86
|
const countStarted = Date.now()
|
|
106
87
|
for (const source of scanRoots) await this.countFiles(source.root)
|
|
107
88
|
this.state.count_duration_ms = Date.now() - countStarted
|
|
@@ -411,9 +392,20 @@ class Sweeper {
|
|
|
411
392
|
async _hashJob(job) {
|
|
412
393
|
const primary = job.names[0]
|
|
413
394
|
const started = Date.now()
|
|
414
|
-
|
|
395
|
+
const currentHash = {
|
|
396
|
+
path: primary.filePath,
|
|
397
|
+
size: Math.max(0, Number(primary.expected.size) || 0),
|
|
398
|
+
bytes: 0
|
|
399
|
+
}
|
|
400
|
+
this.currentHash = currentHash
|
|
415
401
|
try {
|
|
416
|
-
const { hash, size } = await this.vault.hashFile(primary.filePath
|
|
402
|
+
const { hash, size } = await this.vault.hashFile(primary.filePath, {
|
|
403
|
+
onProgress: (bytes) => {
|
|
404
|
+
if (this.currentHash === currentHash) {
|
|
405
|
+
currentHash.bytes = Math.min(currentHash.size, Math.max(0, Number(bytes) || 0))
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
})
|
|
417
409
|
this.state.hashed += 1
|
|
418
410
|
this.state.hash_bytes += size || 0
|
|
419
411
|
const primaryStat = await fs.promises.lstat(primary.filePath)
|
|
@@ -460,7 +452,7 @@ class Sweeper {
|
|
|
460
452
|
if (job.inodeKey && this.hashJobsByIno.get(job.inodeKey) === job) {
|
|
461
453
|
this.hashJobsByIno.delete(job.inodeKey)
|
|
462
454
|
}
|
|
463
|
-
this.currentHash = null
|
|
455
|
+
if (this.currentHash === currentHash) this.currentHash = null
|
|
464
456
|
this.state.queued = Math.max(0, this.state.queued - 1)
|
|
465
457
|
if (this.state.queued < this.hashQueueLimit) {
|
|
466
458
|
for (const resolve of this.hashCapacityWaiters.splice(0)) resolve()
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinokiod",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.46",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"download_readme": "wget -O kernel/proto/PINOKIO.md https://raw.githubusercontent.com/pinokiocomputer/home/refs/heads/main/docs/README.md",
|
|
8
8
|
"update:amd-gfx-targets": "node script/update-amd-gfx-targets.js",
|
|
9
|
+
"check:amd-gfx-targets": "node script/update-amd-gfx-targets.js --check",
|
|
9
10
|
"start": "node script/index"
|
|
10
11
|
},
|
|
11
12
|
"author": "",
|
|
@@ -1,11 +1,65 @@
|
|
|
1
|
+
const crypto = require('node:crypto')
|
|
1
2
|
const fs = require('node:fs')
|
|
2
3
|
const https = require('node:https')
|
|
3
4
|
const path = require('node:path')
|
|
4
5
|
|
|
5
6
|
const { normalize_model } = require('../kernel/gpu/common')
|
|
6
7
|
|
|
7
|
-
const GPU_SPECS_URL = 'https://raw.githubusercontent.com/ROCm/ROCm/develop/docs/reference/gpu-arch-specs.rst'
|
|
8
8
|
const GENERATOR = 'script/update-amd-gfx-targets.js'
|
|
9
|
+
const AMD_VENDOR_ID = '1002'
|
|
10
|
+
|
|
11
|
+
// Update these revisions deliberately during release maintenance. Runtime GPU
|
|
12
|
+
// detection never fetches upstream data.
|
|
13
|
+
const ROCM_REVISION = '14f8138863403a26e0caef6671cfab9b09aa636e'
|
|
14
|
+
const MESA_REVISION = '3e2d8517b897026377267c09975db83525d2fc95'
|
|
15
|
+
const LLVM_REVISION = '2f3f97fb72606f51ece7d64d6a7a73c9eaad8978'
|
|
16
|
+
const LINUX_REVISION = '7d0a66e4bb9081d75c82ec4957c50034cb0ea449'
|
|
17
|
+
const LIBDRM_REVISION = 'f198c21dfcb89127083413dd4779a13b3f9a6507'
|
|
18
|
+
|
|
19
|
+
const SOURCES = {
|
|
20
|
+
rocm_specs: {
|
|
21
|
+
repository: 'https://github.com/ROCm/ROCm',
|
|
22
|
+
revision: ROCM_REVISION,
|
|
23
|
+
path: 'docs/reference/gpu-arch-specs.rst',
|
|
24
|
+
url: `https://raw.githubusercontent.com/ROCm/ROCm/${ROCM_REVISION}/docs/reference/gpu-arch-specs.rst`
|
|
25
|
+
},
|
|
26
|
+
mesa_pci_ids: {
|
|
27
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
28
|
+
revision: MESA_REVISION,
|
|
29
|
+
path: 'include/pci_ids/radeonsi_pci_ids.h',
|
|
30
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/include/pci_ids/radeonsi_pci_ids.h`
|
|
31
|
+
},
|
|
32
|
+
mesa_families: {
|
|
33
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
34
|
+
revision: MESA_REVISION,
|
|
35
|
+
path: 'src/amd/common/amd_family.c',
|
|
36
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/src/amd/common/amd_family.c`
|
|
37
|
+
},
|
|
38
|
+
mesa_family_names: {
|
|
39
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
40
|
+
revision: MESA_REVISION,
|
|
41
|
+
path: 'src/amd/common/amd_family.h',
|
|
42
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/src/amd/common/amd_family.h`
|
|
43
|
+
},
|
|
44
|
+
llvm_processors: {
|
|
45
|
+
repository: 'https://github.com/llvm/llvm-project',
|
|
46
|
+
revision: LLVM_REVISION,
|
|
47
|
+
path: 'llvm/lib/Target/AMDGPU/GCNProcessors.td',
|
|
48
|
+
url: `https://raw.githubusercontent.com/llvm/llvm-project/${LLVM_REVISION}/llvm/lib/Target/AMDGPU/GCNProcessors.td`
|
|
49
|
+
},
|
|
50
|
+
linux_pci_ids: {
|
|
51
|
+
repository: 'https://github.com/torvalds/linux',
|
|
52
|
+
revision: LINUX_REVISION,
|
|
53
|
+
path: 'drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c',
|
|
54
|
+
url: `https://raw.githubusercontent.com/torvalds/linux/${LINUX_REVISION}/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c`
|
|
55
|
+
},
|
|
56
|
+
libdrm_names: {
|
|
57
|
+
repository: 'https://gitlab.freedesktop.org/mesa/libdrm',
|
|
58
|
+
revision: LIBDRM_REVISION,
|
|
59
|
+
path: 'data/amdgpu.ids',
|
|
60
|
+
url: `https://gitlab.freedesktop.org/mesa/libdrm/-/raw/${LIBDRM_REVISION}/data/amdgpu.ids`
|
|
61
|
+
}
|
|
62
|
+
}
|
|
9
63
|
|
|
10
64
|
const repoRoot = path.resolve(__dirname, '..')
|
|
11
65
|
|
|
@@ -33,6 +87,10 @@ function fetchText(url) {
|
|
|
33
87
|
})
|
|
34
88
|
}
|
|
35
89
|
|
|
90
|
+
function sha256(value) {
|
|
91
|
+
return crypto.createHash('sha256').update(value).digest('hex')
|
|
92
|
+
}
|
|
93
|
+
|
|
36
94
|
function canonicalKey(value) {
|
|
37
95
|
let key = normalize_model(value)
|
|
38
96
|
let previous
|
|
@@ -95,25 +153,243 @@ function parseGpuSpecs(rst) {
|
|
|
95
153
|
return entries
|
|
96
154
|
}
|
|
97
155
|
|
|
156
|
+
function parseLlvmProcessorTargets(tablegen) {
|
|
157
|
+
const processors = {}
|
|
158
|
+
const processorPattern = /def\s*:\s*ProcessorModel<"([^"]+)"([\s\S]*?)>;/g
|
|
159
|
+
let processorMatch
|
|
160
|
+
while ((processorMatch = processorPattern.exec(tablegen))) {
|
|
161
|
+
const name = processorMatch[1].toLowerCase()
|
|
162
|
+
const version = processorMatch[2].match(
|
|
163
|
+
/FeatureISAVersion([0-9]+)_([0-9a-f]+)_([0-9a-f]+)\.Features/i
|
|
164
|
+
)
|
|
165
|
+
if (!version) continue
|
|
166
|
+
const target = /^gfx[0-9a-f]+$/.test(name)
|
|
167
|
+
? name
|
|
168
|
+
: `gfx${version[1]}${version[2]}${version[3]}`.toLowerCase()
|
|
169
|
+
processors[name] = target
|
|
170
|
+
}
|
|
171
|
+
return processors
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseMesaFamilyTargets(source, processorTargets) {
|
|
175
|
+
const start = source.indexOf('const char *ac_get_llvm_processor_name')
|
|
176
|
+
const end = source.indexOf('const char *ac_get_ip_type_string', start)
|
|
177
|
+
if (start < 0 || end < 0) {
|
|
178
|
+
throw new Error('Mesa ac_get_llvm_processor_name function was not found')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const body = source.slice(start, end)
|
|
182
|
+
const familyTargets = {}
|
|
183
|
+
const returnPattern = /((?:\s*case\s+CHIP_[A-Z0-9_]+:\s*)+)\s*return\s+"([^"]+)";/g
|
|
184
|
+
let returnMatch
|
|
185
|
+
while ((returnMatch = returnPattern.exec(body))) {
|
|
186
|
+
const processor = returnMatch[2].toLowerCase()
|
|
187
|
+
const target = /^gfx[0-9a-f]+$/.test(processor)
|
|
188
|
+
? processor
|
|
189
|
+
: processorTargets[processor]
|
|
190
|
+
if (!target || !/^gfx[0-9a-f]+$/.test(target)) continue
|
|
191
|
+
|
|
192
|
+
const familyPattern = /case\s+CHIP_([A-Z0-9_]+):/g
|
|
193
|
+
let familyMatch
|
|
194
|
+
while ((familyMatch = familyPattern.exec(returnMatch[1]))) {
|
|
195
|
+
familyTargets[familyMatch[1]] = target
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return familyTargets
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseMesaFamilyAliases(source) {
|
|
202
|
+
const aliases = {}
|
|
203
|
+
const aliasPattern = /CHIP_([A-Z0-9_]+),[^\n]*formerly\s+"([^"]+)"/gi
|
|
204
|
+
let aliasMatch
|
|
205
|
+
while ((aliasMatch = aliasPattern.exec(source))) {
|
|
206
|
+
const current = aliasMatch[1].toUpperCase()
|
|
207
|
+
const former = aliasMatch[2].toUpperCase().replace(/[^A-Z0-9]+/g, '_')
|
|
208
|
+
aliases[former] = current
|
|
209
|
+
}
|
|
210
|
+
return aliases
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function addPciEntry(entries, device, family, target) {
|
|
214
|
+
if (!target) return
|
|
215
|
+
const key = `${AMD_VENDOR_ID}:${device.toLowerCase()}`
|
|
216
|
+
const previous = entries[key]
|
|
217
|
+
if (previous && previous.target !== target) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Conflicting targets for ${key}: ${previous.target} (${previous.family}) and ${target} (${family})`
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
entries[key] = { target, family }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function parseMesaPciTargets(source, familyTargets, entries = {}) {
|
|
226
|
+
const pciPattern = /CHIPSET\(0x([0-9a-f]{4}),\s*([A-Z0-9_]+)\)/gi
|
|
227
|
+
let pciMatch
|
|
228
|
+
while ((pciMatch = pciPattern.exec(source))) {
|
|
229
|
+
const device = pciMatch[1].toLowerCase()
|
|
230
|
+
const family = pciMatch[2].toUpperCase()
|
|
231
|
+
addPciEntry(entries, device, family, familyTargets[family])
|
|
232
|
+
}
|
|
233
|
+
return entries
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseLinuxPciTargets(source, familyTargets, familyAliases, entries = {}) {
|
|
237
|
+
const pciPattern = /\{\s*0x1002,\s*0x([0-9a-f]{4}),[^\n]*\bCHIP_([A-Z0-9_]+)/gi
|
|
238
|
+
let pciMatch
|
|
239
|
+
while ((pciMatch = pciPattern.exec(source))) {
|
|
240
|
+
const device = pciMatch[1].toLowerCase()
|
|
241
|
+
const sourceFamily = pciMatch[2].toUpperCase()
|
|
242
|
+
const family = familyAliases[sourceFamily] || sourceFamily
|
|
243
|
+
addPciEntry(entries, device, family, familyTargets[family])
|
|
244
|
+
}
|
|
245
|
+
return entries
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseLibdrmRevisionTargets(source, modelTargets, pciTargets) {
|
|
249
|
+
const entries = {}
|
|
250
|
+
const rowPattern = /^([0-9a-f]{4}),\s*([0-9a-f]{2}),\s*(.+?)\s*$/gim
|
|
251
|
+
let rowMatch
|
|
252
|
+
while ((rowMatch = rowPattern.exec(source))) {
|
|
253
|
+
const device = rowMatch[1].toLowerCase()
|
|
254
|
+
const revision = rowMatch[2].toLowerCase()
|
|
255
|
+
const model = rowMatch[3].trim()
|
|
256
|
+
const modelTarget = modelTargets[canonicalKey(model)]
|
|
257
|
+
const pciTarget = pciTargets[`${AMD_VENDOR_ID}:${device}`]
|
|
258
|
+
if (pciTarget && modelTarget && pciTarget.target !== modelTarget) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Conflicting family/model targets for ${device}:${revision} (${model}): ` +
|
|
261
|
+
`${pciTarget.target} and ${modelTarget}`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
const target = pciTarget ? pciTarget.target : modelTarget
|
|
265
|
+
if (target) {
|
|
266
|
+
entries[`${AMD_VENDOR_ID}:${device}:${revision}`] = { target, model }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return entries
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function sourceMetadata(definition, contents) {
|
|
273
|
+
return {
|
|
274
|
+
repository: definition.repository,
|
|
275
|
+
revision: definition.revision,
|
|
276
|
+
path: definition.path,
|
|
277
|
+
url: definition.url,
|
|
278
|
+
sha256: sha256(contents)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function sortedObject(entries) {
|
|
283
|
+
return Object.fromEntries(Object.entries(entries).sort(([a], [b]) => a.localeCompare(b)))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function buildOutputs(contents) {
|
|
287
|
+
const modelTargets = parseGpuSpecs(contents.rocm_specs)
|
|
288
|
+
const processorTargets = parseLlvmProcessorTargets(contents.llvm_processors)
|
|
289
|
+
const familyTargets = parseMesaFamilyTargets(contents.mesa_families, processorTargets)
|
|
290
|
+
const familyAliases = parseMesaFamilyAliases(contents.mesa_family_names)
|
|
291
|
+
const pciTargets = parseMesaPciTargets(contents.mesa_pci_ids, familyTargets)
|
|
292
|
+
parseLinuxPciTargets(
|
|
293
|
+
contents.linux_pci_ids,
|
|
294
|
+
familyTargets,
|
|
295
|
+
familyAliases,
|
|
296
|
+
pciTargets
|
|
297
|
+
)
|
|
298
|
+
const revisionTargets = parseLibdrmRevisionTargets(
|
|
299
|
+
contents.libdrm_names,
|
|
300
|
+
modelTargets,
|
|
301
|
+
pciTargets
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const required = {
|
|
305
|
+
'1002:731f': 'gfx1010',
|
|
306
|
+
'1002:7340': 'gfx1012',
|
|
307
|
+
'1002:73bf': 'gfx1030',
|
|
308
|
+
'1002:73ff': 'gfx1032'
|
|
309
|
+
}
|
|
310
|
+
for (const [key, target] of Object.entries(required)) {
|
|
311
|
+
if (!pciTargets[key] || pciTargets[key].target !== target) {
|
|
312
|
+
throw new Error(`Pinned sources did not produce required mapping ${key} -> ${target}`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
models: {
|
|
318
|
+
source: SOURCES.rocm_specs.url,
|
|
319
|
+
source_revision: SOURCES.rocm_specs.revision,
|
|
320
|
+
source_sha256: sha256(contents.rocm_specs),
|
|
321
|
+
generated_by: GENERATOR,
|
|
322
|
+
entries: sortedObject(modelTargets)
|
|
323
|
+
},
|
|
324
|
+
pci: {
|
|
325
|
+
generated_by: GENERATOR,
|
|
326
|
+
sources: [
|
|
327
|
+
sourceMetadata(SOURCES.mesa_pci_ids, contents.mesa_pci_ids),
|
|
328
|
+
sourceMetadata(SOURCES.mesa_families, contents.mesa_families),
|
|
329
|
+
sourceMetadata(SOURCES.mesa_family_names, contents.mesa_family_names),
|
|
330
|
+
sourceMetadata(SOURCES.llvm_processors, contents.llvm_processors),
|
|
331
|
+
sourceMetadata(SOURCES.linux_pci_ids, contents.linux_pci_ids),
|
|
332
|
+
sourceMetadata(SOURCES.libdrm_names, contents.libdrm_names)
|
|
333
|
+
],
|
|
334
|
+
revision_entries: sortedObject(revisionTargets),
|
|
335
|
+
entries: sortedObject(pciTargets)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
98
340
|
function writeJson(relativePath, data) {
|
|
99
341
|
const file = path.join(repoRoot, relativePath)
|
|
100
342
|
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`)
|
|
101
343
|
}
|
|
102
344
|
|
|
345
|
+
function checkJson(relativePath, data) {
|
|
346
|
+
const file = path.join(repoRoot, relativePath)
|
|
347
|
+
const expected = `${JSON.stringify(data, null, 2)}\n`
|
|
348
|
+
if (fs.readFileSync(file, 'utf8') !== expected) {
|
|
349
|
+
throw new Error(`${relativePath} is stale; run npm run update:amd-gfx-targets`)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
103
353
|
async function main() {
|
|
104
|
-
const
|
|
105
|
-
|
|
354
|
+
const contents = Object.fromEntries(await Promise.all(
|
|
355
|
+
Object.entries(SOURCES).map(async ([name, source]) => [name, await fetchText(source.url)])
|
|
356
|
+
))
|
|
357
|
+
const outputs = buildOutputs(contents)
|
|
358
|
+
const files = [
|
|
359
|
+
['kernel/gpu/amd_gfx_targets.json', outputs.models],
|
|
360
|
+
['kernel/gpu/amd_pci_targets.json', outputs.pci]
|
|
361
|
+
]
|
|
106
362
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
363
|
+
if (process.argv.includes('--check')) {
|
|
364
|
+
for (const [file, data] of files) checkJson(file, data)
|
|
365
|
+
console.log('AMD gfx target data is up to date')
|
|
366
|
+
return
|
|
367
|
+
}
|
|
112
368
|
|
|
113
|
-
|
|
369
|
+
for (const [file, data] of files) writeJson(file, data)
|
|
370
|
+
|
|
371
|
+
console.log(
|
|
372
|
+
`Wrote ${Object.keys(outputs.models.entries).length} AMD model targets and ` +
|
|
373
|
+
`${Object.keys(outputs.pci.entries).length} AMD PCI targets`
|
|
374
|
+
)
|
|
114
375
|
}
|
|
115
376
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
377
|
+
if (require.main === module) {
|
|
378
|
+
main().catch((error) => {
|
|
379
|
+
console.error(error)
|
|
380
|
+
process.exit(1)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = {
|
|
385
|
+
SOURCES,
|
|
386
|
+
buildOutputs,
|
|
387
|
+
checkJson,
|
|
388
|
+
parseGpuSpecs,
|
|
389
|
+
parseLibdrmRevisionTargets,
|
|
390
|
+
parseLinuxPciTargets,
|
|
391
|
+
parseLlvmProcessorTargets,
|
|
392
|
+
parseMesaFamilyAliases,
|
|
393
|
+
parseMesaFamilyTargets,
|
|
394
|
+
parseMesaPciTargets
|
|
395
|
+
}
|