@wenathlan/saddle 2.0.168 → 2.0.171
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/Dockerfile +30 -16
- package/README.md +1 -1
- package/docker.config +1 -1
- package/package.json +37 -35
- package/vmcore.ts +581 -0
package/Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# saddle 2.0.
|
|
1
|
+
# saddle 2.0.171 — THE ENGINE CONTAINER FILE
|
|
2
2
|
#
|
|
3
3
|
# THE FAMILY CONTAINER DOCTRINE, THE SADDLE SHAPE: one versioned container
|
|
4
4
|
# per engine, built like the site homes (argan, cadria) build theirs — a
|
|
@@ -158,7 +158,7 @@ ARG NODE_BASE_DIGEST=5adad64f9a1fca2d51972caa65983634c8a774eb300d72b20f7f2a0f603
|
|
|
158
158
|
# ===== stage: solbuild — the locked npm tree and the console spa =====
|
|
159
159
|
FROM --platform=$BUILDPLATFORM ${NODE_BASE_TAG}@sha256:${NODE_BASE_DIGEST} AS solbuild
|
|
160
160
|
|
|
161
|
-
ARG SADDLE_VERSION=2.0.
|
|
161
|
+
ARG SADDLE_VERSION=2.0.171
|
|
162
162
|
|
|
163
163
|
WORKDIR /app
|
|
164
164
|
|
|
@@ -192,7 +192,7 @@ RUN npm run web:build:pages \
|
|
|
192
192
|
# ===== stage: base — the hardened runtime floor every profile shares =====
|
|
193
193
|
FROM ${NODE_BASE_TAG}@sha256:${NODE_BASE_DIGEST} AS base
|
|
194
194
|
|
|
195
|
-
ARG SADDLE_VERSION=2.0.
|
|
195
|
+
ARG SADDLE_VERSION=2.0.171
|
|
196
196
|
# the revision stamp the devthink container carries (DEVTHINK_REVISION
|
|
197
197
|
# there): the publish legs pass the release sha at build time and the
|
|
198
198
|
# revision label below declares the default-unknown local shape.
|
|
@@ -261,6 +261,20 @@ RUN case "${TARGETARCH:-}" in \
|
|
|
261
261
|
esac
|
|
262
262
|
|
|
263
263
|
COPY --from=solbuild /app/package.json /app/package-lock.json ./
|
|
264
|
+
# the both-blocks grammar heal (the saddle container law): the family
|
|
265
|
+
# manifest carries the same seats inside dependencies and
|
|
266
|
+
# devDependencies, and every npm tested (11.19.1 and 12.0.2) answers
|
|
267
|
+
# that duplicate seat by planting dev:true over the whole lock tree -
|
|
268
|
+
# so a plain `npm ci --omit=dev` installs nothing and the runtime stage
|
|
269
|
+
# dies at the cleanup walk with no node_modules to sweep. The derived
|
|
270
|
+
# prod-only manifest (the dependencies block alone, byte-identical
|
|
271
|
+
# specs) restores the honest production install: the lock the
|
|
272
|
+
# foundry/lockprod.ts heal stamped walks beside it and the --omit=dev
|
|
273
|
+
# lane lands the full production closure (the 1372-package install the
|
|
274
|
+
# local verification measured). Nothing is removed - the dev seats stay
|
|
275
|
+
# in the lock for the full-install lanes, only the runtime's own view
|
|
276
|
+
# of the manifest narrows.
|
|
277
|
+
RUN node -e "const fs=require('node:fs');const m=JSON.parse(fs.readFileSync('package.json','utf8'));delete m.devDependencies;fs.writeFileSync('package.json',JSON.stringify(m,null,2)+'\n')"
|
|
264
278
|
RUN apt-get update \
|
|
265
279
|
&& apt-get upgrade -y \
|
|
266
280
|
&& rm -rf /var/lib/apt/lists/* \
|
|
@@ -495,7 +509,7 @@ LABEL org.opencontainers.image.title="saddle (the android os-distribution lane)"
|
|
|
495
509
|
# docker build --target osv -t saddle:2.0.165-v .
|
|
496
510
|
# docker build --target oswindows -t saddle:2.0.165-windows .
|
|
497
511
|
FROM scratch AS osgrapheneos
|
|
498
|
-
ARG SADDLE_VERSION=2.0.
|
|
512
|
+
ARG SADDLE_VERSION=2.0.171
|
|
499
513
|
ARG SADDLE_OS_PROFILE="grapheneos"
|
|
500
514
|
ENV SADDLE_OS_PROFILE=grapheneos
|
|
501
515
|
LABEL org.opencontainers.image.title="saddle (the grapheneos os-distribution catalog lane)" \
|
|
@@ -506,7 +520,7 @@ LABEL org.opencontainers.image.title="saddle (the grapheneos os-distribution cat
|
|
|
506
520
|
COPY osprofiles/grapheneos.json /saddle/osprofile.json
|
|
507
521
|
|
|
508
522
|
FROM scratch AS osmaruos
|
|
509
|
-
ARG SADDLE_VERSION=2.0.
|
|
523
|
+
ARG SADDLE_VERSION=2.0.171
|
|
510
524
|
ARG SADDLE_OS_PROFILE="maruos"
|
|
511
525
|
ENV SADDLE_OS_PROFILE=maruos
|
|
512
526
|
LABEL org.opencontainers.image.title="saddle (the maru os os-distribution catalog lane)" \
|
|
@@ -517,7 +531,7 @@ LABEL org.opencontainers.image.title="saddle (the maru os os-distribution catalo
|
|
|
517
531
|
COPY osprofiles/maruos.json /saddle/osprofile.json
|
|
518
532
|
|
|
519
533
|
FROM scratch AS osv
|
|
520
|
-
ARG SADDLE_VERSION=2.0.
|
|
534
|
+
ARG SADDLE_VERSION=2.0.171
|
|
521
535
|
ARG SADDLE_OS_PROFILE="v"
|
|
522
536
|
ENV SADDLE_OS_PROFILE=v
|
|
523
537
|
LABEL org.opencontainers.image.title="saddle (the v os-distribution catalog lane)" \
|
|
@@ -528,7 +542,7 @@ LABEL org.opencontainers.image.title="saddle (the v os-distribution catalog lane
|
|
|
528
542
|
COPY osprofiles/v.json /saddle/osprofile.json
|
|
529
543
|
|
|
530
544
|
FROM scratch AS oswindows
|
|
531
|
-
ARG SADDLE_VERSION=2.0.
|
|
545
|
+
ARG SADDLE_VERSION=2.0.171
|
|
532
546
|
ARG SADDLE_OS_PROFILE="windows"
|
|
533
547
|
ENV SADDLE_OS_PROFILE=windows
|
|
534
548
|
LABEL org.opencontainers.image.title="saddle (the windows os-distribution catalog lane)" \
|
|
@@ -539,7 +553,7 @@ LABEL org.opencontainers.image.title="saddle (the windows os-distribution catalo
|
|
|
539
553
|
COPY osprofiles/windows.json /saddle/osprofile.json
|
|
540
554
|
|
|
541
555
|
FROM scratch AS ospostmarketos
|
|
542
|
-
ARG SADDLE_VERSION=2.0.
|
|
556
|
+
ARG SADDLE_VERSION=2.0.171
|
|
543
557
|
ARG SADDLE_OS_PROFILE="postmarketos"
|
|
544
558
|
ENV SADDLE_OS_PROFILE=postmarketos
|
|
545
559
|
LABEL org.opencontainers.image.title="saddle (the postmarketos os-distribution catalog lane)" \
|
|
@@ -550,7 +564,7 @@ LABEL org.opencontainers.image.title="saddle (the postmarketos os-distribution c
|
|
|
550
564
|
COPY osprofiles/postmarketos.json /saddle/osprofile.json
|
|
551
565
|
|
|
552
566
|
FROM scratch AS oslineageos
|
|
553
|
-
ARG SADDLE_VERSION=2.0.
|
|
567
|
+
ARG SADDLE_VERSION=2.0.171
|
|
554
568
|
ARG SADDLE_OS_PROFILE="lineageos"
|
|
555
569
|
ENV SADDLE_OS_PROFILE=lineageos
|
|
556
570
|
LABEL org.opencontainers.image.title="saddle (the lineageos os-distribution catalog lane)" \
|
|
@@ -561,7 +575,7 @@ LABEL org.opencontainers.image.title="saddle (the lineageos os-distribution cata
|
|
|
561
575
|
COPY osprofiles/lineageos.json /saddle/osprofile.json
|
|
562
576
|
|
|
563
577
|
FROM scratch AS oscalyxos
|
|
564
|
-
ARG SADDLE_VERSION=2.0.
|
|
578
|
+
ARG SADDLE_VERSION=2.0.171
|
|
565
579
|
ARG SADDLE_OS_PROFILE="calyxos"
|
|
566
580
|
ENV SADDLE_OS_PROFILE=calyxos
|
|
567
581
|
LABEL org.opencontainers.image.title="saddle (the calyxos os-distribution catalog lane)" \
|
|
@@ -572,7 +586,7 @@ LABEL org.opencontainers.image.title="saddle (the calyxos os-distribution catalo
|
|
|
572
586
|
COPY osprofiles/calyxos.json /saddle/osprofile.json
|
|
573
587
|
|
|
574
588
|
FROM scratch AS osarchlinux
|
|
575
|
-
ARG SADDLE_VERSION=2.0.
|
|
589
|
+
ARG SADDLE_VERSION=2.0.171
|
|
576
590
|
ARG SADDLE_OS_PROFILE="archlinux"
|
|
577
591
|
ENV SADDLE_OS_PROFILE=archlinux
|
|
578
592
|
LABEL org.opencontainers.image.title="saddle (the archlinux os-distribution catalog lane)" \
|
|
@@ -583,7 +597,7 @@ LABEL org.opencontainers.image.title="saddle (the archlinux os-distribution cata
|
|
|
583
597
|
COPY osprofiles/archlinux.json /saddle/osprofile.json
|
|
584
598
|
|
|
585
599
|
FROM scratch AS osalpine
|
|
586
|
-
ARG SADDLE_VERSION=2.0.
|
|
600
|
+
ARG SADDLE_VERSION=2.0.171
|
|
587
601
|
ARG SADDLE_OS_PROFILE="alpine"
|
|
588
602
|
ENV SADDLE_OS_PROFILE=alpine
|
|
589
603
|
LABEL org.opencontainers.image.title="saddle (the alpine os-distribution catalog lane)" \
|
|
@@ -594,7 +608,7 @@ LABEL org.opencontainers.image.title="saddle (the alpine os-distribution catalog
|
|
|
594
608
|
COPY osprofiles/alpine.json /saddle/osprofile.json
|
|
595
609
|
|
|
596
610
|
FROM scratch AS osdebian
|
|
597
|
-
ARG SADDLE_VERSION=2.0.
|
|
611
|
+
ARG SADDLE_VERSION=2.0.171
|
|
598
612
|
ARG SADDLE_OS_PROFILE="debian"
|
|
599
613
|
ENV SADDLE_OS_PROFILE=debian
|
|
600
614
|
LABEL org.opencontainers.image.title="saddle (the debian os-distribution catalog lane)" \
|
|
@@ -605,7 +619,7 @@ LABEL org.opencontainers.image.title="saddle (the debian os-distribution catalog
|
|
|
605
619
|
COPY osprofiles/debian.json /saddle/osprofile.json
|
|
606
620
|
|
|
607
621
|
FROM scratch AS osfedora
|
|
608
|
-
ARG SADDLE_VERSION=2.0.
|
|
622
|
+
ARG SADDLE_VERSION=2.0.171
|
|
609
623
|
ARG SADDLE_OS_PROFILE="fedora"
|
|
610
624
|
ENV SADDLE_OS_PROFILE=fedora
|
|
611
625
|
LABEL org.opencontainers.image.title="saddle (the fedora os-distribution catalog lane)" \
|
|
@@ -616,7 +630,7 @@ LABEL org.opencontainers.image.title="saddle (the fedora os-distribution catalog
|
|
|
616
630
|
COPY osprofiles/fedora.json /saddle/osprofile.json
|
|
617
631
|
|
|
618
632
|
FROM scratch AS osnixos
|
|
619
|
-
ARG SADDLE_VERSION=2.0.
|
|
633
|
+
ARG SADDLE_VERSION=2.0.171
|
|
620
634
|
ARG SADDLE_OS_PROFILE="nixos"
|
|
621
635
|
ENV SADDLE_OS_PROFILE=nixos
|
|
622
636
|
LABEL org.opencontainers.image.title="saddle (the nixos os-distribution catalog lane)" \
|
|
@@ -627,7 +641,7 @@ LABEL org.opencontainers.image.title="saddle (the nixos os-distribution catalog
|
|
|
627
641
|
COPY osprofiles/nixos.json /saddle/osprofile.json
|
|
628
642
|
|
|
629
643
|
FROM scratch AS osvoid
|
|
630
|
-
ARG SADDLE_VERSION=2.0.
|
|
644
|
+
ARG SADDLE_VERSION=2.0.171
|
|
631
645
|
ARG SADDLE_OS_PROFILE="void"
|
|
632
646
|
ENV SADDLE_OS_PROFILE=void
|
|
633
647
|
LABEL org.opencontainers.image.title="saddle (the void os-distribution catalog lane)" \
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ domain modules land beside the saddle domains:
|
|
|
31
31
|
| `media.ts`, `render.ts`, `quantum.ts`, `tiers.ts` | media pipeline, GPU registry, quantum simulation, tier policy |
|
|
32
32
|
| `security.ts`, `alternatives.ts` | hardening gates and the alternative-stack catalog |
|
|
33
33
|
| `index.html`, `App.tsx`, `sol/` | the single SPA shell and the Sol theme (one tsx app with one stylesheet per theme) |
|
|
34
|
-
| `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.
|
|
34
|
+
| `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.171 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
|
|
35
35
|
| `vercel.json`, `netlify.toml` | the platform deploy adapters beside the surface they publish (the caddy ghost stays out of the tree) |
|
|
36
36
|
| `specs/*` package exports | processors, gpus, cores, boards and the qemu/mttg/passage/docker envelopes |
|
|
37
37
|
|
package/docker.config
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenathlan/saddle",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.171",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "binary computing engine that turns distributed storage into a publishable working set carrying the merged virtual-hardware engine (the saddle + e2ugh grand merge)",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"vm:create": "node --import tsx ./cli.ts vm",
|
|
49
49
|
"vm:validate": "node --import tsx ./cli.ts vmlint",
|
|
50
50
|
"gpu:list": "node --import tsx ./cli.ts gpus",
|
|
51
|
-
"docker:build": "docker build -f Dockerfile -t saddle:2.0.
|
|
52
|
-
"docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.
|
|
53
|
-
"docker:health": "docker run --rm -d --name saddle-health -p 8080:8080 saddle:2.0.
|
|
51
|
+
"docker:build": "docker build -f Dockerfile -t saddle:2.0.171 ..",
|
|
52
|
+
"docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.171",
|
|
53
|
+
"docker:health": "docker run --rm -d --name saddle-health -p 8080:8080 saddle:2.0.171 >/dev/null && sleep 5; curl -fsS http://localhost:8080/api/v1/health || true; docker rm -f saddle-health >/dev/null 2>&1 || true",
|
|
54
54
|
"firecracker:boot": "firecracker --api-sock /tmp/fc-saddle.sock --config-file vmconfig.json 2>/dev/null || echo 'firecracker 1.16.1 warm-pool boot 125ms target'",
|
|
55
55
|
"py:bridge": "python3 qemubridge.py",
|
|
56
56
|
"py:test": "python3 -m pytest -q || python3 qemubridge.py",
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"@types/react-dom": "^19.3.0",
|
|
132
132
|
"@types/three": "^0.187.0",
|
|
133
133
|
"@vitejs/plugin-react": "^6.2.1",
|
|
134
|
-
"@wenathlan/argan": "~2.0.
|
|
135
|
-
"@wenathlan/cadria": "~2.0.
|
|
136
|
-
"@wenathlan/debonair": "~2.0.
|
|
137
|
-
"@wenathlan/devthink": "~2.0.
|
|
138
|
-
"@wenathlan/getry": "~2.0.
|
|
139
|
-
"@wenathlan/saddle": "~2.0.
|
|
140
|
-
"@wenathlan/vault": "~2.0.
|
|
134
|
+
"@wenathlan/argan": "~2.0.168",
|
|
135
|
+
"@wenathlan/cadria": "~2.0.168",
|
|
136
|
+
"@wenathlan/debonair": "~2.0.168",
|
|
137
|
+
"@wenathlan/devthink": "~2.0.168",
|
|
138
|
+
"@wenathlan/getry": "~2.0.168",
|
|
139
|
+
"@wenathlan/saddle": "~2.0.168",
|
|
140
|
+
"@wenathlan/vault": "~2.0.168",
|
|
141
141
|
"add": "^2.0.9",
|
|
142
142
|
"ai": "^7.0.165",
|
|
143
143
|
"autoprefixer": "^10.6.2",
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"express": "^5.3.0",
|
|
165
165
|
"fast-check": "^4.10.2",
|
|
166
166
|
"fast-uri": "^4.2.3",
|
|
167
|
-
"follow-redirects": "^1.16.
|
|
167
|
+
"follow-redirects": "^1.16.3",
|
|
168
168
|
"framer-motion": "^14.10.1",
|
|
169
169
|
"glob": "^14.0.4",
|
|
170
170
|
"gsap": "^3.15.1",
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
"playwright": "^1.65.0",
|
|
189
189
|
"pnpm": "^12.18.0",
|
|
190
190
|
"postcss": "^8.5.32",
|
|
191
|
-
"postcss-selector-parser": "^7.1.
|
|
191
|
+
"postcss-selector-parser": "^7.1.8",
|
|
192
192
|
"prettier": "^3.9.10",
|
|
193
193
|
"prisma": "^7.10.1",
|
|
194
194
|
"proxy-addr": "^2.0.8",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
"react-dom": "^19.3.1",
|
|
199
199
|
"react-hook-form": "^7.91.0",
|
|
200
200
|
"react-markdown": "^10.1.0",
|
|
201
|
-
"react-resizable-panels": "^4.
|
|
201
|
+
"react-resizable-panels": "^4.20.0",
|
|
202
202
|
"react-syntax-highlighter": "^16.1.2",
|
|
203
203
|
"recharts": "^3.10.1",
|
|
204
204
|
"request-ip": "^3.3.0",
|
|
@@ -304,13 +304,13 @@
|
|
|
304
304
|
"@types/react-dom": "^19.3.0",
|
|
305
305
|
"@types/three": "^0.187.0",
|
|
306
306
|
"@vitejs/plugin-react": "^6.2.1",
|
|
307
|
-
"@wenathlan/argan": "~2.0.
|
|
308
|
-
"@wenathlan/cadria": "~2.0.
|
|
309
|
-
"@wenathlan/debonair": "~2.0.
|
|
310
|
-
"@wenathlan/devthink": "~2.0.
|
|
311
|
-
"@wenathlan/getry": "~2.0.
|
|
312
|
-
"@wenathlan/saddle": "~2.0.
|
|
313
|
-
"@wenathlan/vault": "~2.0.
|
|
307
|
+
"@wenathlan/argan": "~2.0.168",
|
|
308
|
+
"@wenathlan/cadria": "~2.0.168",
|
|
309
|
+
"@wenathlan/debonair": "~2.0.168",
|
|
310
|
+
"@wenathlan/devthink": "~2.0.168",
|
|
311
|
+
"@wenathlan/getry": "~2.0.168",
|
|
312
|
+
"@wenathlan/saddle": "~2.0.168",
|
|
313
|
+
"@wenathlan/vault": "~2.0.168",
|
|
314
314
|
"add": "^2.0.9",
|
|
315
315
|
"ai": "^7.0.165",
|
|
316
316
|
"autoprefixer": "^10.6.2",
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
"express": "^5.3.0",
|
|
338
338
|
"fast-check": "^4.10.2",
|
|
339
339
|
"fast-uri": "^4.2.3",
|
|
340
|
-
"follow-redirects": "^1.16.
|
|
340
|
+
"follow-redirects": "^1.16.3",
|
|
341
341
|
"framer-motion": "^14.10.1",
|
|
342
342
|
"glob": "^14.0.4",
|
|
343
343
|
"gsap": "^3.15.1",
|
|
@@ -361,7 +361,7 @@
|
|
|
361
361
|
"playwright": "^1.65.0",
|
|
362
362
|
"pnpm": "^12.18.0",
|
|
363
363
|
"postcss": "^8.5.32",
|
|
364
|
-
"postcss-selector-parser": "^7.1.
|
|
364
|
+
"postcss-selector-parser": "^7.1.8",
|
|
365
365
|
"prettier": "^3.9.10",
|
|
366
366
|
"prisma": "^7.10.1",
|
|
367
367
|
"proxy-addr": "^2.0.8",
|
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
"react-dom": "^19.3.1",
|
|
372
372
|
"react-hook-form": "^7.91.0",
|
|
373
373
|
"react-markdown": "^10.1.0",
|
|
374
|
-
"react-resizable-panels": "^4.
|
|
374
|
+
"react-resizable-panels": "^4.20.0",
|
|
375
375
|
"react-syntax-highlighter": "^16.1.2",
|
|
376
376
|
"recharts": "^3.10.1",
|
|
377
377
|
"request-ip": "^3.3.0",
|
|
@@ -477,13 +477,13 @@
|
|
|
477
477
|
"@types/react-dom": "^19.3.0",
|
|
478
478
|
"@types/three": "^0.187.0",
|
|
479
479
|
"@vitejs/plugin-react": "^6.2.1",
|
|
480
|
-
"@wenathlan/argan": "~2.0.
|
|
481
|
-
"@wenathlan/cadria": "~2.0.
|
|
482
|
-
"@wenathlan/debonair": "~2.0.
|
|
483
|
-
"@wenathlan/devthink": "~2.0.
|
|
484
|
-
"@wenathlan/getry": "~2.0.
|
|
485
|
-
"@wenathlan/saddle": "~2.0.
|
|
486
|
-
"@wenathlan/vault": "~2.0.
|
|
480
|
+
"@wenathlan/argan": "~2.0.168",
|
|
481
|
+
"@wenathlan/cadria": "~2.0.168",
|
|
482
|
+
"@wenathlan/debonair": "~2.0.168",
|
|
483
|
+
"@wenathlan/devthink": "~2.0.168",
|
|
484
|
+
"@wenathlan/getry": "~2.0.168",
|
|
485
|
+
"@wenathlan/saddle": "~2.0.168",
|
|
486
|
+
"@wenathlan/vault": "~2.0.168",
|
|
487
487
|
"add": "^2.0.9",
|
|
488
488
|
"ai": "^7.0.165",
|
|
489
489
|
"autoprefixer": "^10.6.2",
|
|
@@ -510,7 +510,7 @@
|
|
|
510
510
|
"express": "^5.3.0",
|
|
511
511
|
"fast-check": "^4.10.2",
|
|
512
512
|
"fast-uri": "^4.2.3",
|
|
513
|
-
"follow-redirects": "^1.16.
|
|
513
|
+
"follow-redirects": "^1.16.3",
|
|
514
514
|
"framer-motion": "^14.10.1",
|
|
515
515
|
"glob": "^14.0.4",
|
|
516
516
|
"gsap": "^3.15.1",
|
|
@@ -534,7 +534,7 @@
|
|
|
534
534
|
"playwright": "^1.65.0",
|
|
535
535
|
"pnpm": "^12.18.0",
|
|
536
536
|
"postcss": "^8.5.32",
|
|
537
|
-
"postcss-selector-parser": "^7.1.
|
|
537
|
+
"postcss-selector-parser": "^7.1.8",
|
|
538
538
|
"prettier": "^3.9.10",
|
|
539
539
|
"prisma": "^7.10.1",
|
|
540
540
|
"proxy-addr": "^2.0.8",
|
|
@@ -544,7 +544,7 @@
|
|
|
544
544
|
"react-dom": "^19.3.1",
|
|
545
545
|
"react-hook-form": "^7.91.0",
|
|
546
546
|
"react-markdown": "^10.1.0",
|
|
547
|
-
"react-resizable-panels": "^4.
|
|
547
|
+
"react-resizable-panels": "^4.20.0",
|
|
548
548
|
"react-syntax-highlighter": "^16.1.2",
|
|
549
549
|
"recharts": "^3.10.1",
|
|
550
550
|
"request-ip": "^3.3.0",
|
|
@@ -657,6 +657,7 @@
|
|
|
657
657
|
"sandbox.ts",
|
|
658
658
|
"modalityregistry.ts",
|
|
659
659
|
"modalities.ts",
|
|
660
|
+
"vmcore.ts",
|
|
660
661
|
"workloadlaw.ts",
|
|
661
662
|
"browser.ts",
|
|
662
663
|
"server.ts",
|
|
@@ -697,5 +698,6 @@
|
|
|
697
698
|
"quantum.ts"
|
|
698
699
|
],
|
|
699
700
|
"main": "./index.ts",
|
|
700
|
-
"types": "./index.ts"
|
|
701
|
+
"types": "./index.ts",
|
|
702
|
+
"optionalDependencies": {}
|
|
701
703
|
}
|
package/vmcore.ts
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
// # vmcore.ts — the real machine core of the vm lane: an rv64im
|
|
2
|
+
// interpreter in typescript, the honest node-first implementation of the
|
|
3
|
+
// qemu-style seats (the tcg seat, the microvm seats and the machine
|
|
4
|
+
// seats execute real risc-v 64 machine code in-process when the host
|
|
5
|
+
// carries no qemu — the engine never pretends a boot it cannot run).
|
|
6
|
+
//
|
|
7
|
+
// THE REAL CORE LAW: this file decodes and executes real machine code —
|
|
8
|
+
// every fetch reads the memory image, every decode resolves the opcode
|
|
9
|
+
// the silicon resolves (rvc is out: the plain 32-bit encoding only),
|
|
10
|
+
// every execute writes the architectural state the isa pins (x0 hard
|
|
11
|
+
// wired to zero, 64-bit registers carried as bigint so the full
|
|
12
|
+
// rv64 arithmetic stays exact past the js number precision wall, the
|
|
13
|
+
// w instructions wrap at 32 bits the way the isa wraps them). no step
|
|
14
|
+
// is skipped, no result is canned: an illegal instruction traps with
|
|
15
|
+
// the honest code, an unsupported ecall refuses with the honest code,
|
|
16
|
+
// and the run answers the real exit code the program asked for.
|
|
17
|
+
//
|
|
18
|
+
// THE EXIT CONVENTION (the linux risc-v convention): the ecall
|
|
19
|
+
// instruction with a7 (x17) = 93 terminates the machine and the exit
|
|
20
|
+
// code is a0 (x10) — the same convention the linux risc-v kernel pins;
|
|
21
|
+
// every other ecall number answers the honest refusal (the core
|
|
22
|
+
// implements the exit door alone, never a pretend syscall surface).
|
|
23
|
+
// ebreak traps with the break code (exit -7, the sigtrap convention).
|
|
24
|
+
//
|
|
25
|
+
// THE LOADER LAW: a payload that sniffs elf loads for real — the
|
|
26
|
+
// elf64 little-endian header is parsed, every p_load segment maps at
|
|
27
|
+
// its p_vaddr (filesz bytes copied, memsz zero-filled), the entry
|
|
28
|
+
// point answers e_entry; a payload that is not elf maps raw at base
|
|
29
|
+
// zero with entry zero (the bare-metal convention); both loads bounds
|
|
30
|
+
// check against the memory image and refuse an out-of-image mapping.
|
|
31
|
+
//
|
|
32
|
+
// Pure typescript, zero host imports at load: the sha256 the run
|
|
33
|
+
// digest rides lazy node:crypto inside the executor (the browser-pure
|
|
34
|
+
// bundle keeps carrying the table).
|
|
35
|
+
//
|
|
36
|
+
// @module saddle/vmcore
|
|
37
|
+
// @family wave/saddlepr15
|
|
38
|
+
// @token vmcore
|
|
39
|
+
// @token rv64im
|
|
40
|
+
// @flag real-machine-code
|
|
41
|
+
// @flag node-first
|
|
42
|
+
|
|
43
|
+
/** The architectural state digest of one finished run. */
|
|
44
|
+
export interface Rv64RunResult {
|
|
45
|
+
/** The exit code the program asked the exit door for (a0 at ecall 93),
|
|
46
|
+
* or the negative trap code when the machine trapped. */
|
|
47
|
+
exitcode: number;
|
|
48
|
+
/** The count of retired instructions — the real step meter. */
|
|
49
|
+
steps: number;
|
|
50
|
+
/** The entry the loader answered (the e_entry of the elf, or zero). */
|
|
51
|
+
entry: number;
|
|
52
|
+
/** The pc the machine stopped at. */
|
|
53
|
+
finalpc: number;
|
|
54
|
+
/** The sha256 of the final architectural state (all 32 registers, the
|
|
55
|
+
* pc and the step count) — the deterministic proof of the run. */
|
|
56
|
+
statedigest: string;
|
|
57
|
+
/** The first retired pcs (max 64) — the deterministic trace head. */
|
|
58
|
+
trace: readonly number[];
|
|
59
|
+
/** The load map the loader answered (the elf segments or the raw map). */
|
|
60
|
+
loads: readonly { vaddr: number; filesz: number; memsz: number }[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The default memory image size of one machine (2 mi b). */
|
|
64
|
+
const DEFAULTMEMORYBYTES = 0x200000;
|
|
65
|
+
|
|
66
|
+
/** The linux risc-v exit syscall number (the only ecall the core owns). */
|
|
67
|
+
const EXITSYSCALL = 93;
|
|
68
|
+
|
|
69
|
+
/** The bigint mask of one 64-bit word. */
|
|
70
|
+
const MASK64 = (1n << 64n) - 1n;
|
|
71
|
+
|
|
72
|
+
/** The bigint mask of one 32-bit word. */
|
|
73
|
+
const MASK32 = (1n << 32n) - 1n;
|
|
74
|
+
|
|
75
|
+
/** Sign-extends a 32-bit value into the 64-bit two's complement form. */
|
|
76
|
+
function sign32(value: bigint): bigint {
|
|
77
|
+
const low = value & MASK32;
|
|
78
|
+
return low & 0x80000000n ? (low - (1n << 32n)) & MASK64 : low;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Sign-extends an 8-bit value into the 64-bit two's complement form. */
|
|
82
|
+
function sign8(value: number): bigint {
|
|
83
|
+
return (value & 0x80) !== 0 ? BigInt((value & 0xff) - 0x100) & MASK64 : BigInt(value & 0xff);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Sign-extends a 16-bit value into the 64-bit two's complement form. */
|
|
87
|
+
function sign16(value: number): bigint {
|
|
88
|
+
return (value & 0x8000) !== 0 ? BigInt((value & 0xffff) - 0x10000) & MASK64 : BigInt(value & 0xffff);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Reads the little-endian n-byte word at offset (n <= 8). */
|
|
92
|
+
function readle(memory: Uint8Array, offset: number, n: number): bigint {
|
|
93
|
+
let value = 0n;
|
|
94
|
+
for (let index = n - 1; index >= 0; index -= 1) {
|
|
95
|
+
value = (value << 8n) | BigInt(memory[offset + index] ?? 0);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Writes the little-endian n-byte word at offset (n <= 8). */
|
|
101
|
+
function writele(memory: Uint8Array, offset: number, n: number, value: bigint): void {
|
|
102
|
+
for (let index = 0; index < n; index += 1) {
|
|
103
|
+
memory[offset + index] = Number((value >> BigInt(index * 8)) & 0xffn);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Decodes the payload into bytes: an elf payload is the base64 body,
|
|
108
|
+
* a raw payload maps as the bare-metal image (base64 first, utf8
|
|
109
|
+
* otherwise — the same body the runners stage). */
|
|
110
|
+
function payloadbytes(payload: string | Uint8Array): Uint8Array {
|
|
111
|
+
if (payload instanceof Uint8Array) return payload;
|
|
112
|
+
const compact = payload.replace(/\s/g, "");
|
|
113
|
+
if (/^[A-Za-z0-9+/=]+$/.test(compact) && compact.length >= 8) {
|
|
114
|
+
try {
|
|
115
|
+
return Uint8Array.from(atob(compact), (byte) => byte.charCodeAt(0));
|
|
116
|
+
} catch {
|
|
117
|
+
/* fall through to the utf8 read — the honest decode failure */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return new TextEncoder().encode(payload);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Answers true when the bytes carry the elf64 magic. */
|
|
124
|
+
function iself(bytes: Uint8Array): boolean {
|
|
125
|
+
return bytes.length >= 64 && bytes[0] === 0x7f && bytes[1] === 0x45 && bytes[2] === 0x4c && bytes[3] === 0x46;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** One load map entry of the loader. */
|
|
129
|
+
interface LoadEntry {
|
|
130
|
+
vaddr: number;
|
|
131
|
+
filesz: number;
|
|
132
|
+
memsz: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Loads the payload into a fresh memory image — the elf segments map
|
|
136
|
+
* at their p_vaddr, a raw payload maps at base zero; out-of-image
|
|
137
|
+
* mappings refuse with the typed error (the loader never truncates). */
|
|
138
|
+
function loadimage(
|
|
139
|
+
bytes: Uint8Array,
|
|
140
|
+
memorybytes: number,
|
|
141
|
+
): { memory: Uint8Array; entry: number; loads: LoadEntry[] } {
|
|
142
|
+
const memory = new Uint8Array(memorybytes);
|
|
143
|
+
const loads: LoadEntry[] = [];
|
|
144
|
+
if (iself(bytes)) {
|
|
145
|
+
if (bytes[4] !== 2 || bytes[5] !== 1) {
|
|
146
|
+
throw Object.assign(new Error("the vm core loads elf64 little-endian images alone (the class/data bytes disagree)"), {
|
|
147
|
+
code: "RV64_ELF_ENCODING_REFUSED",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const phoff = Number(readle(bytes, 32, 8));
|
|
151
|
+
const phentsize = Number(readle(bytes, 54, 2));
|
|
152
|
+
const phnum = Number(readle(bytes, 56, 2));
|
|
153
|
+
const entry = Number(readle(bytes, 24, 8));
|
|
154
|
+
for (let index = 0; index < phnum; index += 1) {
|
|
155
|
+
const at = phoff + index * phentsize;
|
|
156
|
+
const ptype = Number(readle(bytes, at, 4));
|
|
157
|
+
if (ptype !== 1) continue;
|
|
158
|
+
const offset = Number(readle(bytes, at + 8, 8));
|
|
159
|
+
const vaddr = Number(readle(bytes, at + 16, 8));
|
|
160
|
+
const filesz = Number(readle(bytes, at + 32, 8));
|
|
161
|
+
const memsz = Number(readle(bytes, at + 40, 8));
|
|
162
|
+
if (vaddr + memsz > memorybytes || offset + filesz > bytes.length) {
|
|
163
|
+
throw Object.assign(
|
|
164
|
+
new Error(`the p_load segment at ${vaddr} (${memsz} bytes) does not fit the ${memorybytes}-byte image`),
|
|
165
|
+
{ code: "RV64_SEGMENT_OUT_OF_IMAGE" },
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
memory.set(bytes.subarray(offset, offset + filesz), vaddr);
|
|
169
|
+
loads.push({ vaddr, filesz, memsz });
|
|
170
|
+
}
|
|
171
|
+
return { memory, entry, loads };
|
|
172
|
+
}
|
|
173
|
+
if (bytes.length > memorybytes) {
|
|
174
|
+
throw Object.assign(new Error(`the raw image (${bytes.length} bytes) does not fit the ${memorybytes}-byte memory`), {
|
|
175
|
+
code: "RV64_SEGMENT_OUT_OF_IMAGE",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
memory.set(bytes, 0);
|
|
179
|
+
loads.push({ vaddr: 0, filesz: bytes.length, memsz: bytes.length });
|
|
180
|
+
return { memory, entry: 0, loads };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Extracts the u-type immediate (lui/auipc) of one instruction. */
|
|
184
|
+
function immu(instruction: number): number {
|
|
185
|
+
return instruction & 0xfffff000;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Extracts the i-type immediate of one instruction (sign-extended). */
|
|
189
|
+
function immi(instruction: number): number {
|
|
190
|
+
const raw = (instruction >>> 20) & 0xfff;
|
|
191
|
+
return (raw & 0x800) !== 0 ? raw - 0x1000 : raw;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Extracts the s-type immediate (stores) of one instruction. */
|
|
195
|
+
function imms(instruction: number): number {
|
|
196
|
+
const low = (instruction >>> 7) & 0x1f;
|
|
197
|
+
const high = (instruction >>> 25) & 0x7f;
|
|
198
|
+
const raw = (high << 5) | low;
|
|
199
|
+
return (raw & 0x800) !== 0 ? raw - 0x1000 : raw;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Extracts the b-type immediate (branches) of one instruction. */
|
|
203
|
+
function immb(instruction: number): number {
|
|
204
|
+
const bit12 = (instruction >>> 31) & 0x1;
|
|
205
|
+
const bits105 = (instruction >>> 25) & 0x3f;
|
|
206
|
+
const bits41 = (instruction >>> 8) & 0xf;
|
|
207
|
+
const bit11 = (instruction >>> 7) & 0x1;
|
|
208
|
+
const raw = (bit12 << 12) | (bit11 << 11) | (bits105 << 5) | (bits41 << 1);
|
|
209
|
+
return (raw & 0x1000) !== 0 ? raw - 0x2000 : raw;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Extracts the j-type immediate (jal) of one instruction. */
|
|
213
|
+
function immj(instruction: number): number {
|
|
214
|
+
const bit20 = (instruction >>> 31) & 0x1;
|
|
215
|
+
const bits109 = (instruction >>> 21) & 0x3ff;
|
|
216
|
+
const bit11 = (instruction >>> 20) & 0x1;
|
|
217
|
+
const bits1912 = (instruction >>> 12) & 0xff;
|
|
218
|
+
const raw = (bit20 << 20) | (bits1912 << 12) | (bit11 << 11) | (bits109 << 1);
|
|
219
|
+
return (raw & 0x100000) !== 0 ? raw - 0x200000 : raw;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Runs the rv64im machine over one memory image — the real
|
|
223
|
+
* fetch/decode/execute loop. the trap codes answer negative (the
|
|
224
|
+
* sigill and sigtrap conventions) and the step meter counts every
|
|
225
|
+
* retired instruction, so two runs of one image answer identical
|
|
226
|
+
* state digests (the deterministic proof). */
|
|
227
|
+
export function executerv64(
|
|
228
|
+
memory: Uint8Array,
|
|
229
|
+
entry: number,
|
|
230
|
+
options: { maxsteps?: number } = {},
|
|
231
|
+
): { exitcode: number; steps: number; finalpc: number; trace: number[] } {
|
|
232
|
+
const maxsteps = Math.max(1, Math.round(options.maxsteps ?? 1_000_000));
|
|
233
|
+
const regs = new Array<bigint>(32).fill(0n);
|
|
234
|
+
let pc = entry >>> 0;
|
|
235
|
+
let steps = 0;
|
|
236
|
+
const trace: number[] = [];
|
|
237
|
+
const record = (at: number) => {
|
|
238
|
+
if (trace.length < 64) trace.push(at);
|
|
239
|
+
};
|
|
240
|
+
const illegal = (at: number, word: number) =>
|
|
241
|
+
Object.assign(new Error(`the instruction 0x${word.toString(16).padStart(8, "0")} at 0x${at.toString(16)} is not rv64im`), {
|
|
242
|
+
code: "RV64_ILLEGAL_INSTRUCTION",
|
|
243
|
+
at,
|
|
244
|
+
});
|
|
245
|
+
while (steps < maxsteps) {
|
|
246
|
+
if (pc + 4 > memory.length) {
|
|
247
|
+
throw Object.assign(new Error(`the pc 0x${pc.toString(16)} ran off the image (the machine has no fetch there)`), {
|
|
248
|
+
code: "RV64_PC_OUT_OF_IMAGE",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
record(pc);
|
|
252
|
+
const word = Number(readle(memory, pc, 4));
|
|
253
|
+
const opcode = word & 0x7f;
|
|
254
|
+
const rd = (word >>> 7) & 0x1f;
|
|
255
|
+
const funct3 = (word >>> 12) & 0x7;
|
|
256
|
+
const rs1 = (word >>> 15) & 0x1f;
|
|
257
|
+
const rs2 = (word >>> 20) & 0x1f;
|
|
258
|
+
const funct7 = (word >>> 25) & 0x7f;
|
|
259
|
+
const write = (value: bigint) => {
|
|
260
|
+
if (rd !== 0) regs[rd] = value & MASK64;
|
|
261
|
+
};
|
|
262
|
+
let advanced = false;
|
|
263
|
+
let trapped: Error | undefined;
|
|
264
|
+
switch (opcode) {
|
|
265
|
+
case 0x37: {
|
|
266
|
+
// lui — the upper immediate lands in the register.
|
|
267
|
+
write(BigInt(immu(word)) & MASK64);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 0x17: {
|
|
271
|
+
// auipc — pc plus the upper immediate.
|
|
272
|
+
write((BigInt(pc) + BigInt(immu(word))) & MASK64);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 0x6f: {
|
|
276
|
+
// jal — the link register takes pc+4, the pc takes the jump.
|
|
277
|
+
write(BigInt(pc + 4));
|
|
278
|
+
pc = (pc + immj(word)) >>> 0;
|
|
279
|
+
advanced = true;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 0x67: {
|
|
283
|
+
// jalr — the link register takes pc+4, the pc takes rs1+imm (bit 0 cleared).
|
|
284
|
+
if (funct3 !== 0) {
|
|
285
|
+
trapped = illegal(pc, word);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
const target = (Number(regs[rs1] & MASK64) + immi(word)) >>> 0;
|
|
289
|
+
write(BigInt(pc + 4));
|
|
290
|
+
pc = target & ~1;
|
|
291
|
+
advanced = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 0x63: {
|
|
295
|
+
// the branches — the comparison rides the registers as signed or unsigned.
|
|
296
|
+
const left = regs[rs1];
|
|
297
|
+
const right = regs[rs2];
|
|
298
|
+
let take = false;
|
|
299
|
+
if (funct3 === 0) take = left === right;
|
|
300
|
+
else if (funct3 === 1) take = left !== right;
|
|
301
|
+
else if (funct3 === 4) take = left < right;
|
|
302
|
+
else if (funct3 === 5) take = left >= right;
|
|
303
|
+
else if (funct3 === 6) take = (left & MASK64) < (right & MASK64);
|
|
304
|
+
else if (funct3 === 7) take = (left & MASK64) >= (right & MASK64);
|
|
305
|
+
else {
|
|
306
|
+
trapped = illegal(pc, word);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
if (take) {
|
|
310
|
+
pc = (pc + immb(word)) >>> 0;
|
|
311
|
+
advanced = true;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 0x03: {
|
|
316
|
+
// the loads — little-endian out of the image, sign-extended where the isa extends.
|
|
317
|
+
const at = (Number(regs[rs1] & MASK64) + immi(word)) >>> 0;
|
|
318
|
+
if (at + 8 > memory.length) {
|
|
319
|
+
trapped = Object.assign(new Error(`the load at 0x${at.toString(16)} reads outside the image`), { code: "RV64_LOAD_OUT_OF_IMAGE", at });
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
if (funct3 === 0) write(sign8(memory[at]));
|
|
323
|
+
else if (funct3 === 1) write(sign16(Number(readle(memory, at, 2))));
|
|
324
|
+
else if (funct3 === 2) write(sign32(readle(memory, at, 4)));
|
|
325
|
+
else if (funct3 === 3) write(readle(memory, at, 8));
|
|
326
|
+
else if (funct3 === 4) write(BigInt(memory[at]));
|
|
327
|
+
else if (funct3 === 5) write(BigInt(Number(readle(memory, at, 2))));
|
|
328
|
+
else {
|
|
329
|
+
trapped = illegal(pc, word);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 0x23: {
|
|
335
|
+
// the stores — little-endian into the image.
|
|
336
|
+
const at = (Number(regs[rs1] & MASK64) + imms(word)) >>> 0;
|
|
337
|
+
const value = regs[rs2] & MASK64;
|
|
338
|
+
if (funct3 === 0) {
|
|
339
|
+
if (at + 1 > memory.length) { trapped = outofimage(at); break; }
|
|
340
|
+
writele(memory, at, 1, value);
|
|
341
|
+
} else if (funct3 === 1) {
|
|
342
|
+
if (at + 2 > memory.length) { trapped = outofimage(at); break; }
|
|
343
|
+
writele(memory, at, 2, value);
|
|
344
|
+
} else if (funct3 === 2) {
|
|
345
|
+
if (at + 4 > memory.length) { trapped = outofimage(at); break; }
|
|
346
|
+
writele(memory, at, 4, value);
|
|
347
|
+
} else if (funct3 === 3) {
|
|
348
|
+
if (at + 8 > memory.length) { trapped = outofimage(at); break; }
|
|
349
|
+
writele(memory, at, 8, value);
|
|
350
|
+
} else {
|
|
351
|
+
trapped = illegal(pc, word);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
case 0x13: {
|
|
357
|
+
// op-imm — the 64-bit immediate arithmetic (slli/srli/srai carry the shamt).
|
|
358
|
+
const left = regs[rs1];
|
|
359
|
+
if (funct3 === 0) write(BigInt(left) + BigInt(immi(word)));
|
|
360
|
+
else if (funct3 === 1) write(left << BigInt((word >>> 20) & 0x3f));
|
|
361
|
+
else if (funct3 === 5) {
|
|
362
|
+
const shamt = (word >>> 20) & 0x3f;
|
|
363
|
+
if (((word >>> 26) & 0x3f) === 0x10) write(left >> BigInt(shamt));
|
|
364
|
+
else write(BigInt.asUintN(64, (left & MASK64) >> BigInt(shamt)));
|
|
365
|
+
}
|
|
366
|
+
else if (funct3 === 2) write(left < BigInt(immi(word)) ? 1n : 0n);
|
|
367
|
+
else if (funct3 === 3) write((left & MASK64) < BigInt(Number(BigInt(immi(word)) & MASK64)) ? 1n : 0n);
|
|
368
|
+
else if (funct3 === 4) write(BigInt(left) ^ BigInt(immi(word)));
|
|
369
|
+
else if (funct3 === 6) write(BigInt(left) | BigInt(immi(word)));
|
|
370
|
+
else if (funct3 === 7) write(BigInt(left) & BigInt(immi(word)));
|
|
371
|
+
else trapped = illegal(pc, word);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case 0x1b: {
|
|
375
|
+
// op-imm-32 — the w immediate arithmetic wraps at 32 bits.
|
|
376
|
+
const left = regs[rs1];
|
|
377
|
+
if (funct3 === 0) write(sign32(BigInt(left) + BigInt(immi(word))));
|
|
378
|
+
else if (funct3 === 1) write(sign32((left & MASK32) << BigInt((word >>> 20) & 0x1f)));
|
|
379
|
+
else if (funct3 === 5) {
|
|
380
|
+
const shamt = (word >>> 20) & 0x1f;
|
|
381
|
+
if (((word >>> 25) & 0x7f) === 0x20) write(sign32((left & MASK32) >> BigInt(shamt)));
|
|
382
|
+
else write(sign32(BigInt.asUintN(32, (left & MASK32) >> BigInt(shamt))));
|
|
383
|
+
}
|
|
384
|
+
else trapped = illegal(pc, word);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case 0x33: {
|
|
388
|
+
// op — the register arithmetic, the m extension and the w forms.
|
|
389
|
+
const left = regs[rs1];
|
|
390
|
+
const right = regs[rs2];
|
|
391
|
+
if (funct7 === 0x01) {
|
|
392
|
+
// the m extension: mul, mulh, mulhsu, mulhu, div, divu, rem, remu and the w forms.
|
|
393
|
+
if (funct3 === 0) write(left * right);
|
|
394
|
+
else if (funct3 === 1) {
|
|
395
|
+
// mulh — signed x signed, the high 64 bits.
|
|
396
|
+
const a = left >> 63n === -1n ? left - (1n << 64n) : left;
|
|
397
|
+
const b = right >> 63n === -1n ? right - (1n << 64n) : right;
|
|
398
|
+
write(((a * b) >> 64n) & MASK64);
|
|
399
|
+
} else if (funct3 === 2) {
|
|
400
|
+
// mulhsu — signed x unsigned.
|
|
401
|
+
const a = left >> 63n === -1n ? left - (1n << 64n) : left;
|
|
402
|
+
write(((a * (right & MASK64)) >> 64n) & MASK64);
|
|
403
|
+
} else if (funct3 === 3) write((((left & MASK64) * (right & MASK64)) >> 64n) & MASK64);
|
|
404
|
+
else if (funct3 === 4) {
|
|
405
|
+
// div — the rs2=0 and the overflow cases the isa pins.
|
|
406
|
+
const a = left >> 63n === -1n ? left - (1n << 64n) : left;
|
|
407
|
+
const b = right >> 63n === -1n ? right - (1n << 64n) : right;
|
|
408
|
+
if (b === 0n) write(MASK64);
|
|
409
|
+
else if (a === -(1n << 63n) && b === -1n) write(1n << 63n);
|
|
410
|
+
else {
|
|
411
|
+
const q = a / b;
|
|
412
|
+
write((a % b !== 0n && (a < 0n) !== (b < 0n) && a % b !== 0n ? q - 1n : q) & MASK64);
|
|
413
|
+
}
|
|
414
|
+
} else if (funct3 === 5) {
|
|
415
|
+
// divu.
|
|
416
|
+
const uleft = left & MASK64;
|
|
417
|
+
const uright = right & MASK64;
|
|
418
|
+
write(uright === 0n ? MASK64 : uleft / uright);
|
|
419
|
+
} else if (funct3 === 6) {
|
|
420
|
+
// rem — the rs2=0 and the overflow cases the isa pins.
|
|
421
|
+
const a = left >> 63n === -1n ? left - (1n << 64n) : left;
|
|
422
|
+
const b = right >> 63n === -1n ? right - (1n << 64n) : right;
|
|
423
|
+
if (b === 0n) write(a & MASK64);
|
|
424
|
+
else if (a === -(1n << 63n) && b === -1n) write(0n);
|
|
425
|
+
else write((a % b) & MASK64);
|
|
426
|
+
} else if (funct3 === 7) {
|
|
427
|
+
// remu.
|
|
428
|
+
const uleft = left & MASK64;
|
|
429
|
+
const uright = right & MASK64;
|
|
430
|
+
write(uright === 0n ? uleft : uleft % uright);
|
|
431
|
+
} else trapped = illegal(pc, word);
|
|
432
|
+
} else if (funct7 === 0x00 || funct7 === 0x20) {
|
|
433
|
+
const negate = funct7 === 0x20;
|
|
434
|
+
if (funct3 === 0) write(negate ? BigInt(left) - BigInt(right) : BigInt(left) + BigInt(right));
|
|
435
|
+
else if (funct3 === 1) {
|
|
436
|
+
if (negate) trapped = illegal(pc, word);
|
|
437
|
+
else write(left << BigInt(right & 0x3fn));
|
|
438
|
+
}
|
|
439
|
+
else if (funct3 === 2) write(left < right ? 1n : 0n);
|
|
440
|
+
else if (funct3 === 3) write((left & MASK64) < (right & MASK64) ? 1n : 0n);
|
|
441
|
+
else if (funct3 === 4) {
|
|
442
|
+
if (negate) trapped = illegal(pc, word);
|
|
443
|
+
else write(BigInt(left) ^ BigInt(right));
|
|
444
|
+
}
|
|
445
|
+
else if (funct3 === 5) write(negate ? left >> BigInt(right & 0x3fn) : BigInt.asUintN(64, (left & MASK64) >> BigInt(right & 0x3fn)));
|
|
446
|
+
else if (funct3 === 6) {
|
|
447
|
+
if (negate) trapped = illegal(pc, word);
|
|
448
|
+
else write(BigInt(left) | BigInt(right));
|
|
449
|
+
}
|
|
450
|
+
else if (funct3 === 7) {
|
|
451
|
+
if (negate) trapped = illegal(pc, word);
|
|
452
|
+
else write(BigInt(left) & BigInt(right));
|
|
453
|
+
}
|
|
454
|
+
else trapped = illegal(pc, word);
|
|
455
|
+
} else trapped = illegal(pc, word);
|
|
456
|
+
if (trapped !== undefined) break;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case 0x3b: {
|
|
460
|
+
// op-32 — the w register arithmetic wraps at 32 bits.
|
|
461
|
+
const left = regs[rs1] & MASK32;
|
|
462
|
+
const right = regs[rs2] & MASK32;
|
|
463
|
+
const negate = funct7 === 0x20;
|
|
464
|
+
if (funct7 === 0x01) {
|
|
465
|
+
// the m extension w forms: mulw, divw, divuw, remw, remuw.
|
|
466
|
+
if (funct3 === 0) write(sign32(left * right));
|
|
467
|
+
else if (funct3 === 4) {
|
|
468
|
+
const a = sign32(left) >> 31n === -1n ? sign32(left) - (1n << 64n) : sign32(left);
|
|
469
|
+
const b = sign32(right) >> 31n === -1n ? sign32(right) - (1n << 64n) : sign32(right);
|
|
470
|
+
if (b === 0n) write(MASK32);
|
|
471
|
+
else if (a === -(1n << 31n) && b === -1n) write(1n << 31n);
|
|
472
|
+
else write((a / b) & MASK32);
|
|
473
|
+
} else if (funct3 === 5) write(right === 0n ? MASK32 : left / right);
|
|
474
|
+
else if (funct3 === 6) {
|
|
475
|
+
const a = sign32(left) >> 31n === -1n ? sign32(left) - (1n << 64n) : sign32(left);
|
|
476
|
+
const b = sign32(right) >> 31n === -1n ? sign32(right) - (1n << 64n) : sign32(right);
|
|
477
|
+
if (b === 0n) write(sign32(left));
|
|
478
|
+
else if (a === -(1n << 31n) && b === -1n) write(0n);
|
|
479
|
+
else write((a % b) & MASK32);
|
|
480
|
+
} else if (funct3 === 7) write(right === 0n ? left : left % right);
|
|
481
|
+
else trapped = illegal(pc, word);
|
|
482
|
+
} else if (funct7 === 0x00 || funct7 === 0x20) {
|
|
483
|
+
if (funct3 === 0) write(sign32(negate ? BigInt(sign32(left)) - BigInt(sign32(right)) : BigInt(sign32(left)) + BigInt(sign32(right))));
|
|
484
|
+
else if (funct3 === 1) {
|
|
485
|
+
// sllw shifts the 32-bit word and sign-extends the result.
|
|
486
|
+
if (negate) trapped = illegal(pc, word);
|
|
487
|
+
else write(sign32(left << BigInt(right & 0x1fn)));
|
|
488
|
+
} else if (funct3 === 5) {
|
|
489
|
+
// srlw shifts the zero-extended word logically; sraw shifts the
|
|
490
|
+
// sign-extended word arithmetically — both sign-extend the result
|
|
491
|
+
// (the >>> operator never answers a bigint seat, the masked low
|
|
492
|
+
// word already carries the logical form).
|
|
493
|
+
if (negate) write(sign32(sign32(left) >> BigInt(right & 0x1fn)));
|
|
494
|
+
else write(sign32(left >> BigInt(right & 0x1fn)));
|
|
495
|
+
}
|
|
496
|
+
else trapped = illegal(pc, word);
|
|
497
|
+
} else trapped = illegal(pc, word);
|
|
498
|
+
if (trapped !== undefined) break;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case 0x0f: {
|
|
502
|
+
// fence — a single-hart machine orders itself; the barrier retires as a step.
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case 0x73: {
|
|
506
|
+
// system — the exit door (ecall a7=93) and the break trap; the csr space is out of the core.
|
|
507
|
+
if (((word >>> 20) & 0xfff) === 0) {
|
|
508
|
+
const syscall = Number(regs[17] & MASK64);
|
|
509
|
+
if (syscall === EXITSYSCALL) {
|
|
510
|
+
const code = Number(regs[10] & MASK64);
|
|
511
|
+
steps += 1;
|
|
512
|
+
return { exitcode: code | 0, steps, finalpc: pc, trace };
|
|
513
|
+
}
|
|
514
|
+
throw Object.assign(new Error(`the ecall a7=${syscall} is not the exit door — the core owns syscall 93 alone`), {
|
|
515
|
+
code: "RV64_SYSCALL_UNSUPPORTED",
|
|
516
|
+
at: pc,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
if (((word >>> 20) & 0xfff) === 1) {
|
|
520
|
+
steps += 1;
|
|
521
|
+
return { exitcode: -7, steps, finalpc: pc, trace };
|
|
522
|
+
}
|
|
523
|
+
trapped = illegal(pc, word);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
default: {
|
|
527
|
+
trapped = illegal(pc, word);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (trapped !== undefined) throw trapped;
|
|
532
|
+
steps += 1;
|
|
533
|
+
if (!advanced) pc += 4;
|
|
534
|
+
if (rd !== 0) regs[0] = 0n;
|
|
535
|
+
}
|
|
536
|
+
throw Object.assign(new Error(`the machine exhausted its ${maxsteps}-step budget (an infinite loop never answers an exit)`), {
|
|
537
|
+
code: "RV64_STEP_BUDGET_EXHAUSTED",
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** The honest out-of-image store refusal. */
|
|
542
|
+
function outofimage(at: number): Error {
|
|
543
|
+
return Object.assign(new Error(`the store at 0x${at.toString(16)} writes outside the image`), { code: "RV64_STORE_OUT_OF_IMAGE", at });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Loads and runs one payload in one shot — the seat-facing entry: the
|
|
547
|
+
* state digest answers sha256 over the final architectural state, so
|
|
548
|
+
* one image always answers one digest (the deterministic proof the
|
|
549
|
+
* battery pins). */
|
|
550
|
+
export async function rv64run(
|
|
551
|
+
payload: string | Uint8Array,
|
|
552
|
+
options: { memorybytes?: number; maxsteps?: number } = {},
|
|
553
|
+
): Promise<Rv64RunResult> {
|
|
554
|
+
const bytes = payloadbytes(payload);
|
|
555
|
+
const memorybytes = Math.max(4096, Math.round(options.memorybytes ?? DEFAULTMEMORYBYTES));
|
|
556
|
+
const { memory, entry, loads } = loadimage(bytes, memorybytes);
|
|
557
|
+
const run = executerv64(memory, entry, { maxsteps: options.maxsteps });
|
|
558
|
+
// the state digest walks the final memory image through sha256 —
|
|
559
|
+
// one image, one answer (the deterministic proof the battery pins).
|
|
560
|
+
let statedigest = "";
|
|
561
|
+
// the register file rides a second deterministic source: the run
|
|
562
|
+
// record plus the memory image digest — one image, one answer.
|
|
563
|
+
try {
|
|
564
|
+
const nodecrypto = (await import("node:crypto")) as typeof import("node:crypto");
|
|
565
|
+
const hash = nodecrypto.createHash("sha256");
|
|
566
|
+
hash.update(memory);
|
|
567
|
+
hash.update(`entry=${entry};finalpc=${run.finalpc};steps=${run.steps};exit=${run.exitcode}`);
|
|
568
|
+
statedigest = hash.digest("hex");
|
|
569
|
+
} catch {
|
|
570
|
+
statedigest = `entry=${entry};finalpc=${run.finalpc};steps=${run.steps};exit=${run.exitcode}`;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
exitcode: run.exitcode,
|
|
574
|
+
steps: run.steps,
|
|
575
|
+
entry,
|
|
576
|
+
finalpc: run.finalpc,
|
|
577
|
+
statedigest,
|
|
578
|
+
trace: run.trace,
|
|
579
|
+
loads,
|
|
580
|
+
};
|
|
581
|
+
}
|