Compare commits
261
Commits
0629f5e2df
...
v0.36.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a77dd12d2d | ||
|
|
e5df0a9d84 | ||
|
|
a9ee2bc702 | ||
|
|
6f3e054bab | ||
|
|
9179d5cc7a | ||
|
|
431d0d9b0b | ||
|
|
cea5db6964 | ||
|
|
4eb9100def | ||
|
|
43494a30f0 | ||
|
|
f661c2fc78 | ||
|
|
a3eaa82f87 | ||
|
|
1031cd9ae7 | ||
|
|
eda8b16c5f | ||
|
|
718181e7a7 | ||
|
|
107ca512b2 | ||
|
|
4066396226 | ||
|
|
df7a5091e0 | ||
|
|
c27ba30862 | ||
|
|
5f6be4584d | ||
|
|
c1cef7b6e8 | ||
|
|
1120a52a54 | ||
|
|
fa50521619 | ||
|
|
5ee60c860b | ||
|
|
76f8ba6403 | ||
|
|
0211d6672d | ||
|
|
7aba29ac67 | ||
|
|
aeb109a64c | ||
|
|
bd85f8838d | ||
|
|
12a5cfff52 | ||
|
|
cdc3a75c88 | ||
|
|
82dbf087a8 | ||
|
|
0760cc91af | ||
|
|
bd80499c74 | ||
|
|
b93fccc075 | ||
|
|
31ab584eee | ||
|
|
ee2c6d51b3 | ||
|
|
6e49cbd099 | ||
|
|
d4878524e8 | ||
|
|
07f4622ffe | ||
|
|
b3ede5f952 | ||
|
|
5a5936d222 | ||
|
|
4632ac1bb9 | ||
|
|
96000dd64d | ||
|
|
47d561b229 | ||
|
|
27ef71859b | ||
|
|
03f9ef0ac6 | ||
|
|
86cb785e58 | ||
|
|
4ff47fe17a | ||
|
|
d18195f581 | ||
|
|
f0942ff7f3 | ||
|
|
6c66f5bbd9 | ||
|
|
509afbb6c9 | ||
|
|
8080e0acef | ||
|
|
87493d9391 | ||
|
|
63ed141522 | ||
|
|
373ec51061 | ||
|
|
f932c5811c | ||
|
|
b54d2b4520 | ||
|
|
f452b8a995 | ||
|
|
715298ed23 | ||
|
|
bbe14dd1ab | ||
|
|
05de774c0a | ||
|
|
6218023a63 | ||
|
|
71cc65ea48 | ||
|
|
21b6348dbe | ||
|
|
32c453d66e | ||
|
|
59637d35b1 | ||
|
|
e600fc00e7 | ||
|
|
acd30088af | ||
|
|
d03de62c07 | ||
|
|
8de1b371da | ||
|
|
fb6d01a7d0 | ||
|
|
e56c04e9ee | ||
|
|
104bea036b | ||
|
|
6faf850793 | ||
|
|
dac0a5b51b | ||
|
|
965b33e5b0 | ||
|
|
b9dfb48d79 | ||
|
|
41aa8edfd2 | ||
|
|
7f955f8bd1 | ||
|
|
8fdc511d0d | ||
|
|
e5035d92e9 | ||
|
|
b54547c08d | ||
|
|
e9510e8a68 | ||
|
|
9d50212a71 | ||
|
|
f2892e4f59 | ||
|
|
f8dbd4f017 | ||
|
|
e5aabf9801 | ||
|
|
36d1ac804a | ||
|
|
da35883633 | ||
|
|
f0318d2c99 | ||
|
|
a7f9d5eb67 | ||
|
|
0f6e008c9a | ||
|
|
3f35b2a718 | ||
|
|
2c70359ad0 | ||
|
|
d98aadbbbf | ||
|
|
8bded3ea39 | ||
|
|
98a562d8b3 | ||
|
|
33e7fdac98 | ||
|
|
7a69be8b59 | ||
|
|
2385bb7069 | ||
|
|
7bc80ccb54 | ||
|
|
405e2427ed | ||
|
|
4fc96decc4 | ||
|
|
4ccd3bb4c6 | ||
|
|
b5d6f1b46a | ||
|
|
409c8b348d | ||
|
|
2b2a72d54e | ||
|
|
8ab99c9b0c | ||
|
|
82d741514a | ||
|
|
f20aa156d0 | ||
|
|
d853432dba | ||
|
|
fbdad8424f | ||
|
|
a9b54b6868 | ||
|
|
d786b90fa1 | ||
|
|
9ef14bdb71 | ||
|
|
458cdd2066 | ||
|
|
a344399b81 | ||
|
|
a0fa7e802c | ||
|
|
71e8dd550d | ||
|
|
2bd52eb7ad | ||
|
|
daf7fad5b9 | ||
|
|
a6bd9c1ebe | ||
|
|
458d981f31 | ||
|
|
dd356b9e6a | ||
|
|
6c4932c4ec | ||
|
|
8231302bca | ||
|
|
83052ab466 | ||
|
|
9c951c232e | ||
|
|
c2adde948f | ||
|
|
631fb8a8d7 | ||
|
|
470639cd68 | ||
|
|
e7a1c20467 | ||
|
|
0cfed5516c | ||
|
|
a829b8f321 | ||
|
|
abc2d32b83 | ||
|
|
11cac26508 | ||
|
|
ccb155437e | ||
|
|
9c1392dfc3 | ||
|
|
8d611bfdaf | ||
|
|
454a21f5b7 | ||
|
|
d275dee3ae | ||
|
|
7d69dda874 | ||
|
|
d1030a1788 | ||
|
|
8e3b7f1caa | ||
|
|
b22abf512f | ||
|
|
ffc23929c6 | ||
|
|
a4a77b3168 | ||
|
|
380314a05f | ||
|
|
de2f28504a | ||
|
|
7878112b93 | ||
|
|
dd074dbafd | ||
|
|
14de1dd287 | ||
|
|
ea8b02b190 | ||
|
|
b394551af8 | ||
|
|
9b751f6e58 | ||
|
|
955bc6643e | ||
|
|
82e8919208 | ||
|
|
778c297214 | ||
|
|
5de9e985f8 | ||
|
|
e43fa39dc9 | ||
|
|
ddfa33ccc1 | ||
|
|
d82ef33fa9 | ||
|
|
c3540f0549 | ||
|
|
bffe408afa | ||
|
|
103864e8b2 | ||
|
|
aa9c7ca030 | ||
|
|
0354a1f4c1 | ||
|
|
127e52de69 | ||
|
|
22055b9bf3 | ||
|
|
a2301b52de | ||
|
|
bc37ea5b79 | ||
|
|
28bea95128 | ||
|
|
2d803e38d8 | ||
|
|
dadeda144a | ||
|
|
a90ec84bee | ||
|
|
c107b45933 | ||
|
|
a69f8cf4a8 | ||
|
|
cfc3abb752 | ||
|
|
6af3fd60d5 | ||
|
|
257feace6e | ||
|
|
f83bc8ddef | ||
|
|
f0b08ccea0 | ||
|
|
1040739fbb | ||
|
|
6cc6165c2b | ||
|
|
0768b4dc84 | ||
|
|
ae1baa0e61 | ||
|
|
5a8e9acbf3 | ||
|
|
2747fce7d3 | ||
|
|
e02918c17b | ||
|
|
02a6359c1f | ||
|
|
b4c1e133c0 | ||
|
|
1107928870 | ||
|
|
bc4ac93fd9 | ||
|
|
c1bca7ce7e | ||
|
|
fefb76beb9 | ||
|
|
42bc1669d7 | ||
|
|
69dcbec8ef | ||
|
|
f405cea5bc | ||
|
|
f57377abb9 | ||
|
|
dd1782c538 | ||
|
|
17cc49fee4 | ||
|
|
bafb2fd130 | ||
|
|
2f679326c2 | ||
|
|
96f2dd65b4 | ||
|
|
ca887d3927 | ||
|
|
6570709226 | ||
|
|
9a5217d9c1 | ||
|
|
4d01bb3ecf | ||
|
|
332c63e440 | ||
|
|
b306c210c6 | ||
|
|
b9015e1c2e | ||
|
|
26c5008136 | ||
|
|
74d6b90d69 | ||
|
|
7b11c62f53 | ||
|
|
8eed54b3da | ||
|
|
4be16dcdf5 | ||
|
|
ded9cabdf4 | ||
|
|
cf6bc6987e | ||
|
|
ff7b1452b1 | ||
|
|
517c1cea25 | ||
|
|
a3e3010e0f | ||
|
|
057c4eb545 | ||
|
|
f720381d43 | ||
|
|
2c9042d62c | ||
|
|
82ef289d3a | ||
|
|
7246b0e002 | ||
|
|
8cfd40aac8 | ||
|
|
5382c9a8e4 | ||
|
|
53de91b2df | ||
|
|
8a36af7c7d | ||
|
|
e9789ce3f4 | ||
|
|
837231c068 | ||
|
|
95025be1bc | ||
|
|
03a964bb2d | ||
|
|
123a16e346 | ||
|
|
9701812bee | ||
|
|
29ac03468e | ||
|
|
bfb7701db1 | ||
|
|
e8b6ff5d7c | ||
|
|
1456907000 | ||
|
|
ec1c521187 | ||
|
|
a7744c24bd | ||
|
|
522e6f2ae8 | ||
|
|
81d4bd81e4 | ||
|
|
687678a2ea | ||
|
|
b0f9071bf5 | ||
|
|
81e2673923 | ||
|
|
75e9fd771b | ||
|
|
863926abd6 | ||
|
|
241e7256f6 | ||
|
|
6556b85abf | ||
|
|
289cabe993 | ||
|
|
d6cf7cfa44 | ||
|
|
4cc2f0eba5 | ||
|
|
97dfaa7526 | ||
|
|
66aa4dbc8b | ||
|
|
dce5d31462 | ||
|
|
9dc3987e02 | ||
|
|
9b238a525a | ||
|
|
ad82aac663 |
No files matched your search
@@ -1,37 +0,0 @@
|
|||||||
# Race, Go. Dispatched by hand, and never a gate on a push or a tag: the release tag is
|
|
||||||
# cut only after `just gates` has already raced the tree, so this workflow is the
|
|
||||||
# explicit second opinion, not a step of the release.
|
|
||||||
#
|
|
||||||
# The race detector roughly doubles both time and memory, which the shared runner box
|
|
||||||
# cannot afford on every push. Locally it belongs to `just gates`, which runs it once per
|
|
||||||
# task; here it is a decision rather than a routine.
|
|
||||||
#
|
|
||||||
# Every step is one command, so the step that fails is the gate that failed.
|
|
||||||
name: Race
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
# One core: parallelism buys no speed here and costs memory the box does not have.
|
|
||||||
GOFLAGS: -p=1
|
|
||||||
GOMAXPROCS: "2"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
race:
|
|
||||||
runs-on: fedora
|
|
||||||
timeout-minutes: 20
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install gcc
|
|
||||||
# The race detector needs cgo and the runner image carries no C compiler.
|
|
||||||
run: dnf install -y gcc
|
|
||||||
|
|
||||||
- name: Race
|
|
||||||
run: go test -race -count=1 -timeout 10m ./...
|
|
||||||
+30
-117
@@ -1,20 +1,28 @@
|
|||||||
# Release, Go binaries. Runs on version tags (v1.2.3) pushed to main.
|
# Release, Go binaries. Runs on version tags (v1.2.3) pushed to main.
|
||||||
#
|
#
|
||||||
# The module sits at the repository root: the toolchain records a version only for a root
|
# No gate runs at the tag: the tagged tree was tested on every push to development,
|
||||||
# module, measured on go1.27.1, so a build of a module in a subdirectory reports (devel)
|
# the full suite is the suite workflow's business, and race never runs in CI at all.
|
||||||
# even at its own <module>/vX.Y.Z tag and this workflow's smoke test can never pass for
|
# This pipeline publishes and nothing else. The version contract has no injection
|
||||||
# it. A Go repository is one module at the root.
|
# step: the toolchain records the tag into the binary's build information, so the
|
||||||
|
# build simply has to happen at the tag, which the trigger guarantees.
|
||||||
#
|
#
|
||||||
# The version contract these steps implement: nothing is injected. The toolchain records
|
# The module sits at the repository root: the toolchain records a version only for a
|
||||||
# the tag into the binary's build information, so the build simply has to happen at the
|
# root module, measured on go1.27.1, so a build of a module in a subdirectory reports
|
||||||
# tag, which the trigger guarantees.
|
# (devel) even at its own <module>/vX.Y.Z tag and this workflow's smoke test can never
|
||||||
|
# pass for it. A Go repository is one module at the root.
|
||||||
#
|
#
|
||||||
# The gates run in their own job, once, before the matrix, minus the race detector: race
|
# The matrix carries the platforms the project ships: Linux on amd64, arm64,
|
||||||
# never runs on a push path or a tag, and the local gate raced this tree before the tag
|
# loong64 and riscv64, and FreeBSD on amd64 and arm64. Go cross-compiles
|
||||||
# was cut. Putting the gates inside the matrix would run the whole suite once per target
|
# FreeBSD natively and the tree builds under CGO_ENABLED=0, which the
|
||||||
# on the box that also hosts the forge. Each job validates the tag for itself rather than
|
# suite's cross-build gates prove for the whole module; the ptrace suite
|
||||||
# passing a value between jobs, so no workflow feature has to be trusted for the version
|
# itself still needs a FreeBSD machine, so nothing FreeBSD runs here.
|
||||||
# to reach the file name.
|
# Nothing is installed: the fedora job image carries git, perl and node
|
||||||
|
# (verified on the runner, 2026-10-04).
|
||||||
|
#
|
||||||
|
# Each job validates the tag for itself rather than passing a value between jobs, so
|
||||||
|
# no workflow feature has to be trusted for the version to reach the file name. Every
|
||||||
|
# step is one command, and the scripted steps are Perl with builtins only: Perl drives
|
||||||
|
# curl through a list, so no argument is ever word-split, globbed or quoted wrong.
|
||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -22,111 +30,16 @@ on:
|
|||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# The box is shared with the forge, so parallelism is bounded on purpose. The gates job
|
# The box is shared with the forge, so parallelism is bounded on purpose.
|
||||||
# needs it most; the build jobs inherit it for their parallel compilation.
|
|
||||||
GOFLAGS: -p=1
|
GOFLAGS: -p=1
|
||||||
GOMAXPROCS: "2"
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gates:
|
|
||||||
runs-on: fedora
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
# Perl for the steps below. The install is a no-op where the package
|
|
||||||
# is already present.
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Validate the tag
|
|
||||||
env:
|
|
||||||
VERSION: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
my $v = $ENV{VERSION} // q{};
|
|
||||||
$v =~ m{^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$}
|
|
||||||
or die qq{ERROR: expected a semver tag like v1.2.3, got: $v\n};
|
|
||||||
print qq{tag $v\n};
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Security policy names this release
|
|
||||||
# The supported-versions table is the one part of SECURITY.md that
|
|
||||||
# carries a version, so it goes stale the moment a tag is cut. Fail
|
|
||||||
# here rather than publish a policy naming the previous release.
|
|
||||||
env:
|
|
||||||
VERSION: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
my $v = $ENV{VERSION} // q{};
|
|
||||||
(my $nv = $v) =~ s/^v//;
|
|
||||||
open(my $f, q{<}, q{SECURITY.md}) or die qq{SECURITY.md: $!\n};
|
|
||||||
local $/;
|
|
||||||
my $t = <$f>;
|
|
||||||
close $f;
|
|
||||||
$t =~ m{^\|\s*\Q$nv\E\s*\|\s*yes\s*\|}m
|
|
||||||
or die qq{ERROR: SECURITY.md does not name $nv as supported; update the table before releasing.\n};
|
|
||||||
print qq{SECURITY.md names $nv\n};
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: go build ./...
|
|
||||||
|
|
||||||
- name: Format
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
|
||||||
my @bad = <$g>;
|
|
||||||
close($g);
|
|
||||||
print @bad;
|
|
||||||
exit(@bad ? 1 : 0);
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Vet
|
|
||||||
run: go vet ./...
|
|
||||||
|
|
||||||
- name: Modernise
|
|
||||||
run: go fix -diff ./...
|
|
||||||
|
|
||||||
- name: Tests
|
|
||||||
# The same command as in test.yml, so the floor is the same number everywhere.
|
|
||||||
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
|
||||||
|
|
||||||
- name: Tests outside the coverage set
|
|
||||||
# The same command as in test.yml: the CLI's exit codes and manual-page guard,
|
|
||||||
# and the debugger's architecture-neutral units, run outside the floor.
|
|
||||||
run: go test -count=1 -timeout 10m ./cmd/... ./debug/...
|
|
||||||
|
|
||||||
- name: Coverage floor
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
|
||||||
my $total;
|
|
||||||
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
|
||||||
close($c);
|
|
||||||
die qq{no total line in coverage.out\n} unless defined $total;
|
|
||||||
printf qq{Total coverage: %s%%\n}, $total;
|
|
||||||
exit($total < 80 ? 1 : 0);
|
|
||||||
'
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 25
|
|
||||||
needs: gates
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Portable targets: amd64, arm64, loong64 and riscv64 on Linux, at the toolchain
|
|
||||||
# default level. No 32-bit, no wasm, no macOS, no Windows. FreeBSD stays out until
|
|
||||||
# verify/jit.go ports off syscall.Mprotect: the Go syscall package defines no
|
|
||||||
# Mprotect for freebsd, and verify/jit.go:50 calls it to drop the write bit from
|
|
||||||
# the JIT mapping, so every freebsd target fails to build with "undefined:
|
|
||||||
# syscall.Mprotect" (verified for amd64, arm64 and riscv64 on go1.27.1).
|
|
||||||
include:
|
include:
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
@@ -136,6 +49,10 @@ jobs:
|
|||||||
goarch: loong64
|
goarch: loong64
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: riscv64
|
goarch: riscv64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: amd64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -144,9 +61,6 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Validate the tag
|
- name: Validate the tag
|
||||||
id: version
|
id: version
|
||||||
env:
|
env:
|
||||||
@@ -209,7 +123,6 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 15
|
|
||||||
needs: build
|
needs: build
|
||||||
permissions:
|
permissions:
|
||||||
# contents: read is required for the checkout: a job that declares any
|
# contents: read is required for the checkout: a job that declares any
|
||||||
@@ -226,9 +139,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Extract the CHANGELOG section
|
- name: Extract the CHANGELOG section
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ gitea.ref_name }}
|
VERSION: ${{ gitea.ref_name }}
|
||||||
@@ -342,7 +252,10 @@ jobs:
|
|||||||
my @cmd = (q{curl}, q{-sS}, q{-o}, q{/dev/null}, q{-w}, q{%{http_code}},
|
my @cmd = (q{curl}, q{-sS}, q{-o}, q{/dev/null}, q{-w}, q{%{http_code}},
|
||||||
q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}},
|
q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}},
|
||||||
q{-H}, q{Content-Type: application/octet-stream},
|
q{-H}, q{Content-Type: application/octet-stream},
|
||||||
q{-X}, q{POST}, q{--data-binary}, qq{@$path},
|
# The @ must not sit inside a qq{} string: there it starts an
|
||||||
|
# array interpolation and the upload body collapses to empty,
|
||||||
|
# which Gitea stores as a 201-created zero-byte attachment.
|
||||||
|
q{-X}, q{POST}, q{--data-binary}, q{@} . $path,
|
||||||
qq{$ENV{GITEA_SERVER_URL}/api/v1/repos/$ENV{GITEA_REPOSITORY}/releases/$id/assets?name=$name});
|
qq{$ENV{GITEA_SERVER_URL}/api/v1/repos/$ENV{GITEA_REPOSITORY}/releases/$id/assets?name=$name});
|
||||||
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
||||||
my $code = <$curl>;
|
my $code = <$curl>;
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Suite, Go. Dispatched by hand, on development. Never a push gate.
|
||||||
|
#
|
||||||
|
# The complete gate set minus race: the build, the FreeBSD cross-builds the
|
||||||
|
# port's compile proof needs (amd64 and arm64, the same two the release
|
||||||
|
# matrix ships), both static gates, the full suite with every short-layer
|
||||||
|
# skip unskipped, and the coverage floor. It is the pipeline form of the
|
||||||
|
# local `just test`, for the moments when the tree must be proven end to end
|
||||||
|
# and nobody is at the keyboard.
|
||||||
|
#
|
||||||
|
# Race never runs in CI. It roughly doubles the time and the memory on a box shared
|
||||||
|
# with the forge, and the local `just gates` races the tree on the machine at the
|
||||||
|
# keyboard, which is where that gate belongs.
|
||||||
|
name: Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# One core: parallelism buys no speed here and costs memory the box does not have.
|
||||||
|
GOFLAGS: -p=1
|
||||||
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
suite:
|
||||||
|
runs-on: fedora
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
# The module is the source of truth for the version, so it cannot drift.
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build ./...
|
||||||
|
|
||||||
|
- name: FreeBSD build (amd64)
|
||||||
|
# The port is pure Go: the cross-build needs no C toolchain and the
|
||||||
|
# release matrix ships the same two binaries.
|
||||||
|
run: CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build ./...
|
||||||
|
|
||||||
|
- name: FreeBSD build (arm64)
|
||||||
|
run: CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build ./...
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
||||||
|
my @bad = <$g>;
|
||||||
|
close($g);
|
||||||
|
print @bad;
|
||||||
|
exit(@bad ? 1 : 0);
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Modernise
|
||||||
|
# Exits non-zero when it has something to rewrite, so it needs no output capture.
|
||||||
|
run: go fix -diff ./...
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
# The full suite over the logic packages (`packages` in the justfile), every
|
||||||
|
# short-layer skip lifted, with the coverage profile. No timeout: a run that
|
||||||
|
# does not finish is a defect to find.
|
||||||
|
run: go test -count=1 -timeout 0 -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
||||||
|
|
||||||
|
- name: Tests outside the coverage set
|
||||||
|
# The same second invocation as the local gate: the CLI's exit codes and
|
||||||
|
# manual-page guard, and the debugger's units, live ptrace sessions included.
|
||||||
|
# They run without a profile, because a thin main and a ptrace-bound package
|
||||||
|
# would drag the floor down rather than measure the product.
|
||||||
|
run: go test -count=1 -timeout 0 ./cmd/... ./debug/...
|
||||||
|
|
||||||
|
- name: Coverage floor
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
||||||
|
my $total;
|
||||||
|
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
||||||
|
close($c);
|
||||||
|
die qq{no total line in coverage.out\n} unless defined $total;
|
||||||
|
printf qq{Total coverage: %s%%\n}, $total;
|
||||||
|
exit($total < 80 ? 1 : 0);
|
||||||
|
'
|
||||||
+24
-46
@@ -1,17 +1,20 @@
|
|||||||
# Test, Go. Push and pull request to development. Never on main.
|
# Test, Go. Push and pull request to development. Never on main.
|
||||||
#
|
#
|
||||||
# The gates are the ones the justfile's `gates` recipe runs, minus race: the shared
|
# The push path owns a two-minute budget end to end, so it carries exactly the gates
|
||||||
# runner box cannot afford the race detector on every push, so it lives in race.yml.
|
# that fit it: the format check, go vet, the suite's short layer and the coverage
|
||||||
# The box is one core and 2 GB beside Gitea, so parallelism is bounded on purpose and
|
# floor. There is no build step (go test compiles what it runs) and the modernisation
|
||||||
# everything runs in one job. Extra jobs would duplicate the checkout, the Go setup and
|
# gate (`go fix -diff`) belongs to the suite workflow, which is dispatched by hand.
|
||||||
# the dependency download three times without buying any parallelism.
|
# Nothing is installed: the fedora job image carries git, perl and node (verified on
|
||||||
|
# the runner, 2026-10-04), and no pipeline ever installs gcc or runs the race detector.
|
||||||
#
|
#
|
||||||
# Every step is one command, so the step that fails is the gate that failed, and no shell
|
# The live ptrace sessions and the other deliberate-run categories skip under
|
||||||
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and
|
# testing.Short: they need the machine to themselves and belong to the suite workflow
|
||||||
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over
|
# and to the local `just test`, never to every push.
|
||||||
# on ash, and it is one language instead of two. The Perl uses builtins only, because
|
#
|
||||||
# Fedora packages the Perl modules separately and nothing beyond `perl` itself may be
|
# Every step is one command, so the step that fails is the gate that failed, and no
|
||||||
# assumed present.
|
# shell option has to be trusted for the run to stop. The scripted steps are Perl with
|
||||||
|
# builtins only: Fedora packages the Perl modules separately, so nothing beyond `perl`
|
||||||
|
# itself may be assumed present, and there is no bashism to trip over.
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -36,7 +39,6 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -46,16 +48,6 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
# The runner images are minimal and Perl is not guaranteed. The install is a
|
|
||||||
# no-op where it is already present; drop this step once verified on the box.
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
# The steps follow the `gates` order of the justfile contract: build, format,
|
|
||||||
# vet, test. The vet gate is go vet and go fix -diff, two steps here.
|
|
||||||
- name: Build
|
|
||||||
run: go build ./...
|
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: |
|
run: |
|
||||||
perl -e '
|
perl -e '
|
||||||
@@ -69,33 +61,19 @@ jobs:
|
|||||||
- name: Vet
|
- name: Vet
|
||||||
run: go vet ./...
|
run: go vet ./...
|
||||||
|
|
||||||
- name: Modernise
|
|
||||||
# Exits non-zero when it has something to rewrite, so it needs no output capture.
|
|
||||||
run: go fix -diff ./...
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
# The suite must be fast: a push pipeline that cannot finish in a few minutes moves
|
# The pattern is `packages` in the project's justfile, so the floor is the
|
||||||
# its heavy part behind a dispatch. The inner timeout matches the job's, so a
|
# same number the local gate reports: the logic packages, since a thin cmd/
|
||||||
# hanging test reports its own goroutine dump rather than a silent job kill.
|
# and a ptrace-bound debug package would drag the total under it. No timeout
|
||||||
# The pattern is `packages` in the project's justfile: the logic packages, since a
|
# anywhere: `-timeout 0` disables go test's own ten-minute default, because a
|
||||||
# thin cmd/ would drag the total under the floor. release.yml runs the same
|
# run that does not finish is a defect to find and a timeout only hides it.
|
||||||
# command, so the floor is the same number everywhere. ./verify/... carries the
|
run: go test -short -count=1 -timeout 0 -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
||||||
# live oracle-parity comparison against `go tool asm` (the TestGroundTruth
|
|
||||||
# suites); the runner's Go setup provides both the tool and GOROOT.
|
|
||||||
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
|
||||||
|
|
||||||
- name: Tests outside the coverage set
|
- name: Tests outside the coverage set
|
||||||
# The CLI and the debugger sit outside `packages` because a thin main and a
|
# The CLI's exit codes and manual-page guard and the debugger's
|
||||||
# ptrace-bound package pull the total under the floor, but their tests guard
|
# architecture-neutral units, still tested but outside the profile the floor
|
||||||
# shipped surfaces: the command exit codes, the manual pages against the
|
# is computed from. `-short` skips the debugger's live ptrace sessions.
|
||||||
# binary's own help, and the debugger's architecture-neutral units. They run
|
run: go test -short -count=1 -timeout 0 ./cmd/... ./debug/...
|
||||||
# here so the floor stays a product measure and nothing is left untested.
|
|
||||||
run: go test -count=1 -timeout 10m ./cmd/... ./debug/...
|
|
||||||
|
|
||||||
- name: Oracle parity
|
|
||||||
# Re-run the live go-tool-asm comparison as its own step so that a parity
|
|
||||||
# regression names the gate that failed instead of hiding inside the suite.
|
|
||||||
run: go test -count=1 -timeout 10m -run 'TestGroundTruth' ./verify/...
|
|
||||||
|
|
||||||
- name: Coverage floor
|
- name: Coverage floor
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
coverage.out
|
coverage.out
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
# The fuzz campaigns' cache: committed seeds live in the test file, crashes are
|
||||||
|
# minimised into it, so everything go test -fuzz writes under testdata/fuzz is
|
||||||
|
# transient. pending-* holds minimised reproductions awaiting their owner's fix.
|
||||||
|
/asm/testdata/fuzz/
|
||||||
|
|
||||||
# Crash dumps from the emulator runs
|
# Crash dumps from the emulator runs
|
||||||
core
|
core
|
||||||
core.*
|
core.*
|
||||||
|
|||||||
+380
-10
@@ -1,15 +1,304 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to gasm-devkit are documented here.
|
All notable changes to gasm-sdk are documented here.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [development]
|
## [development]
|
||||||
|
|
||||||
|
## [0.36.0] - 2026-10-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **The GOROOT instruction wave, part 1.** The encoder now covers the
|
- **The SVE2 and SVE2.1 instruction sets on arm64.** Over 350 SVE
|
||||||
|
mnemonics across 537 encoded forms join the extension layer: the
|
||||||
|
narrowing two-to-one arithmetic family, the SVE2 cryptographic set
|
||||||
|
including ZADCLB, BFloat16 arithmetic, predicate counters and
|
||||||
|
reductions, the pairwise and quadword forms, multiple-structure loads
|
||||||
|
and stores (LD2/ST2 through LD4/ST4), shift by vector and the
|
||||||
|
shift-immediate scheme with its element-size encoding, CLASTA and
|
||||||
|
CLASTB, the last-active and compare predicate families, and the
|
||||||
|
vector-length arithmetic ADDVL, ADDPL and RDVL. Every form derives
|
||||||
|
from the toolchain's own encoder tables and is pinned against the
|
||||||
|
GOROOT corpus words.
|
||||||
|
- **The complete AVX512-FP16 set and AVX-VNNI-INT16 on amd64.** The
|
||||||
|
extension layer reaches 191 entries: the packed and scalar FMA
|
||||||
|
families (VFMADD, VFMSUB, VFMADDSUB and VFMSUBADD in the PH widths
|
||||||
|
with their SH mirrors), the complex multiply and complex FMA pairs
|
||||||
|
(VFMULC, VFCMULC, VFMADDC, VFCMADDC, packed and scalar, each
|
||||||
|
conjugating the source its prefix names), VMINMAXPH and VMINMAXSH
|
||||||
|
under their imm8 control, and the AVX-VNNI-INT16 dot products
|
||||||
|
(VPDPWSUD, VPDPWSUDS, VPDPWUSD, VPDPWUSDS) through a new VEX
|
||||||
|
encoding path beside the EVEX one. Every entry carries golden
|
||||||
|
vectors byte-checked against GNU as.
|
||||||
|
- **Extension-layer instructions assemble straight from `.s` source on
|
||||||
|
amd64.** The registered mnemonics dispatch from the assembly front
|
||||||
|
end with their operand grammar: `k0` through `k7` write masks with
|
||||||
|
merging and zeroing, `{1toN}` broadcast, embedded rounding and
|
||||||
|
`{sae}`, memory and scaled-index operands, and the imm8-control
|
||||||
|
forms, the VEX forms beside the EVEX ones. `gasm lint` surfaces the
|
||||||
|
layer's refusals as `extension-form` errors instead of letting a bad
|
||||||
|
shape reach the encoder, and every distinct mnemonic is pinned from
|
||||||
|
source text against the registry's bytes.
|
||||||
|
- **arm64 output byte-identical with the toolchain across the GOROOT
|
||||||
|
tree.** 31 of the 36 files the differential harness walks (90
|
||||||
|
functions, 27408 bytes) now assemble without a differing byte: the
|
||||||
|
missing shapes are in (GETCALLERPC, the REM, REMW, UREM and UREMW
|
||||||
|
family, DWORD, the FCVTHS, FCVTSH, FCVTDH and FCVTHD conversions, TLS
|
||||||
|
local-exec loads as a single MOVZ with the TLS relocation), the
|
||||||
|
literal pool drains mid-function when a function's literals would
|
||||||
|
overflow the ±512 KiB load-literal range, and frame-relative
|
||||||
|
addresses and flag-setting logicals materialise the way
|
||||||
|
cmd/internal/obj/arm64 writes them.
|
||||||
|
- **riscv64 END and GETCALLERPC.** END is accepted anywhere and emits
|
||||||
|
nothing, and GETCALLERPC reads the return address the toolchain's
|
||||||
|
rewrite describes: a leaf function reads LR, a framed body reads the
|
||||||
|
prologue's save slot, with the compressed spellings where they fit.
|
||||||
|
- **The loong64 register-pair spelling.** `MULV R4:R5, R6` parses the
|
||||||
|
way the toolchain's pair sugar does: the colon splits the operand and
|
||||||
|
the halves swap, any instruction shape takes the spelling, and the
|
||||||
|
malformed forms fail with the toolchain's own wording.
|
||||||
|
- **Fuzz targets across the tool.** The assembler carries
|
||||||
|
per-architecture FuzzAssemble targets and the front end carries
|
||||||
|
targets over the lexer, parser, formatter and the extension
|
||||||
|
registry; the seed corpora run as ordinary tests, and `just fuzz`
|
||||||
|
drives the campaigns locally. Hardening the targets found caps GLOBL
|
||||||
|
and DATA at 64 MiB per symbol and 128 MiB per section, so a crafted
|
||||||
|
input cannot materialise unbounded memory.
|
||||||
|
- **The FreeBSD port of the debugger.** `gasm debug` runs on FreeBSD on
|
||||||
|
amd64 and arm64 with the same interactive surface as on Linux:
|
||||||
|
breakpoints, hardware watchpoints (x86 debug registers, the arm64 debug
|
||||||
|
register file), single-stepping, register and memory access, all behind
|
||||||
|
the kernel's own ptrace requests, with tracee memory through `PT_IO` and
|
||||||
|
stop reports through `PT_LWPINFO`. The JIT substrate maps executable
|
||||||
|
memory through `golang.org/x/sys/unix`, so `verify` builds on FreeBSD
|
||||||
|
too. The dispatched suite compile-gates both architectures, the
|
||||||
|
release pipeline ships the two binaries, and live validation awaits a
|
||||||
|
FreeBSD machine.
|
||||||
|
- **Workspace-wide navigation in the language server.** `gasm lsp` indexes
|
||||||
|
the `.s` files under the workspace root beyond the documents the editor
|
||||||
|
has open, so go-to-definition, find references and workspace symbol search
|
||||||
|
reach files that were never opened. An open buffer always shadows its
|
||||||
|
disk copy, and watched-file events together with a per-query freshness
|
||||||
|
check keep the index current.
|
||||||
|
- **Quick fixes for the textflag include and the argument area.** The
|
||||||
|
`missing-textflag-include` warning offers to add the include after the
|
||||||
|
last one in the file, and the `abi-argsize` warning offers to set the
|
||||||
|
TEXT argument area to the size the `// func` signature implies, computed
|
||||||
|
by the new `lint.ExpectedArgSize`.
|
||||||
|
- **Eleven new lint rules over the directives, the data section and the
|
||||||
|
sharpest addressing edges.** `missing-argsize` flags a TEXT that
|
||||||
|
declares no argument area its `// func` signature implies;
|
||||||
|
`noframe-frame-size` a NOFRAME with a positive frame;
|
||||||
|
`unnamed-fp-reference` a nameless `0(FP)`, which both assemblers
|
||||||
|
reject; `hardware-sp-addressing` a negative offset off the hardware SP
|
||||||
|
rather than the virtual frame; `vex-sse-mixing` a kernel that mixes VEX
|
||||||
|
and legacy SSE spellings and pays the transition penalty;
|
||||||
|
`unnamed-result` a `ret+N(FP)` the signature names; `data-width`,
|
||||||
|
`data-value-overflow`, `data-string-width`, `data-without-globl` and
|
||||||
|
`data-exceeds-globl` police the DATA width against its value type and
|
||||||
|
the GLOBL size behind it. `missing-ret` now also flags a function
|
||||||
|
whose tail can fall off its end even though a RET sits somewhere in the
|
||||||
|
body, and `invalid-textflag` also reports a flag misplaced between TEXT
|
||||||
|
and GLOBL.
|
||||||
|
- **Hover documentation and wider completions in the language server.**
|
||||||
|
Hovering a directive or pseudo-operation (TEXT, DATA, GLOBL, PCALIGN,
|
||||||
|
FUNCDATA, PCDATA, the BYTE family) shows its grammar and rules in
|
||||||
|
preference to the empty instruction-table entry, and completion offers
|
||||||
|
those names beside the instruction set. The `missing-argsize` warning
|
||||||
|
carries a quick fix that declares the argument area the signature
|
||||||
|
implies (`$0` becomes `$0-16`).
|
||||||
|
- **The GOOBJ link parity gate.** A regression test assembles a kernel
|
||||||
|
per architecture through `gasm asm --format goobj`, substitutes the
|
||||||
|
object into a real `go build`'s package archive, proves the archive
|
||||||
|
carries it byte for byte and re-links with cmd/link on amd64, arm64,
|
||||||
|
riscv64 and loong64. The binaries run, natively on amd64 and under
|
||||||
|
qemu-user on arm64 and riscv64, and their output must match the
|
||||||
|
toolchain-built baseline; loong64 is link-only. The pipeline installs
|
||||||
|
qemu-user and runs the gate on every push.
|
||||||
|
- **The amd64 and loong64 encoders close four more corpus files.** The
|
||||||
|
whole-tree measure moves to 272 of 322 (84.5 %): amd64 gains the
|
||||||
|
one-operand IMUL, the SSE compare family (CMPPD, CMPPS, CMPSS), RETFL,
|
||||||
|
the LOOP family, the MMX register bank with its bank-crossing moves,
|
||||||
|
MOVNTDQ, the CR and DR register moves, PUSH and POP of FS and GS, the
|
||||||
|
`(TLS)` pseudo-base, the wait and cache controls (CLWB, CLDEMOTE,
|
||||||
|
TPAUSE, UMONITOR, UMWAIT, RDPID, ENDBR64), indirect branches with the
|
||||||
|
star spelling (`JMP *(R12)(R13*4)`), `RET sym(SB)` as the tail jump,
|
||||||
|
the colon shift spelling (`SHLL CX, R11:AX`) and the EVEX and VEX forms
|
||||||
|
of the rounds, AES key assist, string compares, extracts, blends and
|
||||||
|
permutes; loong64 gains the acquire and release pair (LLACQ, SCREL,
|
||||||
|
with the vector widths), the VMOVQ and XVMOVQ lane forms and the BYTE
|
||||||
|
literal-data escape hatch the other architectures already take.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **TEXT flag operands behave like the toolchain's.** A numeric or
|
||||||
|
parenthesised flag list (`TEXT ·f(SB), 4, $4096-0`,
|
||||||
|
`(NOSPLIT|NOFRAME)`) now suppresses the prologue and stack guard the
|
||||||
|
names suppress, where the numbers were silently ignored and the
|
||||||
|
prologue was emitted anyway; unknown flag names are rejected with the
|
||||||
|
toolchain's wording instead of passing unnoticed; and the toolchain's
|
||||||
|
own TEXT diagnostics fire, `ABIInternal requires NOSPLIT` and
|
||||||
|
`NOFRAME functions must have a frame size of 0`.
|
||||||
|
- **The disassembler names what it used to print raw.** The 195 amd64
|
||||||
|
encodings that decoded to anonymous renders now decode to their
|
||||||
|
instructions and round-trip, the BMI1 and BMI2 VEX set, RDSEED, RDPID,
|
||||||
|
the WAITPKG family, ENDBR, CLDEMOTE, UD1 and RORX among them, with the
|
||||||
|
arm64 HVC, SMC and SB forms and 74 loong64 rendering corrections
|
||||||
|
beside.
|
||||||
|
- **Variadic macro parameters are rejected like the toolchain rejects
|
||||||
|
them.** A macro declaration whose parameter list the toolchain
|
||||||
|
refuses no longer parses into a broken definition.
|
||||||
|
- **The corpus audit assembles like the build.** A file's `//go:build`
|
||||||
|
constraint decides which target architectures attempt it: cpu_x86.s is
|
||||||
|
an x86 build alone, and the msan and goexperiment.runtimesecret trees
|
||||||
|
are compiled by no supported build, so they leave the measured set
|
||||||
|
instead of failing it. The headline now reads "assemble for every
|
||||||
|
applicable target": every real-code GOROOT assembly file, the tree
|
||||||
|
without testdata, assembles for all four architectures (250 of 250,
|
||||||
|
100 %); over the whole tree including testdata the measure is 272 of
|
||||||
|
322 (84.5 %).
|
||||||
|
- **The module moves to `sourcedock.dev/petrbalvin/gasm-sdk`.** The
|
||||||
|
repository and the module rename together with the product, now the
|
||||||
|
GAsm Software Development Kit. Fresh installs become
|
||||||
|
`go install sourcedock.dev/petrbalvin/gasm-sdk/cmd/gasm@latest`, and
|
||||||
|
installs pinned to the old `gasm-sdk` path stop resolving once the
|
||||||
|
repository takes the new name: reinstall from the new path. The
|
||||||
|
binary stays `gasm`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **riscv64 memory offsets beyond the 12-bit immediate no longer
|
||||||
|
truncate silently.** `LD 4096(X6), X5` encoded as `LD X5, 0(X6)` and
|
||||||
|
read the wrong address; the expansion the toolchain performs (the
|
||||||
|
upper bits into its temporary register, then the access) is emitted
|
||||||
|
now, byte-identically, and offsets the toolchain refuses are refused
|
||||||
|
with its wording.
|
||||||
|
- **riscv64 operand acceptance matches the toolchain's.** 398 operand
|
||||||
|
shapes the toolchain rejects assembled without complaint (a float
|
||||||
|
register where an integer one is required, out-of-range CSRs, vector
|
||||||
|
shapes against the wrong bank, MOV family widths), and 488 more
|
||||||
|
produced the wrong diagnostic; all now fail with the toolchain's
|
||||||
|
texts, and a 1030-case error-parity catalogue pins them against the
|
||||||
|
toolchain's own negative files.
|
||||||
|
- **The debugger survives its failure paths.** Killing a debuggee that
|
||||||
|
had already died or sat ptrace-stopped could block the session
|
||||||
|
forever; Kill now wakes the tracee, signals and reaps it without
|
||||||
|
waiting on a parked child, so every launch-failure path returns
|
||||||
|
instead of hanging the tool.
|
||||||
|
- **Encoder defects found by fuzzing and the differential audits.** A
|
||||||
|
GLOBL with a negative size panicked the assembler and a small crafted
|
||||||
|
input materialised 3.92 GiB of DATA; certain SVE VTBL operands
|
||||||
|
panicked; an over-strict guard refused BIC into RSP where the
|
||||||
|
toolchain assembles it; byte-register operands now select the 8-bit
|
||||||
|
forms the toolchain picks (`XADDL DL, DL` and its companions); and
|
||||||
|
the loong64 shift compositions and indexed-offset forms that dropped
|
||||||
|
fields re-encode faithfully.
|
||||||
|
- **Formatter edges around labels and macros.** A stacked label that
|
||||||
|
names a macro stays attached to it, macro content behind a label
|
||||||
|
stays on its line, a selector-folded label resolves to its macro, an
|
||||||
|
empty TEXT body formats, and a file ending in a comment no longer
|
||||||
|
loses the comment.
|
||||||
|
- **Rename edits land in their own documents.** A rename collected the
|
||||||
|
ranges of every reference across the open documents but applied them all
|
||||||
|
to the document that started it, so renaming a symbol used in a second
|
||||||
|
file moved that file's text into the first. Each edit now applies to the
|
||||||
|
document it was collected in.
|
||||||
|
- **Negative numeric PC-relative jumps.** `JMP -3(PC)`, the shape the
|
||||||
|
runtime's exit loops write (sys_linux_amd64.s, sys_netbsd_amd64.s),
|
||||||
|
resolved to nothing: only the forward forms counted. A negative count
|
||||||
|
now walks the same instruction statements backwards, labels excluded,
|
||||||
|
byte-identical with the toolchain.
|
||||||
|
- **The arm64 move-wide family reads its immediate as an unsigned
|
||||||
|
pattern.** `MOVK $(40000<<48)` folds to a negative int64 and was
|
||||||
|
rejected; the toolchain picks the 16-bit lane from the 64-bit bit
|
||||||
|
pattern, so the encoder now does the same, and a zero immediate is
|
||||||
|
rejected where the toolchain rejects it.
|
||||||
|
- **NOPTR data emits its own symbol kind.** A GLOBL with NOPTR was
|
||||||
|
emitted as plain SDATA, the kind the linker holds to its Go type
|
||||||
|
information requirement, so every gasm object carrying runtime-shaped
|
||||||
|
data (`GLOBL ·x(SB), NOPTR, ...`) died in cmd/link with "missing Go
|
||||||
|
type information". NOPTR data is now SNOPTRDATA, the toolchain's kind
|
||||||
|
for pointer-free globals, and RODATA still wins where both flags
|
||||||
|
appear, exactly as the toolchain chooses. The three end-to-end link
|
||||||
|
tests that should have caught this substituted the gasm object after
|
||||||
|
the package archive was already packed, so they passed vacuously; they
|
||||||
|
now substitute inside the archive, prove the substitution byte for
|
||||||
|
byte and re-link.
|
||||||
|
- **Latent encoder divergences against the toolchain, found by the
|
||||||
|
whole-file differential harness.** On loong64, `MOVx $off(reg)` lost
|
||||||
|
its base register, SCQ swapped its operand fields, the vector lane
|
||||||
|
inserts did not scale offsets by the element width and two immediate
|
||||||
|
opcodes were mistyped; on amd64, NOP with operands encoded 0x90 where
|
||||||
|
the toolchain emits nothing at all, the VEX gather length bit ignored
|
||||||
|
the VSIB index width, four permute and extract families were pushed
|
||||||
|
into EVEX where the toolchain stays VEX, and a reference to a static
|
||||||
|
symbol no GLOBL defines failed where the toolchain defers it to the
|
||||||
|
linker as an external relocation.
|
||||||
|
- **Seven front-end defects found by fuzzing.** The formatter swallowed
|
||||||
|
the statement after a leading block comment (`/* head */ MOVQ AX, BX`
|
||||||
|
formatted to the comment alone), dropped trailing tokens after an
|
||||||
|
`#include` header, and trimmed the trailing whitespace inside a string
|
||||||
|
literal; the parser peeled only one label of a stacked pair (`a: b:`
|
||||||
|
parsed differently than it formatted); deeply nested parentheses in an
|
||||||
|
immediate overflowed the stack through the constant folder, which now
|
||||||
|
stops at a bounded depth and falls back to the ordinary operand paths;
|
||||||
|
macro expansion is linear in the invocation count instead of
|
||||||
|
quadratic; and amplifying macros (the billion-laughs shape) stop at a
|
||||||
|
work budget sized by the line and the macro table, reported as a
|
||||||
|
diagnostic instead of running for hours.
|
||||||
|
- **The debugger handles the edges its tests now reach.** Memory reads
|
||||||
|
and writes cover exactly the requested bytes, so a request ending in
|
||||||
|
the last page of a mapping no longer fails on the unmapped page behind
|
||||||
|
it; disassembly shrinks its instruction window at a mapping's end
|
||||||
|
instead of failing; a debuggee that dies before signalling readiness
|
||||||
|
writes a failure notice the launcher reads, so launch fails fast with
|
||||||
|
the reason instead of after the whole poll (and the poll no longer
|
||||||
|
waits on the child, which could consume the SIGSTOP park and hang the
|
||||||
|
launch); amd64 watchpoints acknowledge the sticky DR6 hit bits and
|
||||||
|
clear the address register on release; the breakpoint listing is
|
||||||
|
ordered by address so its numbering is stable; the REPL rejects bad
|
||||||
|
counts, sizes and watchpoint types instead of silently guessing,
|
||||||
|
reports stops on signals during stepping, and a breakpoint-class trap
|
||||||
|
that matches no breakpoint and leaves the PC in place surfaces instead
|
||||||
|
of spinning the continue loop and the coverage run forever.
|
||||||
|
|
||||||
|
## [0.35.0] - 2026-09-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **The go_asm.h generator.** `gasm asm` generates the package's go_asm.h
|
||||||
|
itself when an assembly file includes it: the Go files beside the source
|
||||||
|
are type-checked for the target architecture and the constants and field
|
||||||
|
offsets become assembler defines, so package-context files assemble with
|
||||||
|
no compiler and no `go build` in the loop. `-GOOS` selects the
|
||||||
|
type-checking GOOS for GOOS-specific files, and the corpus audit derives
|
||||||
|
the GOOS from the file name.
|
||||||
|
- **ELF data relocations on arm64, riscv64 and loong64.** `gasm asm
|
||||||
|
--format elf` emits `.rela.data` for symbol-valued DATA initialisers on
|
||||||
|
every architecture (amd64 carried them already), so standalone ELF
|
||||||
|
objects link on all four targets.
|
||||||
|
- **Corpus failure listing.** `gasm audit-instructions --corpus --list`
|
||||||
|
prints every failing file with its failure reason, per architecture,
|
||||||
|
instead of one representative file per reason.
|
||||||
|
- **DATA with symbol values and relaxed symbol spellings.** DATA
|
||||||
|
initialisers accept `$symbol(SB)` values, laid down as an absolute
|
||||||
|
relocation at the data field (GOOBJ on all four architectures and ELF
|
||||||
|
on all four as of this release), and U+2215 is accepted inside symbol
|
||||||
|
package paths.
|
||||||
|
- **Macro expansion and include splicing.** `gasm asm`, `gasm diff` and
|
||||||
|
`gasm audit-instructions` now preprocess assembly the way the
|
||||||
|
toolchain does: object and parameterised `#define` macros expand at
|
||||||
|
the point of use, `#undef` and the `#ifdef`/`#ifndef`/`#else`/
|
||||||
|
`#endif` family select branches, `#include` splices headers resolved
|
||||||
|
through the source directory and the new repeatable `-I` flag, `;`
|
||||||
|
separates statements, and constant expressions left in operands
|
||||||
|
(`$(32-7)`, `$~63`, `(index*4)(base)`) fold at parse. Expansion
|
||||||
|
happens only on the assembly path: `gasm lint`, `gasm fmt` and the
|
||||||
|
language server keep reading the raw file.
|
||||||
|
- **Encoder coverage: the instruction families GOROOT's real code
|
||||||
|
uses.** The encoder now covers the
|
||||||
instruction families GOROOT's real code uses that gasm lacked,
|
instruction families GOROOT's real code uses that gasm lacked,
|
||||||
byte-verified against `go tool asm`: on amd64 the carry ALU, the
|
byte-verified against `go tool asm`: on amd64 the carry ALU, the
|
||||||
atomics (CMPXCHG, XADD, XCHG), AES-NI, SHA-1/256, PCLMULQDQ, CRC32,
|
atomics (CMPXCHG, XADD, XCHG), AES-NI, SHA-1/256, PCLMULQDQ, CRC32,
|
||||||
@@ -25,14 +314,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
Also fixed on the way: arm64 `CASD`/`CASW` lacked an opcode bit, and
|
Also fixed on the way: arm64 `CASD`/`CASW` lacked an opcode bit, and
|
||||||
riscv64 `VSETVLI` with an immediate length now canonicalises to
|
riscv64 `VSETVLI` with an immediate length now canonicalises to
|
||||||
`vsetivli` as the toolchain does.
|
`vsetivli` as the toolchain does.
|
||||||
- **The corpus audit measures honestly.** Files named for Go ports gasm
|
- **Encoder coverage: quad-register AVX-512 and floating-point
|
||||||
does not target (arm, 386, s390x, ...) are no longer attempted for the
|
immediates.** The encoder gains the
|
||||||
four supported architectures (no supported build compiles them), and
|
quad-register AVX-512 families (4FMAPS, 4FNMADD, 4VNNIW, VP4DPWSSD,
|
||||||
the headline rate is reported over attemptable files: 136 of 433 on
|
VP4DPWSSDS) with the register list riding the inverted V'VVVV field,
|
||||||
the full corpus (31.4 %), 135 of 383 on real code (35.2 %), from the
|
floating-point immediates on the SSE scalar moves and arithmetic
|
||||||
127 that the previous release measured. The probe battery that
|
(the constant lands in a synthesised read-only pool, a positive zero
|
||||||
decides encodability gained the operand shapes the new families use.
|
collapses to XORPS exactly as the toolchain does), accept-and-ignore
|
||||||
-
|
FUNCDATA and PCDATA, three-operand double shifts, static-symbol
|
||||||
|
operands for the legacy SSE moves, and the pooled 64-bit immediate
|
||||||
|
materialisation on riscv64. The parser carries bracketed register
|
||||||
|
ranges, index-only VSIB memory operands and bare trailing immediates;
|
||||||
|
macro substitution reaches parameters used with element suffixes
|
||||||
|
(`A.S4`), and `;` separates statements in plain files.
|
||||||
|
- **Per-architecture reference pages.** [docs/asm/](docs/asm/README.md)
|
||||||
|
gains AMD64, ARM64, RISCV64 and LOONG64: the register files and the
|
||||||
|
roles the ABI fixes, addressing, operand order with every special form,
|
||||||
|
constants and materialisation, alignment, fences and the relocations
|
||||||
|
each target emits. An instruction inventory appendix per architecture
|
||||||
|
is generated from the toolchain's own tables by `just gen`, and the
|
||||||
|
regenerated tables recognise 147 more mnemonics than the previous
|
||||||
|
release carried (arm64 107, riscv64 31, loong64 9).
|
||||||
|
- **The Plan 9 assembly language reference.** [docs/asm/](docs/asm/README.md)
|
||||||
|
opens the complete language reference with its common core: the lexicon,
|
||||||
|
statement structure and constant expressions, the operand grammar with
|
||||||
|
the pseudo-registers and symbol naming, the directives and the function
|
||||||
|
flag vocabulary, preprocessing with `#define` and `#include`, and the
|
||||||
|
Go-embedded layer (ABI0, prototypes, `go_asm.h`, `funcdata.h` and the
|
||||||
|
runtime contract). Every claim is verified against `go tool asm` of
|
||||||
|
Go 1.27.1 and gasm's differential tests; the per-architecture pages and
|
||||||
|
generated instruction appendices follow.
|
||||||
|
- **GOOBJ format specification.** [docs/GOOBJ.md](docs/GOOBJ.md)
|
||||||
|
documents the Go object file format in full: both containers, the 96
|
||||||
|
byte header and all 19 blocks, every structure with its byte
|
||||||
|
offsets, symbol kinds and flag bits, all 106 relocation types with
|
||||||
|
the weak variants, aux symbols, the FuncInfo payload, the pc-value
|
||||||
|
table encoding, the content hashes and the builtin table, all
|
||||||
|
verified byte for byte against objects produced by Go 1.27.1's own
|
||||||
|
tools.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **The corpus audit measures like a build.** Files named for a Go port
|
||||||
|
gasm does not target (arm, 386, s390x, ...) are never attempted, because
|
||||||
|
no supported build compiles them; the GOOS comes from the file name; and
|
||||||
|
each target's go_asm.h is generated on the fly. The headline is reported
|
||||||
|
over attemptable files: 291 of 353 on the full corpus (82.4 %) assemble
|
||||||
|
for every target architecture and 295 of 303 on real code (97.4 %),
|
||||||
|
against 108 of 627 over all files (17.2 %) that the previous release
|
||||||
|
measured.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **The operand forms GOROOT writes.** Numeric PC-relative jumps
|
||||||
|
(`JEQ 2(PC)`, the park loop `JMP 0(PC)`) resolve with the toolchain's
|
||||||
|
own instruction counting and fold jump-to-jump chains exactly as its
|
||||||
|
branch optimiser does; symbol immediates (`MOVQ $sym(SB), AX`)
|
||||||
|
assemble to the toolchain's RIP-relative LEA with an R_PCREL
|
||||||
|
relocation; negated constant expressions in operands (`ADJSP
|
||||||
|
$-(REGS - 8)`, the shape the cgo ABI macros write) fold; the immediate
|
||||||
|
multiply (`IMULQ $1000000000, AX`) encodes with the toolchain's
|
||||||
|
0x69/0x6B selection; the TLS access pair assembles as the toolchain's
|
||||||
|
one-instruction form (the bare `MOVQ TLS, r` load nops out and
|
||||||
|
`off(r)(TLS*1)` folds to the segment-prefixed absolute whose disp32
|
||||||
|
carries the R_TLSLE relocation, per-GOOS); arm64 accepts the
|
||||||
|
bare-register indirect branch (`BL R9` beside `BL (R9)`, both BLR) and
|
||||||
|
the zero-immediate store (`MOVD $0, mem` through the zero register,
|
||||||
|
rejecting non-zero immediates as the toolchain does); `PCALIGN` now
|
||||||
|
aligns on amd64, padding with the toolchain's greedy
|
||||||
|
single-instruction NOPs; the segment-absolute forms (`MOVQ 0x30(GS),
|
||||||
|
AX` and the store direction) and the absolute crash-store
|
||||||
|
(`MOVL $0xf1, 0xf1`) encode; and `gasm asm` predefines the
|
||||||
|
`GOARCH_<arch>` and `GOOS_<goos>` macros the go command passes to
|
||||||
|
`go tool asm`, so GOROOT headers' `#ifdef GOARCH_amd64` platform
|
||||||
|
blocks (`go_tls.h`'s `get_tls` and friends) select as intended. The
|
||||||
|
GOROOT corpus measure moves to 291 of 353 files assembling for every
|
||||||
|
target architecture (82.4 %), 97.4 % of the real-code corpus, from
|
||||||
|
70.8 % and 82.2 %.
|
||||||
|
- **Tool corrections across the pipeline.** The formatter keeps square
|
||||||
|
brackets in SIMD operands, statement separators and canonical macro
|
||||||
|
bodies; the linter drops false positives on shift counts, SETcc
|
||||||
|
spellings and ABIInternal references; the lexer treats a trailing
|
||||||
|
carriage return as a line end so comment text stays idempotent; and
|
||||||
|
arm64 rejects bare BTI with a diagnostic while accepting the full
|
||||||
|
family.
|
||||||
|
|
||||||
## [0.34.0] - 2026-09-20
|
## [0.34.0] - 2026-09-20
|
||||||
|
|
||||||
@@ -129,6 +494,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **The corpus audit attempts fewer files that no build would compile.**
|
||||||
|
Files named for Go ports gasm does not target (arm, 386, s390x, ...)
|
||||||
|
are reported as other-port and never attempted, the headline rate is
|
||||||
|
computed over attemptable files, and the audit searches the
|
||||||
|
toolchain's shipped headers (funcdata.h and friends) automatically.
|
||||||
- **riscv64 JALR silently jumped to the wrong register.** The trampoline
|
- **riscv64 JALR silently jumped to the wrong register.** The trampoline
|
||||||
form `JALR X0, 0(X5)` read the memory operand's base as the destination,
|
form `JALR X0, 0(X5)` read the memory operand's base as the destination,
|
||||||
encoding a jump to X0 with no diagnostic; the destination is the first
|
encoding a jump to X0 with no diagnostic; the destination is the first
|
||||||
|
|||||||
+10
-9
@@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Contributions to **gasm-devkit** are governed by the Contributor terms
|
Contributions to **gasm-sdk** are governed by the Contributor terms
|
||||||
below; submitting one means you accept them.
|
below; submitting one means you accept them.
|
||||||
|
|
||||||
## Contributor terms
|
## Contributor terms
|
||||||
@@ -29,8 +29,8 @@ compiler (gcc), because `just gates` includes `just race` and the race
|
|||||||
detector needs cgo.
|
detector needs cgo.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
git clone https://sourcedock.dev/petrbalvin/gasm-sdk.git
|
||||||
cd gasm-devkit
|
cd gasm-sdk
|
||||||
just build
|
just build
|
||||||
just gates
|
just gates
|
||||||
```
|
```
|
||||||
@@ -117,16 +117,17 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
|
|||||||
|
|
||||||
| Workflow | Trigger | What it does |
|
| Workflow | Trigger | What it does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Test | push or pull request to `development` | build, format check, vet, modernisation, the test suite with the coverage floor, the CLI and debugger tests outside the profile, then the oracle-parity rerun against `go tool asm` |
|
| Test | push or pull request to `development` | the format check, `go vet`, and the short layer of the test suite with the coverage profile and the 80 % floor, inside the two-minute budget |
|
||||||
| Release | a `v*` tag | the same gates as Test minus the oracle-parity step, then the matrix build, the version smoke test and the release itself; the race detector runs locally in `just gates` before the tag is cut |
|
| Suite | dispatched by hand | the complete gate set minus race: the build, the two FreeBSD cross-build gates (amd64, arm64), both static gates, the full suite and the coverage floor |
|
||||||
|
| Release | a `v*` tag | the matrix build (Linux on four architectures, FreeBSD on two), the version smoke test and the release with its assets; no gate runs at the tag |
|
||||||
|
|
||||||
The local equivalent is `just gates`, which is the same set plus the race detector. The
|
The local equivalent is `just gates`, which is the same set plus the race detector. Race
|
||||||
race detector also has its own workflow, dispatched by hand; it never runs on a push or a
|
never runs in CI, on a push or a tag: it would double the time and the memory a shared
|
||||||
tag, where it would double the time and the memory a shared runner cannot spare.
|
runner cannot spare, so the local `just gates` runs it before the tag is cut.
|
||||||
|
|
||||||
## Reporting bugs
|
## Reporting bugs
|
||||||
|
|
||||||
Open an issue at `https://sourcedock.dev/petrbalvin/gasm-devkit/issues` with the
|
Open an issue at `https://sourcedock.dev/petrbalvin/gasm-sdk/issues` with the
|
||||||
version, the operating system and architecture, the exact command, the full output,
|
version, the operating system and architecture, the exact command, the full output,
|
||||||
and the expected against the actual behaviour.
|
and the expected against the actual behaviour.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Plan 9 assembly tooling, inside and outside Go
|
# GAsm: Software Development Kit for Plan 9 Assembly
|
||||||
|
|
||||||
> **Warning: this is an experiment.** gasm-devkit is under active
|
> **Warning: this is an experiment.** gasm-sdk is under active
|
||||||
> development and is not stable. The version is 0.x.x: commands, flags,
|
> development and is not stable. The version is 0.x.x: commands, flags,
|
||||||
> output formats and behaviour can change without warning at any time.
|
> output formats and behaviour can change without warning at any time.
|
||||||
> A 1.0.0 release is light years away. Nothing in this document is a
|
> A 1.0.0 release is light years away. Nothing in this document is a
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
there is no formatter, no linter and no debugger for `.s` files, and no
|
there is no formatter, no linter and no debugger for `.s` files, and no
|
||||||
assembler that works without a Go installation. Developers write
|
assembler that works without a Go installation. Developers write
|
||||||
assembly blind, validate it by benchmark, and debug it by print
|
assembly blind, validate it by benchmark, and debug it by print
|
||||||
statement. gasm-devkit is the missing toolkit: a single, self-contained
|
statement. gasm-sdk is the missing toolkit: a single, self-contained
|
||||||
binary, `gasm`, that serves both purposes.
|
binary, `gasm`, that serves both purposes.
|
||||||
|
|
||||||
- **Help develop Plan 9 assembly.** Formatting, linting, disassembly,
|
- **Help develop Plan 9 assembly.** Formatting, linting, disassembly,
|
||||||
@@ -58,7 +58,7 @@ Plan 9 (Go): MOVQ AX, total-16(SP)
|
|||||||
|
|
||||||
The same lines, but only one of them tells you what the number is for.
|
The same lines, but only one of them tells you what the number is for.
|
||||||
The syntax is uppercase, regular and boring, which is the highest
|
The syntax is uppercase, regular and boring, which is the highest
|
||||||
compliment a language for machine code can earn. gasm-devkit exists
|
compliment a language for machine code can earn. gasm-sdk exists
|
||||||
to give that syntax the tooling it deserves.
|
to give that syntax the tooling it deserves.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -81,7 +81,10 @@ to give that syntax the tooling it deserves.
|
|||||||
GOOBJ format, which needs the installed toolchain and which `go build`
|
GOOBJ format, which needs the installed toolchain and which `go build`
|
||||||
consumes in place of the toolchain's output. Framed functions get the
|
consumes in place of the toolchain's output. Framed functions get the
|
||||||
stack-split guard and the morestack block, byte-identical to the
|
stack-split guard and the morestack block, byte-identical to the
|
||||||
toolchain's, so split functions link too.
|
toolchain's, so split functions link too. The assembler preprocesses
|
||||||
|
like the toolchain (`#define`, `#include` with `-I`, `#ifdef`), generates
|
||||||
|
`go_asm.h` from the package's Go files, and carries `PCALIGN`, the
|
||||||
|
`LOCK`/`REP` prefixes and the literal-data pseudo-ops.
|
||||||
- **Disassembler.** `gasm dis` lists a `.s` file's functions at their real
|
- **Disassembler.** `gasm dis` lists a `.s` file's functions at their real
|
||||||
offsets after assembling, or disassembles raw bytes from a file or stdin.
|
offsets after assembling, or disassembles raw bytes from a file or stdin.
|
||||||
- **Dynamic verification.** `gasm verify` JIT-loads assembled functions into
|
- **Dynamic verification.** `gasm verify` JIT-loads assembled functions into
|
||||||
@@ -91,13 +94,16 @@ to give that syntax the tooling it deserves.
|
|||||||
- **Debugger.** `gasm debug` is a source-level ptrace debugger with
|
- **Debugger.** `gasm debug` is a source-level ptrace debugger with
|
||||||
breakpoints (optionally conditional), hardware watchpoints, register and
|
breakpoints (optionally conditional), hardware watchpoints, register and
|
||||||
memory inspection, and headless script runs that report instruction and
|
memory inspection, and headless script runs that report instruction and
|
||||||
label coverage.
|
label coverage; it runs on Linux (all four architectures) and FreeBSD
|
||||||
|
(amd64, arm64).
|
||||||
- **Language server.** `gasm lsp` serves completion, hover, document symbols,
|
- **Language server.** `gasm lsp` serves completion, hover, document symbols,
|
||||||
push and pull diagnostics, semantic-token highlighting, go-to-definition,
|
push and pull diagnostics, semantic-token highlighting, go-to-definition,
|
||||||
find references, rename, formatting, inlay hints, code actions, signature
|
find references, rename, formatting, inlay hints, code actions, signature
|
||||||
help, document highlights, workspace symbol search, #include document
|
help, document highlights, workspace symbol search, #include document
|
||||||
links and folding ranges over stdio; definition, references and rename
|
links and folding ranges over stdio; definition, references and rename
|
||||||
work across every open document.
|
work across every open document and the indexed workspace files beyond
|
||||||
|
them, and the quick fixes add a missing textflag.h include and set the
|
||||||
|
argument area from the // func signature.
|
||||||
- **Comparators and audits.** `gasm diff` compares the machine code of two
|
- **Comparators and audits.** `gasm diff` compares the machine code of two
|
||||||
assembly files byte-for-byte, `gasm profile` shows basic-block structure,
|
assembly files byte-for-byte, `gasm profile` shows basic-block structure,
|
||||||
`gasm audit-instructions` diffs the encoder against the installed toolchain,
|
`gasm audit-instructions` diffs the encoder against the installed toolchain,
|
||||||
@@ -110,9 +116,9 @@ Four architectures, the four that matter in practice:
|
|||||||
| Architecture | GOARCH | File suffix | Instructions recognised |
|
| Architecture | GOARCH | File suffix | Instructions recognised |
|
||||||
|--------------|-------------|--------------|---------------------------------------------|
|
|--------------|-------------|--------------|---------------------------------------------|
|
||||||
| AMD64 | `amd64` | `_amd64.s` | 1600 + common opcodes + traditional aliases |
|
| AMD64 | `amd64` | `_amd64.s` | 1600 + common opcodes + traditional aliases |
|
||||||
| ARM64 | `arm64` | `_arm64.s` | 538 + common opcodes |
|
| ARM64 | `arm64` | `_arm64.s` | 645 + common opcodes |
|
||||||
| RISC-V | `riscv64` | `_riscv64.s` | 961 + common opcodes |
|
| RISC-V | `riscv64` | `_riscv64.s` | 992 + common opcodes |
|
||||||
| LoongArch | `loong64` | `_loong64.s` | 799 + common opcodes |
|
| LoongArch | `loong64` | `_loong64.s` | 808 + common opcodes |
|
||||||
|
|
||||||
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
||||||
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, ...). AMD64 additionally
|
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, ...). AMD64 additionally
|
||||||
@@ -124,10 +130,13 @@ can emit today is narrower, and a recognised but unencodable instruction is
|
|||||||
reported as an explicit error, never as a wrong byte.
|
reported as an explicit error, never as a wrong byte.
|
||||||
|
|
||||||
The same measurement runs over GOROOT's whole assembly corpus:
|
The same measurement runs over GOROOT's whole assembly corpus:
|
||||||
`gasm audit-instructions --corpus` reports 136 of 433 attemptable files
|
`gasm audit-instructions --corpus` reports every real-code GOROOT assembly
|
||||||
(31.4 %) assembling for every target architecture today (files named for
|
file (the tree without testdata) assembling for every target its build
|
||||||
other Go ports are counted but never attempted), with the top failure
|
admits: 250 of 250, 100 %. Over the whole tree including testdata the
|
||||||
reasons per architecture; the number moves with every release.
|
measure is 271 of 322 attemptable (84.2 %); files named for other Go ports
|
||||||
|
are counted but never attempted, and `//go:build` constraints decide which
|
||||||
|
targets attempt a file at all, exactly as the build does. The number moves
|
||||||
|
with every release.
|
||||||
|
|
||||||
### Validation status
|
### Validation status
|
||||||
|
|
||||||
@@ -142,7 +151,7 @@ actually been executed.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Encoding: byte-for-byte against `go tool asm` | native hardware | native hardware (the toolchain cross-assembles any GOARCH on any host) |
|
| Encoding: byte-for-byte against `go tool asm` | native hardware | native hardware (the toolchain cross-assembles any GOARCH on any host) |
|
||||||
| Execution: JIT calls, ABI checks, differential fuzzing | native hardware | qemu-user emulation |
|
| Execution: JIT calls, ABI checks, differential fuzzing | native hardware | qemu-user emulation |
|
||||||
| Debugger: ptrace tracing, breakpoints, watchpoints, coverage | native hardware | emulation cannot run ptrace; the layer compiles and its architecture-neutral units run under `go test ./...`, nothing more |
|
| Debugger: ptrace tracing, breakpoints, watchpoints, coverage | native hardware | emulation cannot run ptrace; the layer compiles and its architecture-neutral units run under `go test ./...`, nothing more. FreeBSD (amd64, arm64) is in the same position: the port compiles behind the cross-build gate and its integration test is ready, but no FreeBSD machine has executed it |
|
||||||
|
|
||||||
Consequences, stated plainly. An emulator is a model of a CPU, not the
|
Consequences, stated plainly. An emulator is a model of a CPU, not the
|
||||||
CPU: instruction semantics are implemented in software and can differ
|
CPU: instruction semantics are implemented in software and can differ
|
||||||
@@ -153,9 +162,43 @@ other. Encoding parity is the exception: the byte comparison against the
|
|||||||
toolchain runs on the host for every architecture, so no emulator stands
|
toolchain runs on the host for every architecture, so no emulator stands
|
||||||
between the claim and the evidence. The debugger is the weakest case: on
|
between the claim and the evidence. The debugger is the weakest case: on
|
||||||
the three emulated architectures its per-architecture ptrace code has
|
the three emulated architectures its per-architecture ptrace code has
|
||||||
been compiled and read, never executed. Its architecture-neutral units
|
been compiled and read, never executed. Its units run under
|
||||||
run under `go test ./...`, which the race workflow and a manual run
|
`go test ./...`, which `just test` sweeps in a second invocation beside
|
||||||
perform; the default `just test` gate does not sweep `./debug/...`.
|
the coverage profile; the live ptrace sessions are skipped in short
|
||||||
|
mode, so they run locally or in the dispatched suite, never on a push.
|
||||||
|
|
||||||
|
## The documentation goal
|
||||||
|
|
||||||
|
The toolkit is the primary goal. The secondary one is documentation: a
|
||||||
|
specification of the Plan 9 assembly language and of the GOOBJ object
|
||||||
|
format that is 100 % complete, detailed enough to implement against,
|
||||||
|
and written to a professional standard. These are the two subjects this
|
||||||
|
project works with every day, and they are the two for which no usable
|
||||||
|
documentation exists.
|
||||||
|
|
||||||
|
Go documents the language on a single page, "A Quick Guide to Go's
|
||||||
|
Assembler", which carries no section for loong64, one of the four
|
||||||
|
architectures gasm supports, and covers a fraction of what each
|
||||||
|
assembler accepts. What exists beyond it lives as comments inside the
|
||||||
|
toolchain's internal source: per-architecture reference manuals for
|
||||||
|
arm64, ppc64, riscv64 and loong64, written for the toolchain's own
|
||||||
|
maintainers rather than for an outside reader, and none at all for
|
||||||
|
amd64. GOOBJ fares worst of all. The format that `go build` consumes
|
||||||
|
has no specification anywhere: it is described by a comment in an
|
||||||
|
internal package, it is not a stable interface, and it can change with
|
||||||
|
any toolchain release.
|
||||||
|
|
||||||
|
The gap is therefore filled the only way it can be filled: by reverse
|
||||||
|
engineering the toolchain itself, the same work the encoders already
|
||||||
|
perform. Most of the documentation can come from nowhere else, and it
|
||||||
|
is written as that knowledge is produced during development. It is
|
||||||
|
verified the way the code is verified: an encoding documented here is
|
||||||
|
one that differential tests against `go tool asm` confirm
|
||||||
|
byte-for-byte, and a format field documented here is one the linker
|
||||||
|
demonstrably reads. The work has begun: [docs/GOOBJ.md](docs/GOOBJ.md)
|
||||||
|
specifies the object file format completely, and
|
||||||
|
[docs/asm/README.md](docs/asm/README.md) opens the language reference
|
||||||
|
with its common core. The per-architecture pages follow.
|
||||||
|
|
||||||
## Direction
|
## Direction
|
||||||
|
|
||||||
@@ -179,21 +222,23 @@ The plan, in the order it is being worked:
|
|||||||
toolchain itself does not support; through ELF, Plan 9 assembly becomes
|
toolchain itself does not support; through ELF, Plan 9 assembly becomes
|
||||||
usable outside Go entirely.
|
usable outside Go entirely.
|
||||||
- **Platforms: Linux and FreeBSD.** Linux is supported today on all four
|
- **Platforms: Linux and FreeBSD.** Linux is supported today on all four
|
||||||
architectures and is where the binary builds. FreeBSD follows: the
|
architectures and is where the binary builds. FreeBSD follows on amd64
|
||||||
JIT's executable-memory mapping and the ptrace debugger layer are the
|
and arm64: the JIT's executable-memory mapping and the ptrace debugger
|
||||||
two pieces of porting work. Other unix systems may follow those two.
|
layer are ported, the release carries the two FreeBSD binaries, and the
|
||||||
|
debugger's live validation awaits a FreeBSD machine (the validation
|
||||||
|
status states it). Other unix systems may follow those two.
|
||||||
- **Four architectures, no more.** amd64, arm64, riscv64 and loong64.
|
- **Four architectures, no more.** amd64, arm64, riscv64 and loong64.
|
||||||
No others are planned.
|
No others are planned.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and
|
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64,
|
||||||
linux/loong64 are on the
|
linux/loong64, freebsd/amd64 and freebsd/arm64 are on the
|
||||||
[releases page](https://sourcedock.dev/petrbalvin/gasm-devkit/releases).
|
[releases page](https://sourcedock.dev/petrbalvin/gasm-sdk/releases).
|
||||||
From source (Go 1.27.1):
|
From source (Go 1.27.1):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install sourcedock.dev/petrbalvin/gasm-devkit/cmd/gasm@latest
|
go install sourcedock.dev/petrbalvin/gasm-sdk/cmd/gasm@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or from a repository checkout:
|
Or from a repository checkout:
|
||||||
@@ -282,6 +327,8 @@ recipe.
|
|||||||
~/.local/share/man (MANDIR overrides); `just uninstall-man` removes
|
~/.local/share/man (MANDIR overrides); `just uninstall-man` removes
|
||||||
them
|
them
|
||||||
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
||||||
|
- [docs/GOOBJ.md](docs/GOOBJ.md): the GOOBJ object file format specification
|
||||||
|
- [docs/asm/](docs/asm/README.md): the Plan 9 assembly language reference
|
||||||
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
|
||||||
- [CHANGELOG.md](CHANGELOG.md): release history
|
- [CHANGELOG.md](CHANGELOG.md): release history
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ releases do not receive them.
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 0.34.0 | yes |
|
| 0.35.0 | yes |
|
||||||
| older releases | no |
|
| older releases | no |
|
||||||
|
|
||||||
## Reporting a vulnerability
|
## Reporting a vulnerability
|
||||||
|
|||||||
+105
-7
@@ -5,9 +5,13 @@
|
|||||||
// toolchain's own assembler source. Go's Plan 9 assembler defines the exact,
|
// toolchain's own assembler source. Go's Plan 9 assembler defines the exact,
|
||||||
// complete set of mnemonics it accepts for each architecture in
|
// complete set of mnemonics it accepts for each architecture in
|
||||||
// $GOROOT/src/cmd/internal/obj/<arch>/anames.go; this tool extracts those
|
// $GOROOT/src/cmd/internal/obj/<arch>/anames.go; this tool extracts those
|
||||||
// names so gasm-devkit supports every instruction the real assembler does,
|
// names so gasm-sdk supports every instruction the real assembler does,
|
||||||
// with no hand-maintained (and therefore inevitably incomplete) lists.
|
// with no hand-maintained (and therefore inevitably incomplete) lists.
|
||||||
//
|
//
|
||||||
|
// The same data feeds the generated instruction appendices of the assembly
|
||||||
|
// language reference, docs/asm/INSTRUCTIONS-<ARCH>.md, so that the reference
|
||||||
|
// cannot drift from the tables it documents.
|
||||||
|
//
|
||||||
// Usage (via the justfile):
|
// Usage (via the justfile):
|
||||||
//
|
//
|
||||||
// just gen
|
// just gen
|
||||||
@@ -26,9 +30,12 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// archDirs maps a gasm-devkit architecture name to its obj sub-directory.
|
// archDirs maps a gasm-sdk architecture name to its obj sub-directory.
|
||||||
var archDirs = []struct {
|
var archDirs = []struct {
|
||||||
arch string
|
arch string
|
||||||
sub string
|
sub string
|
||||||
@@ -39,11 +46,30 @@ var archDirs = []struct {
|
|||||||
{"loong64", "loong64"},
|
{"loong64", "loong64"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// docPages maps an architecture to its generated appendix in the language
|
||||||
|
// reference. The amd64 page carries a per-mnemonic encodability column,
|
||||||
|
// decided by asm.Encodable, which mirrors the encoder's own dispatch; the
|
||||||
|
// other targets have no single cheap predicate, so their pages carry the
|
||||||
|
// inventory and point at the live measurement instead.
|
||||||
|
var docPages = []struct {
|
||||||
|
arch arch.Arch
|
||||||
|
title string
|
||||||
|
file string
|
||||||
|
anames string
|
||||||
|
encodable bool
|
||||||
|
}{
|
||||||
|
{arch.AMD64, "AMD64", "INSTRUCTIONS-AMD64.md", "cmd/internal/obj/x86/anames.go", true},
|
||||||
|
{arch.ARM64, "ARM64", "INSTRUCTIONS-ARM64.md", "cmd/internal/obj/arm64/anames.go", false},
|
||||||
|
{arch.RISCV, "RISC-V 64", "INSTRUCTIONS-RISCV64.md", "cmd/internal/obj/riscv/anames.go", false},
|
||||||
|
{arch.LOONG64, "LoongArch 64", "INSTRUCTIONS-LOONG64.md", "cmd/internal/obj/loong64/anames.go", false},
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
goroot := strings.TrimSpace(runGoEnvGOROOT())
|
goroot := strings.TrimSpace(runGoEnvGOROOT())
|
||||||
if goroot == "" {
|
if goroot == "" {
|
||||||
fatal("could not determine GOROOT")
|
fatal("could not determine GOROOT")
|
||||||
}
|
}
|
||||||
|
version := strings.TrimSpace(runGoEnv("GOVERSION"))
|
||||||
// The common opcodes shared by every architecture (RET, JMP, NOP, CALL,
|
// The common opcodes shared by every architecture (RET, JMP, NOP, CALL,
|
||||||
// TEXT, FUNCDATA, …) live in cmd/internal/obj/util.go.
|
// TEXT, FUNCDATA, …) live in cmd/internal/obj/util.go.
|
||||||
commonPath := filepath.Join(goroot, "src", "cmd", "internal", "obj", "util.go")
|
commonPath := filepath.Join(goroot, "src", "cmd", "internal", "obj", "util.go")
|
||||||
@@ -57,16 +83,24 @@ func main() {
|
|||||||
}
|
}
|
||||||
fmt.Printf("%-8s %4d instructions -> arch/common_gen.go\n", "common", len(common))
|
fmt.Printf("%-8s %4d instructions -> arch/common_gen.go\n", "common", len(common))
|
||||||
|
|
||||||
|
names := map[string][]string{}
|
||||||
for _, a := range archDirs {
|
for _, a := range archDirs {
|
||||||
path := filepath.Join(goroot, "src", "cmd", "internal", "obj", a.sub, "anames.go")
|
path := filepath.Join(goroot, "src", "cmd", "internal", "obj", a.sub, "anames.go")
|
||||||
names, err := extractInstrs(path)
|
names[a.arch], err = extractInstrs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal("extract %s: %v", a.arch, err)
|
fatal("extract %s: %v", a.arch, err)
|
||||||
}
|
}
|
||||||
if err := writeGen(a.arch, a.sub, names); err != nil {
|
if err := writeGen(a.arch, a.sub, names[a.arch]); err != nil {
|
||||||
fatal("write %s: %v", a.arch, err)
|
fatal("write %s: %v", a.arch, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%-8s %4d instructions -> arch/%s_gen.go\n", a.arch, len(names), a.arch)
|
fmt.Printf("%-8s %4d instructions -> arch/%s_gen.go\n", a.arch, len(names[a.arch]), a.arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range docPages {
|
||||||
|
if err := writeDocPage(p.arch, p.title, p.file, p.anames, version, p.encodable); err != nil {
|
||||||
|
fatal("write %s: %v", p.file, err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%-8s -> docs/asm/%s\n", p.arch, p.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +119,7 @@ func filterCommon(names []string) []string {
|
|||||||
// writeCommon emits arch/common_gen.go.
|
// writeCommon emits arch/common_gen.go.
|
||||||
func writeCommon(names []string) error {
|
func writeCommon(names []string) error {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("// Code generated by gasm-devkit _gen; DO NOT EDIT.\n")
|
b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n")
|
||||||
b.WriteString("// Source: cmd/internal/obj/util.go from the Go toolchain.\n")
|
b.WriteString("// Source: cmd/internal/obj/util.go from the Go toolchain.\n")
|
||||||
b.WriteString("//\n")
|
b.WriteString("//\n")
|
||||||
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
||||||
@@ -156,7 +190,7 @@ func stringLit(elt ast.Expr) string {
|
|||||||
// writeGen emits arch/<arch>_gen.go.
|
// writeGen emits arch/<arch>_gen.go.
|
||||||
func writeGen(arch, sub string, names []string) error {
|
func writeGen(arch, sub string, names []string) error {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("// Code generated by gasm-devkit _gen; DO NOT EDIT.\n")
|
b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n")
|
||||||
b.WriteString("// Source: cmd/internal/obj/" + sub + "/anames.go from the Go toolchain.\n")
|
b.WriteString("// Source: cmd/internal/obj/" + sub + "/anames.go from the Go toolchain.\n")
|
||||||
b.WriteString("//\n")
|
b.WriteString("//\n")
|
||||||
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
||||||
@@ -172,6 +206,61 @@ func writeGen(arch, sub string, names []string) error {
|
|||||||
return os.WriteFile(filepath.Join("arch", arch+"_gen.go"), []byte(b.String()), 0o644)
|
return os.WriteFile(filepath.Join("arch", arch+"_gen.go"), []byte(b.String()), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeDocPage emits docs/asm/<file>, the generated instruction appendix of
|
||||||
|
// the language reference for one architecture: every mnemonic the toolchain
|
||||||
|
// accepts, with the curated summary where the architecture table carries one
|
||||||
|
// and, on amd64, a per-mnemonic encodability column.
|
||||||
|
func writeDocPage(a arch.Arch, title, file, anames, version string, encodable bool) error {
|
||||||
|
table := arch.ForArch(a)
|
||||||
|
instrs := table.Instructions()
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("# " + title + ": instruction inventory\n\n")
|
||||||
|
b.WriteString("Generated by gasm-sdk's `_gen` from the Go toolchain's instruction table\n")
|
||||||
|
b.WriteString("(`" + anames + "`, " + version + "); DO NOT EDIT. This page lists every mnemonic\n")
|
||||||
|
b.WriteString("`go tool asm` accepts on this target, which is the upper bound of the\n")
|
||||||
|
b.WriteString("language on it: a name absent here is not an instruction of the target,\n")
|
||||||
|
b.WriteString("and a name present here may still be one gasm's encoder cannot emit yet.\n\n")
|
||||||
|
|
||||||
|
encodableCount := 0
|
||||||
|
if encodable {
|
||||||
|
b.WriteString("The `gasm encodes` column reports whether gasm's encoder can emit the\n")
|
||||||
|
b.WriteString("mnemonic today; the gap is the encoder backlog, measured live by\n")
|
||||||
|
b.WriteString("`gasm audit-instructions`.\n\n")
|
||||||
|
b.WriteString("| Mnemonic | gasm encodes | Notes |\n")
|
||||||
|
b.WriteString("|---|---|---|\n")
|
||||||
|
for _, in := range instrs {
|
||||||
|
ok := asm.Encodable(in.Name)
|
||||||
|
if ok {
|
||||||
|
encodableCount++
|
||||||
|
}
|
||||||
|
b.WriteString("| `" + in.Name + "` | " + yesNo(ok) + " | " + in.Summary + " |\n")
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
|
fmt.Fprintf(&b, "Recognised: %d mnemonics. gasm encodes: %d.\n", len(instrs), encodableCount)
|
||||||
|
} else {
|
||||||
|
b.WriteString("The inventory carries no per-mnemonic encoder column: on this target\n")
|
||||||
|
b.WriteString("encodability is decided per operand shape, and the live measured\n")
|
||||||
|
b.WriteString("coverage is reported by `gasm audit-instructions`.\n\n")
|
||||||
|
b.WriteString("| Mnemonic | Notes |\n")
|
||||||
|
b.WriteString("|---|---|\n")
|
||||||
|
for _, in := range instrs {
|
||||||
|
b.WriteString("| `" + in.Name + "` | " + in.Summary + " |\n")
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
|
fmt.Fprintf(&b, "Recognised: %d mnemonics.\n", len(instrs))
|
||||||
|
}
|
||||||
|
return os.WriteFile(filepath.Join("docs", "asm", file), []byte(b.String()), 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// yesNo renders a boolean as the word the appendix tables use.
|
||||||
|
func yesNo(v bool) string {
|
||||||
|
if v {
|
||||||
|
return "yes"
|
||||||
|
}
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
|
||||||
func runGoEnvGOROOT() string {
|
func runGoEnvGOROOT() string {
|
||||||
out, err := exec.Command("go", "env", "GOROOT").Output()
|
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -180,6 +269,15 @@ func runGoEnvGOROOT() string {
|
|||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runGoEnv runs `go env` for a single variable.
|
||||||
|
func runGoEnv(name string) string {
|
||||||
|
out, err := exec.Command("go", "env", name).Output()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
func fatal(format string, args ...any) {
|
func fatal(format string, args ...any) {
|
||||||
fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
|
fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
+1835
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,201 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package arch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The memory-operand mechanism: the base-plus-displacement validation and
|
||||||
|
// the ModR/M, SIB and displacement byte choices. The expected words are
|
||||||
|
// built on the VMOVSH memory-load template, whose rows the binutils-gdb
|
||||||
|
// assembler testsuite quotes byte for byte; the rows marked GNU match the
|
||||||
|
// listing bytes. Note on the quoted disp8 rows: binutils mainline encodes
|
||||||
|
// EVEX displacements with the APX disp8*N scaling, so its source spellings
|
||||||
|
// (254 for the m16 rows, 8128 for the m512 ones) are N times the plain SDM
|
||||||
|
// displacement the disp8 bytes carry; the words here pin the bytes with the
|
||||||
|
// plain displacement those bytes encode.
|
||||||
|
func TestAmd64ExtMemoryEncoding(t *testing.T) {
|
||||||
|
load := []byte{0x62, 0x05, 0x06, 0x00, 0x10, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
gnuSource string // the binutils source line the bytes serve, empty for a derived row
|
||||||
|
}{
|
||||||
|
{"zero displacement drops the disp bytes", 9, 0,
|
||||||
|
"62457e081031", "vmovsh (%r9),%xmm30"},
|
||||||
|
{"positive disp8", 1, 127,
|
||||||
|
"62657e0810717f", "vmovsh 254(%rcx),%xmm30 (Disp8(7f) under the disp8*N scaling)"},
|
||||||
|
{"negative disp8", 2, -128,
|
||||||
|
"62657e08107280", "vmovsh -256(%rdx),%xmm30 (Disp8(80) under the disp8*N scaling)"},
|
||||||
|
{"disp32 past the disp8 range", 2, 8128,
|
||||||
|
"62657e0810b2c01f0000", ""},
|
||||||
|
{"negative disp32", 13, -200,
|
||||||
|
"62457e0810b538ffffff", ""},
|
||||||
|
{"RSP base takes the SIB byte", 12, 0,
|
||||||
|
"62457e08103424", ""},
|
||||||
|
{"RSP base with a disp8", 12, 4,
|
||||||
|
"62457e0810742404", ""},
|
||||||
|
{"RBP base keeps a zero displacement explicit", 5, 0,
|
||||||
|
"62657e08107500", ""},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeMemory(load, 30, -1, tt.base, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtMemoryRejects checks the validation around the memory operand:
|
||||||
|
// the kinds and ranges the layer refuses before a byte is laid down.
|
||||||
|
func TestAmd64ExtMemoryRejects(t *testing.T) {
|
||||||
|
in := ExtInstr{Name: "TEST"}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
op ExtOperand
|
||||||
|
quote string
|
||||||
|
}{
|
||||||
|
{"a register where the memory operand belongs", ExtXmm(3),
|
||||||
|
"wants a memory operand"},
|
||||||
|
{"base beyond r15", ExtMemory(16, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
{"base under r0", ExtMemory(-1, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
{"displacement past the signed 32-bit ceiling", ExtMemory(8, 1<<31),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"displacement past the signed 32-bit floor", ExtMemory(8, -1<<31-1),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"shift on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Imm: 4, Shift: 2, HasShift: true},
|
||||||
|
"take none"},
|
||||||
|
{"arrangement suffix on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Arr: ExtArrH},
|
||||||
|
"arrangement"},
|
||||||
|
{"predicate qualifier on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Qual: ExtQualZeroing},
|
||||||
|
"predicate qualifier"},
|
||||||
|
{"broadcast on an entry that takes none", ExtBroadcast(8, 0),
|
||||||
|
"takes none"},
|
||||||
|
} {
|
||||||
|
_, _, err := in.amd64Memory(tt.op, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s: validation succeeded, want an error", tt.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.quote) {
|
||||||
|
t.Errorf("%s: error %q lacks %q", tt.name, err, tt.quote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The broadcast spelling passes the flag gate on an entry that carries
|
||||||
|
// Bcast and then meets the same base and displacement checks.
|
||||||
|
bcast := ExtInstr{Name: "TEST", Bcast: true}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
op ExtOperand
|
||||||
|
quote string
|
||||||
|
}{
|
||||||
|
{"broadcast base beyond r15", ExtOperand{Kind: ExtMem, Reg: 16, Imm: 0, Broadcast: true},
|
||||||
|
"outside 0-15"},
|
||||||
|
{"broadcast displacement past the signed 32-bit ceiling", ExtBroadcast(8, 1<<31),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"broadcast base under r0", ExtBroadcast(-1, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
} {
|
||||||
|
if _, _, err := bcast.amd64Memory(tt.op, 1); err == nil {
|
||||||
|
t.Errorf("%s: validation succeeded, want an error", tt.name)
|
||||||
|
} else if !strings.Contains(err.Error(), tt.quote) {
|
||||||
|
t.Errorf("%s: error %q lacks %q", tt.name, err, tt.quote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtBroadcastEncoding pins the broadcast layer over the memory
|
||||||
|
// encoding: EVEX.b, bit 4 of byte three, set on the VADDPH 512-bit template
|
||||||
|
// while the ModR/M mod bits, the SIB byte and the displacement choices keep
|
||||||
|
// the plain semantics amd64EncodeMemory chooses.
|
||||||
|
func TestAmd64ExtBroadcastEncoding(t *testing.T) {
|
||||||
|
add := []byte{0x62, 0x05, 0x04, 0x40, 0x58, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"zero displacement keeps the mod-00 form under the broadcast bit", 9, 0,
|
||||||
|
"624514505831"},
|
||||||
|
{"disp8 semantics unchanged", 1, 127,
|
||||||
|
"6265145058717f"},
|
||||||
|
{"disp32 semantics unchanged", 2, 8128,
|
||||||
|
"6265145058b2c01f0000"},
|
||||||
|
{"RBP keeps the forced displacement", 5, 0,
|
||||||
|
"62651450587500"},
|
||||||
|
{"RSP keeps the SIB byte", 12, 0,
|
||||||
|
"62451450583424"},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeBroadcast(add, 30, 29, tt.base, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtScaledMemoryEncoding pins the SIB layer over the memory
|
||||||
|
// encoding: the scale field, the index and the base in one byte, the r/m
|
||||||
|
// field 100, EVEX.X clearing on an index above 7, and the ModR/M and
|
||||||
|
// displacement choices keeping the plain semantics, RBP's forced
|
||||||
|
// displacement included.
|
||||||
|
func TestAmd64ExtScaledMemoryEncoding(t *testing.T) {
|
||||||
|
add := []byte{0x62, 0x05, 0x04, 0x40, 0x58, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
index int
|
||||||
|
scale int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"scale 1 encodes the scale field zero", 1, 2, 1, 0,
|
||||||
|
"62651440583411"},
|
||||||
|
{"scale 2", 1, 2, 2, 0,
|
||||||
|
"62651440583451"},
|
||||||
|
{"scale 4", 1, 2, 4, 0,
|
||||||
|
"62651440583491"},
|
||||||
|
{"scale 8", 1, 2, 8, 0,
|
||||||
|
"626514405834d1"},
|
||||||
|
{"an index above 7 clears EVEX.X", 1, 12, 2, 0,
|
||||||
|
"62251440583461"},
|
||||||
|
{"RBP base keeps the forced displacement", 5, 14, 8, 0,
|
||||||
|
"622514405874f500"},
|
||||||
|
{"RSP base takes the SIB byte with the index", 12, 3, 4, 0,
|
||||||
|
"6245144058349c"},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeScaledMemory(add, 30, 29, tt.base, tt.index, tt.scale, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtMemoryVocabulary pins the names the shared layer gives the
|
||||||
|
// memory operand and its two forms.
|
||||||
|
func TestAmd64ExtMemoryVocabulary(t *testing.T) {
|
||||||
|
if got := ExtMem.String(); got != "memory operand" {
|
||||||
|
t.Errorf("ExtMem = %q, want %q", got, "memory operand")
|
||||||
|
}
|
||||||
|
if got := ExtFormAmdMemVec.String(); got != "memory into a vector" {
|
||||||
|
t.Errorf("ExtFormAmdMemVec = %q, want %q", got, "memory into a vector")
|
||||||
|
}
|
||||||
|
if got := ExtFormAmdVecMem.String(); got != "a vector into memory" {
|
||||||
|
t.Errorf("ExtFormAmdVecMem = %q, want %q", got, "a vector into memory")
|
||||||
|
}
|
||||||
|
for _, f := range []ExtForm{ExtFormAmdMemVec, ExtFormAmdVecMem} {
|
||||||
|
if got := f.Arity(); got != 2 {
|
||||||
|
t.Errorf("%s takes %d operands, want 2", f, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := ExtMemory(9, 4096); got.Kind != ExtMem || got.Reg != 9 || got.Imm != 4096 {
|
||||||
|
t.Errorf("ExtMemory(9, 4096) = %+v, want base 9 with displacement 4096", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/x86/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/x86/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+31
-10
@@ -24,20 +24,41 @@ const (
|
|||||||
Unknown Arch = ""
|
Unknown Arch = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromFilename guesses the target architecture from a source file name. Go
|
// FromFilename guesses the target architecture from a source file name, by
|
||||||
// assembly files conventionally carry a GOARCH suffix such as "_amd64.s",
|
// go/build's goodOSArchFile rule: directories are stripped, the name is cut
|
||||||
// "_arm64.s", "_riscv64.s" or "_loong64.s". It returns Unknown when no suffix
|
// at its first dot, everything before the first underscore is ignored and a
|
||||||
// matches.
|
// trailing _test segment is dropped; the name is per-architecture only when
|
||||||
|
// its final underscore segment is an architecture name (foo_amd64.s,
|
||||||
|
// sys_darwin_arm64.s, foo_amd64_test.s). Any other name narrows nothing:
|
||||||
|
// not one whose last segment merely contains an architecture name
|
||||||
|
// (x_loong64y.s), not one with no underscore at all (amd64.s, which
|
||||||
|
// go/build also keeps generic), and not one differing in case
|
||||||
|
// (foo_AMD64.s), because the segment match is case-sensitive as go/build's
|
||||||
|
// is. A name carrying an operating system alone (foo_linux.s) narrows by
|
||||||
|
// GOOS rather than architecture, and one carrying an architecture gasm
|
||||||
|
// does not assemble for (vlop_arm.s) reports Unknown so the caller's own
|
||||||
|
// port filter decides. It returns Unknown when the name narrows nothing.
|
||||||
func FromFilename(name string) Arch {
|
func FromFilename(name string) Arch {
|
||||||
lower := strings.ToLower(name)
|
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||||
switch {
|
name = name[i+1:]
|
||||||
case strings.Contains(lower, "_amd64"):
|
}
|
||||||
|
name, _, _ = strings.Cut(name, ".")
|
||||||
|
i := strings.Index(name, "_")
|
||||||
|
if i < 0 {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
segs := strings.Split(name[i:], "_")
|
||||||
|
if last := len(segs) - 1; segs[last] == "test" {
|
||||||
|
segs = segs[:last]
|
||||||
|
}
|
||||||
|
switch segs[len(segs)-1] {
|
||||||
|
case "amd64":
|
||||||
return AMD64
|
return AMD64
|
||||||
case strings.Contains(lower, "_arm64"):
|
case "arm64":
|
||||||
return ARM64
|
return ARM64
|
||||||
case strings.Contains(lower, "_riscv64"), strings.Contains(lower, "_riscv"):
|
case "riscv64":
|
||||||
return RISCV
|
return RISCV
|
||||||
case strings.Contains(lower, "_loong64"), strings.Contains(lower, "_loong"):
|
case "loong64":
|
||||||
return LOONG64
|
return LOONG64
|
||||||
default:
|
default:
|
||||||
return Unknown
|
return Unknown
|
||||||
|
|||||||
+29
-2
@@ -7,13 +7,40 @@ import "testing"
|
|||||||
|
|
||||||
func TestFromFilename(t *testing.T) {
|
func TestFromFilename(t *testing.T) {
|
||||||
cases := map[string]Arch{
|
cases := map[string]Arch{
|
||||||
|
// Positive: the final underscore segment is the architecture.
|
||||||
"avx2_amd64.s": AMD64,
|
"avx2_amd64.s": AMD64,
|
||||||
"foo_arm64.s": ARM64,
|
"foo_arm64.s": ARM64,
|
||||||
"portable.s": Unknown,
|
|
||||||
"decode_ARM64.S": ARM64,
|
|
||||||
"kernels_amd64.s": AMD64,
|
"kernels_amd64.s": AMD64,
|
||||||
"kernel_riscv64.s": RISCV,
|
"kernel_riscv64.s": RISCV,
|
||||||
"kernel_loong64.s": LOONG64,
|
"kernel_loong64.s": LOONG64,
|
||||||
|
"decode_arm64.S": ARM64, // the extension is cut with the first dot
|
||||||
|
// OS before arch: the _<os>_<arch> pair form.
|
||||||
|
"sys_darwin_arm64.s": ARM64,
|
||||||
|
"rt0_linux_amd64.s": AMD64,
|
||||||
|
// A trailing _test segment is dropped before the suffix rule.
|
||||||
|
"foo_amd64_test.s": AMD64,
|
||||||
|
// Trailing junk: a final segment that merely contains an
|
||||||
|
// architecture name narrows nothing, exactly as go/build's
|
||||||
|
// segment rule says.
|
||||||
|
"x_loong64y.s": Unknown,
|
||||||
|
"asm_amd64x.s": Unknown, // a real GOROOT name
|
||||||
|
"asm_darwin_arm64_gc.s": Unknown, // the trailing _gc segment is junk
|
||||||
|
"x_loong.s": Unknown,
|
||||||
|
"foo_amd64x_test.s": Unknown,
|
||||||
|
// Case sensitivity: the segment match is exact, as go/build's is.
|
||||||
|
"foo_AMD64.s": Unknown,
|
||||||
|
"decode_ARM64.S": Unknown,
|
||||||
|
// No underscore at all: generic whatever the stem says.
|
||||||
|
"amd64.s": Unknown,
|
||||||
|
"portable.s": Unknown,
|
||||||
|
// A GOOS-only name narrows by operating system, not architecture.
|
||||||
|
"foo_linux.s": Unknown,
|
||||||
|
// An architecture gasm does not assemble for: named, but Unknown.
|
||||||
|
"vlop_arm.s": Unknown,
|
||||||
|
"foo_riscv.s": Unknown,
|
||||||
|
// Directories are stripped first, whatever dots they carry.
|
||||||
|
"some/dir/kernel_loong64.s": LOONG64,
|
||||||
|
"/a.b/x_amd64.s": AMD64,
|
||||||
}
|
}
|
||||||
for name, want := range cases {
|
for name, want := range cases {
|
||||||
if got := FromFilename(name); got != want {
|
if got := FromFilename(name); got != want {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func arm64Registers() []Register {
|
|||||||
for i := 0; i <= 30; i++ {
|
for i := 0; i <= 30; i++ {
|
||||||
add(fmt.Sprintf("R%d", i), GPR, "64-bit general-purpose register")
|
add(fmt.Sprintf("R%d", i), GPR, "64-bit general-purpose register")
|
||||||
}
|
}
|
||||||
|
add("R18_PLATFORM", GPR, "R18 under its toolchain-reserved Windows name (an alias of R18)")
|
||||||
add("ZR", Special, "zero register (reads as 0)")
|
add("ZR", Special, "zero register (reads as 0)")
|
||||||
add("SP", Special, "stack pointer")
|
add("SP", Special, "stack pointer")
|
||||||
add("LR", Special, "link register (alias of R30)")
|
add("LR", Special, "link register (alias of R30)")
|
||||||
|
|||||||
+5294
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+108
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/arm64/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/arm64/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
@@ -364,6 +364,8 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"REVW",
|
"REVW",
|
||||||
"ROR",
|
"ROR",
|
||||||
"RORW",
|
"RORW",
|
||||||
|
"RPRFM",
|
||||||
|
"SB",
|
||||||
"SBC",
|
"SBC",
|
||||||
"SBCS",
|
"SBCS",
|
||||||
"SBCSW",
|
"SBCSW",
|
||||||
@@ -477,23 +479,68 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"UXTH",
|
"UXTH",
|
||||||
"UXTHW",
|
"UXTHW",
|
||||||
"UXTW",
|
"UXTW",
|
||||||
|
"VABS",
|
||||||
"VADD",
|
"VADD",
|
||||||
"VADDP",
|
"VADDP",
|
||||||
"VADDV",
|
"VADDV",
|
||||||
"VAND",
|
"VAND",
|
||||||
"VBCAX",
|
"VBCAX",
|
||||||
|
"VBIC",
|
||||||
"VBIF",
|
"VBIF",
|
||||||
"VBIT",
|
"VBIT",
|
||||||
"VBSL",
|
"VBSL",
|
||||||
|
"VCLS",
|
||||||
|
"VCLZ",
|
||||||
"VCMEQ",
|
"VCMEQ",
|
||||||
|
"VCMGE",
|
||||||
|
"VCMGT",
|
||||||
|
"VCMHI",
|
||||||
|
"VCMHS",
|
||||||
|
"VCMLE",
|
||||||
|
"VCMLT",
|
||||||
"VCMTST",
|
"VCMTST",
|
||||||
"VCNT",
|
"VCNT",
|
||||||
"VDUP",
|
"VDUP",
|
||||||
"VEOR",
|
"VEOR",
|
||||||
"VEOR3",
|
"VEOR3",
|
||||||
"VEXT",
|
"VEXT",
|
||||||
|
"VFABS",
|
||||||
|
"VFADD",
|
||||||
|
"VFADDP",
|
||||||
|
"VFCMEQ",
|
||||||
|
"VFCMGE",
|
||||||
|
"VFCMGT",
|
||||||
|
"VFCMLE",
|
||||||
|
"VFCMLT",
|
||||||
|
"VFCVTL",
|
||||||
|
"VFCVTL2",
|
||||||
|
"VFCVTN",
|
||||||
|
"VFCVTN2",
|
||||||
|
"VFCVTZS",
|
||||||
|
"VFCVTZU",
|
||||||
|
"VFDIV",
|
||||||
|
"VFMAX",
|
||||||
|
"VFMAXNM",
|
||||||
|
"VFMAXNMP",
|
||||||
|
"VFMAXNMV",
|
||||||
|
"VFMAXP",
|
||||||
|
"VFMAXV",
|
||||||
|
"VFMIN",
|
||||||
|
"VFMINNM",
|
||||||
|
"VFMINNMP",
|
||||||
|
"VFMINNMV",
|
||||||
|
"VFMINP",
|
||||||
|
"VFMINV",
|
||||||
"VFMLA",
|
"VFMLA",
|
||||||
"VFMLS",
|
"VFMLS",
|
||||||
|
"VFMUL",
|
||||||
|
"VFNEG",
|
||||||
|
"VFRINTM",
|
||||||
|
"VFRINTN",
|
||||||
|
"VFRINTP",
|
||||||
|
"VFRINTZ",
|
||||||
|
"VFSQRT",
|
||||||
|
"VFSUB",
|
||||||
"VLD1",
|
"VLD1",
|
||||||
"VLD1R",
|
"VLD1R",
|
||||||
"VLD2",
|
"VLD2",
|
||||||
@@ -502,11 +549,17 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"VLD3R",
|
"VLD3R",
|
||||||
"VLD4",
|
"VLD4",
|
||||||
"VLD4R",
|
"VLD4R",
|
||||||
|
"VMLA",
|
||||||
|
"VMLS",
|
||||||
"VMOV",
|
"VMOV",
|
||||||
"VMOVD",
|
"VMOVD",
|
||||||
"VMOVI",
|
"VMOVI",
|
||||||
"VMOVQ",
|
"VMOVQ",
|
||||||
"VMOVS",
|
"VMOVS",
|
||||||
|
"VMUL",
|
||||||
|
"VNEG",
|
||||||
|
"VNOT",
|
||||||
|
"VORN",
|
||||||
"VORR",
|
"VORR",
|
||||||
"VPMULL",
|
"VPMULL",
|
||||||
"VPMULL2",
|
"VPMULL2",
|
||||||
@@ -515,14 +568,47 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"VREV16",
|
"VREV16",
|
||||||
"VREV32",
|
"VREV32",
|
||||||
"VREV64",
|
"VREV64",
|
||||||
|
"VSCVTF",
|
||||||
|
"VSHADD",
|
||||||
"VSHL",
|
"VSHL",
|
||||||
|
"VSHRN",
|
||||||
|
"VSHRN2",
|
||||||
"VSLI",
|
"VSLI",
|
||||||
|
"VSMAX",
|
||||||
|
"VSMAXP",
|
||||||
|
"VSMAXV",
|
||||||
|
"VSMIN",
|
||||||
|
"VSMINP",
|
||||||
|
"VSMINV",
|
||||||
|
"VSMLAL",
|
||||||
|
"VSMLAL2",
|
||||||
|
"VSMLSL",
|
||||||
|
"VSMLSL2",
|
||||||
|
"VSMULL",
|
||||||
|
"VSMULL2",
|
||||||
|
"VSQABS",
|
||||||
|
"VSQADD",
|
||||||
|
"VSQNEG",
|
||||||
|
"VSQSHL",
|
||||||
|
"VSQSUB",
|
||||||
|
"VSQXTN",
|
||||||
|
"VSQXTN2",
|
||||||
|
"VSQXTUN",
|
||||||
|
"VSQXTUN2",
|
||||||
|
"VSRHADD",
|
||||||
"VSRI",
|
"VSRI",
|
||||||
|
"VSRSHR",
|
||||||
|
"VSSHL",
|
||||||
|
"VSSHLL",
|
||||||
|
"VSSHLL2",
|
||||||
|
"VSSHR",
|
||||||
"VST1",
|
"VST1",
|
||||||
"VST2",
|
"VST2",
|
||||||
"VST3",
|
"VST3",
|
||||||
"VST4",
|
"VST4",
|
||||||
"VSUB",
|
"VSUB",
|
||||||
|
"VSXTL",
|
||||||
|
"VSXTL2",
|
||||||
"VTBL",
|
"VTBL",
|
||||||
"VTBX",
|
"VTBX",
|
||||||
"VTRN1",
|
"VTRN1",
|
||||||
@@ -530,8 +616,27 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"VUADDLV",
|
"VUADDLV",
|
||||||
"VUADDW",
|
"VUADDW",
|
||||||
"VUADDW2",
|
"VUADDW2",
|
||||||
|
"VUCVTF",
|
||||||
|
"VUHADD",
|
||||||
"VUMAX",
|
"VUMAX",
|
||||||
|
"VUMAXP",
|
||||||
|
"VUMAXV",
|
||||||
"VUMIN",
|
"VUMIN",
|
||||||
|
"VUMINP",
|
||||||
|
"VUMINV",
|
||||||
|
"VUMLAL",
|
||||||
|
"VUMLAL2",
|
||||||
|
"VUMLSL",
|
||||||
|
"VUMLSL2",
|
||||||
|
"VUMULL",
|
||||||
|
"VUMULL2",
|
||||||
|
"VUQADD",
|
||||||
|
"VUQSHL",
|
||||||
|
"VUQSUB",
|
||||||
|
"VUQXTN",
|
||||||
|
"VUQXTN2",
|
||||||
|
"VURHADD",
|
||||||
|
"VUSHL",
|
||||||
"VUSHLL",
|
"VUSHLL",
|
||||||
"VUSHLL2",
|
"VUSHLL2",
|
||||||
"VUSHR",
|
"VUSHR",
|
||||||
@@ -541,6 +646,8 @@ var arm64GeneratedInstrs = []string{
|
|||||||
"VUZP1",
|
"VUZP1",
|
||||||
"VUZP2",
|
"VUZP2",
|
||||||
"VXAR",
|
"VXAR",
|
||||||
|
"VXTN",
|
||||||
|
"VXTN2",
|
||||||
"VZIP1",
|
"VZIP1",
|
||||||
"VZIP2",
|
"VZIP2",
|
||||||
"WFE",
|
"WFE",
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/util.go from the Go toolchain.
|
// Source: cmd/internal/obj/util.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+10
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/loong64/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/loong64/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
@@ -152,6 +152,8 @@ var loong64GeneratedInstrs = []string{
|
|||||||
"FNMADDF",
|
"FNMADDF",
|
||||||
"FNMSUBD",
|
"FNMSUBD",
|
||||||
"FNMSUBF",
|
"FNMSUBF",
|
||||||
|
"FRINTD",
|
||||||
|
"FRINTF",
|
||||||
"FSCALEBD",
|
"FSCALEBD",
|
||||||
"FSCALEBF",
|
"FSCALEBF",
|
||||||
"FSEL",
|
"FSEL",
|
||||||
@@ -177,7 +179,10 @@ var loong64GeneratedInstrs = []string{
|
|||||||
"FTINTWF",
|
"FTINTWF",
|
||||||
"JIRL",
|
"JIRL",
|
||||||
"LL",
|
"LL",
|
||||||
|
"LLACQV",
|
||||||
|
"LLACQW",
|
||||||
"LLV",
|
"LLV",
|
||||||
|
"LLW",
|
||||||
"LU12IW",
|
"LU12IW",
|
||||||
"LU32ID",
|
"LU32ID",
|
||||||
"LU52ID",
|
"LU52ID",
|
||||||
@@ -248,7 +253,11 @@ var loong64GeneratedInstrs = []string{
|
|||||||
"ROTR",
|
"ROTR",
|
||||||
"ROTRV",
|
"ROTRV",
|
||||||
"SC",
|
"SC",
|
||||||
|
"SCQ",
|
||||||
|
"SCRELV",
|
||||||
|
"SCRELW",
|
||||||
"SCV",
|
"SCV",
|
||||||
|
"SCW",
|
||||||
"SGT",
|
"SGT",
|
||||||
"SGTU",
|
"SGTU",
|
||||||
"SLL",
|
"SLL",
|
||||||
|
|||||||
+32
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/riscv/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/riscv/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
@@ -81,6 +81,9 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"CLD",
|
"CLD",
|
||||||
"CLDSP",
|
"CLDSP",
|
||||||
"CLI",
|
"CLI",
|
||||||
|
"CLMUL",
|
||||||
|
"CLMULH",
|
||||||
|
"CLMULR",
|
||||||
"CLUI",
|
"CLUI",
|
||||||
"CLW",
|
"CLW",
|
||||||
"CLWSP",
|
"CLWSP",
|
||||||
@@ -95,13 +98,20 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"CSDSP",
|
"CSDSP",
|
||||||
"CSLLI",
|
"CSLLI",
|
||||||
"CSRAI",
|
"CSRAI",
|
||||||
|
"CSRC",
|
||||||
|
"CSRCI",
|
||||||
"CSRLI",
|
"CSRLI",
|
||||||
|
"CSRR",
|
||||||
"CSRRC",
|
"CSRRC",
|
||||||
"CSRRCI",
|
"CSRRCI",
|
||||||
"CSRRS",
|
"CSRRS",
|
||||||
"CSRRSI",
|
"CSRRSI",
|
||||||
"CSRRW",
|
"CSRRW",
|
||||||
"CSRRWI",
|
"CSRRWI",
|
||||||
|
"CSRS",
|
||||||
|
"CSRSI",
|
||||||
|
"CSRW",
|
||||||
|
"CSRWI",
|
||||||
"CSUB",
|
"CSUB",
|
||||||
"CSUBW",
|
"CSUBW",
|
||||||
"CSW",
|
"CSW",
|
||||||
@@ -259,6 +269,7 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"ORCB",
|
"ORCB",
|
||||||
"ORI",
|
"ORI",
|
||||||
"ORN",
|
"ORN",
|
||||||
|
"PAUSE",
|
||||||
"RDCYCLE",
|
"RDCYCLE",
|
||||||
"RDINSTRET",
|
"RDINSTRET",
|
||||||
"RDTIME",
|
"RDTIME",
|
||||||
@@ -322,6 +333,8 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"VADDVI",
|
"VADDVI",
|
||||||
"VADDVV",
|
"VADDVV",
|
||||||
"VADDVX",
|
"VADDVX",
|
||||||
|
"VANDNVV",
|
||||||
|
"VANDNVX",
|
||||||
"VANDVI",
|
"VANDVI",
|
||||||
"VANDVV",
|
"VANDVV",
|
||||||
"VANDVX",
|
"VANDVX",
|
||||||
@@ -329,8 +342,17 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"VASUBUVX",
|
"VASUBUVX",
|
||||||
"VASUBVV",
|
"VASUBVV",
|
||||||
"VASUBVX",
|
"VASUBVX",
|
||||||
|
"VBREV8V",
|
||||||
|
"VBREVV",
|
||||||
|
"VCLMULHVV",
|
||||||
|
"VCLMULHVX",
|
||||||
|
"VCLMULVV",
|
||||||
|
"VCLMULVX",
|
||||||
|
"VCLZV",
|
||||||
"VCOMPRESSVM",
|
"VCOMPRESSVM",
|
||||||
"VCPOPM",
|
"VCPOPM",
|
||||||
|
"VCPOPV",
|
||||||
|
"VCTZV",
|
||||||
"VDIVUVV",
|
"VDIVUVV",
|
||||||
"VDIVUVX",
|
"VDIVUVX",
|
||||||
"VDIVVV",
|
"VDIVVV",
|
||||||
@@ -743,10 +765,16 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"VREMUVX",
|
"VREMUVX",
|
||||||
"VREMVV",
|
"VREMVV",
|
||||||
"VREMVX",
|
"VREMVX",
|
||||||
|
"VREV8V",
|
||||||
"VRGATHEREI16VV",
|
"VRGATHEREI16VV",
|
||||||
"VRGATHERVI",
|
"VRGATHERVI",
|
||||||
"VRGATHERVV",
|
"VRGATHERVV",
|
||||||
"VRGATHERVX",
|
"VRGATHERVX",
|
||||||
|
"VROLVV",
|
||||||
|
"VROLVX",
|
||||||
|
"VRORVI",
|
||||||
|
"VRORVV",
|
||||||
|
"VRORVX",
|
||||||
"VRSUBVI",
|
"VRSUBVI",
|
||||||
"VRSUBVX",
|
"VRSUBVX",
|
||||||
"VS1RV",
|
"VS1RV",
|
||||||
@@ -950,6 +978,9 @@ var riscvGeneratedInstrs = []string{
|
|||||||
"VWMULVX",
|
"VWMULVX",
|
||||||
"VWREDSUMUVS",
|
"VWREDSUMUVS",
|
||||||
"VWREDSUMVS",
|
"VWREDSUMVS",
|
||||||
|
"VWSLLVI",
|
||||||
|
"VWSLLVV",
|
||||||
|
"VWSLLVX",
|
||||||
"VWSUBUVV",
|
"VWSUBUVV",
|
||||||
"VWSUBUVX",
|
"VWSUBUVX",
|
||||||
"VWSUBUWV",
|
"VWSUBUWV",
|
||||||
|
|||||||
+89
-39
@@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
||||||
@@ -178,26 +178,10 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
}
|
}
|
||||||
var work, linkLine, asmObj string
|
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
defer os.RemoveAll(st.work)
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(line, "WORK="):
|
|
||||||
work = strings.TrimPrefix(line, "WORK=")
|
|
||||||
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_arm64.s") && !strings.Contains(line, "-gensymabis"):
|
|
||||||
asmObj = fieldAfter(line, "-o")
|
|
||||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
|
||||||
linkLine = line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if work == "" || asmObj == "" {
|
|
||||||
t.Skipf("could not parse build log (work=%q asmObj=%q)", work, asmObj)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(work)
|
|
||||||
|
|
||||||
// Expand $WORK in the object path.
|
// Assemble the same source with gasm and substitute the object.
|
||||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
|
||||||
|
|
||||||
// Read the toolchain-produced object and assemble the same source with gasm.
|
|
||||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -210,30 +194,16 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("AssembleFileARM64: %v", err)
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
}
|
}
|
||||||
gasmObj, err := img.GOObjectAARCH64("a64link", "main_arm64.s")
|
// The package path is "main", the prefix the Go code's references carry.
|
||||||
|
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||||
}
|
}
|
||||||
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||||
// Replace the toolchain-produced object with gasm's.
|
gasmObj, "GOARCH=arm64")
|
||||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
|
||||||
t.Fatalf("write gasm object: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-link.
|
|
||||||
if linkLine == "" {
|
|
||||||
t.Skip("could not find link command in build log")
|
|
||||||
}
|
|
||||||
// Expand $WORK in the link command.
|
|
||||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
|
||||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
|
||||||
linkCmd.Env = append(os.Environ(), "GOARCH=arm64")
|
|
||||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
|
||||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the binary exists and contains the symbol.
|
// Verify the binary exists and contains the symbol.
|
||||||
binPath := filepath.Join(dir, "prog")
|
binPath := filepath.Join(dir, "prog2")
|
||||||
if _, err := os.Stat(binPath); err != nil {
|
if _, err := os.Stat(binPath); err != nil {
|
||||||
t.Fatalf("binary not found: %v", err)
|
t.Fatalf("binary not found: %v", err)
|
||||||
}
|
}
|
||||||
@@ -245,3 +215,83 @@ func main() {
|
|||||||
t.Error("binary does not contain expected symbol")
|
t.Error("binary does not contain expected symbol")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGOObjectAARCH64DataSymbolLink does for symbol-valued DATA fields what
|
||||||
|
// the rt0 files do ("DATA _rt0…lib+0(SB)/8, $_rt0…lib(SB)"): the gasm object
|
||||||
|
// carries an R_ADDR against the file's own TEXT symbol, the toolchain links
|
||||||
|
// it, and the binary is checked for the symbol (no arm64 host to run it).
|
||||||
|
func TestGOObjectAARCH64DataSymbolLink(t *testing.T) {
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
dir := t.TempDir()
|
||||||
|
asmSrc := `#include "textflag.h"
|
||||||
|
GLOBL entry(SB), NOPTR, $8
|
||||||
|
DATA entry+0(SB)/8, $·keepme(SB)
|
||||||
|
|
||||||
|
TEXT ·keepme(SB), NOSPLIT, $0-0
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·entryptr(SB), NOSPLIT, $0-8
|
||||||
|
MOVD entry+0(SB), R4
|
||||||
|
MOVD R4, ret+0(FP)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main_arm64.s"), []byte(asmSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mainSrc := `package main
|
||||||
|
|
||||||
|
func keepme()
|
||||||
|
func entryptr() uintptr
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if entryptr() == 0 {
|
||||||
|
panic("the entry word is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module a64dlink\n\ngo 1.21\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
|
||||||
|
build.Dir = dir
|
||||||
|
build.Env = append(os.Environ(), "GOARCH=arm64")
|
||||||
|
buildLog, err := build.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
|
}
|
||||||
|
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||||
|
defer os.RemoveAll(st.work)
|
||||||
|
|
||||||
|
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse("main_arm64.s", string(src))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||||
|
}
|
||||||
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||||
|
gasmObj, "GOARCH=arm64")
|
||||||
|
binData, err := os.ReadFile(filepath.Join(dir, "prog2"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(binData), "keepme") {
|
||||||
|
t.Error("binary does not contain the keepme symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// The assembler's side of the amd64 extension layer: this file turns a parsed
|
||||||
|
// amd64 statement into the operand form arch.ExtInstr.Encode consumes and
|
||||||
|
// routes statements only the layer can encode through the registry. It sits
|
||||||
|
// beside the main amd64 encoders, never inside them: the generated table, the
|
||||||
|
// legacy SSE paths and the VEX and EVEX mechanisms are untouched, and a
|
||||||
|
// statement reaches this file only when the mnemonic is registered in the
|
||||||
|
// extension layer and the scalar paths cannot encode it.
|
||||||
|
//
|
||||||
|
// The spellings are the layer's own Plan 9 forms, the ones its metadata
|
||||||
|
// documents: the vector registers carry the house names X0, Y0 and Z0 (the
|
||||||
|
// EVEX 128, 256 and 512-bit classes, registers 16 to 31 included), the general
|
||||||
|
// registers the width their spelling fixes (RAX through R15, EAX through EDI
|
||||||
|
// and R8D through R15D), the opmask registers K0 through K7, and the memory
|
||||||
|
// operand the base-relative form off(base) with the optional scaled index
|
||||||
|
// off(base)(index*scale) the SIB byte carries. The decorations ride the
|
||||||
|
// operand in braces: the write mask {k1} through {k7} and zeroing {z} on the
|
||||||
|
// destination, the {1toN} broadcast on the memory source, and {sae} and
|
||||||
|
// {rn-sae} through {rz-sae} beside the rounding-capable destinations. The
|
||||||
|
// imm8-control forms take their control byte as the leading $ immediate the
|
||||||
|
// reference listings write first.
|
||||||
|
//
|
||||||
|
// The Feature field stays metadata at assembly time: the assembler has no CPU,
|
||||||
|
// the toolchain does not gate assembly on CPU features, and every registered
|
||||||
|
// feature assembles, the behaviour the arm64 wiring established
|
||||||
|
// (asm/arm64_ext.go). The field remains for the linter and the listing.
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// amd64ExtStatement converts one instruction's operands into the extended
|
||||||
|
// layer's operand form. pinned reports that the statement belongs to the
|
||||||
|
// layer: the mnemonic is registered in the amd64 registry and the scalar
|
||||||
|
// paths cannot encode it. A pinned statement can only encode through the
|
||||||
|
// layer, so every operand is read here and its diagnostic replaces whatever
|
||||||
|
// the scalar paths would have said about operands they cannot read; err is
|
||||||
|
// non-nil for a pinned statement whose operands the layer refuses, and
|
||||||
|
// extops is complete only when err is nil. Unpinned means the statement is
|
||||||
|
// nobody's: the caller falls through to the ordinary amd64 encoders, which
|
||||||
|
// keep their exact behaviour for every statement they knew before.
|
||||||
|
func amd64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperand, pinned bool, err error) {
|
||||||
|
if _, ok := LookupExtension(arch.AMD64, mnem); !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
if Encodable(mnem) {
|
||||||
|
// A mnemonic the main encoder knows is never the layer's, whatever
|
||||||
|
// the registry carries: the scalar paths keep the statement. No
|
||||||
|
// registered mnemonic trips this today (the layer is sealed by
|
||||||
|
// test), but the guard keeps the fall-through promise exact should
|
||||||
|
// the toolchain ever learn one of these names.
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
out := make([]arch.ExtOperand, 0, len(ops))
|
||||||
|
for i, op := range ops {
|
||||||
|
ext, convErr := amd64ExtOperand(mnem, op, i+1)
|
||||||
|
if convErr != nil {
|
||||||
|
return nil, true, convErr
|
||||||
|
}
|
||||||
|
out = append(out, ext)
|
||||||
|
}
|
||||||
|
return out, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeAmd64Statement runs one parsed amd64 statement through the layer
|
||||||
|
// exactly as the assembler does: the operands convert the amd64ExtOperand way
|
||||||
|
// and the mnemonic resolves and encodes through the registry. pinned reports
|
||||||
|
// that the statement belongs to the layer alone (the mnemonic is registered
|
||||||
|
// and the scalar paths cannot encode it); err is the layer's own refusal of
|
||||||
|
// the operands, the same text the assembler prints, so the linter surfaces
|
||||||
|
// one diagnostic where assembly would fail. A statement the scalar paths own
|
||||||
|
// returns pinned false, with nothing to report.
|
||||||
|
func EncodeAmd64Statement(mnem string, ops []*ast.Operand) (code []byte, pinned bool, err error) {
|
||||||
|
extops, pinned, err := amd64ExtStatement(mnem, ops)
|
||||||
|
if !pinned || err != nil {
|
||||||
|
return nil, pinned, err
|
||||||
|
}
|
||||||
|
code, err = EncodeExtension(arch.AMD64, mnem, extops...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
return code, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtOperand converts one parsed operand into the layer's form: a $ immediate,
|
||||||
|
// a vector, general or opmask register, or a base-relative memory operand, each
|
||||||
|
// with the brace decorations the spelling carries.
|
||||||
|
func amd64ExtOperand(mnem string, op *ast.Operand, pos int) (arch.ExtOperand, error) {
|
||||||
|
if op.Kind == ast.OpImmediate {
|
||||||
|
return amd64ExtImmediate(mnem, op, pos)
|
||||||
|
}
|
||||||
|
body, dec, err := amd64ExtDecorations(mnem, op, pos)
|
||||||
|
if err != nil {
|
||||||
|
return arch.ExtOperand{}, err
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(body, '(') {
|
||||||
|
ext, ok := amd64ExtMemory(mnem, op, pos)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, fmt.Errorf("%s: operand %d (%s) is not an extended-layer operand: want a base-relative memory operand, off(base)(index*scale) shape", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
ext.Broadcast = dec.broadcast
|
||||||
|
if dec.hasMask {
|
||||||
|
ext.Mask, ext.HasMask = dec.mask, true
|
||||||
|
}
|
||||||
|
ext.Zeroing = dec.zeroing
|
||||||
|
ext.Round = dec.round
|
||||||
|
return ext, nil
|
||||||
|
}
|
||||||
|
ext, ok := amd64ExtRegister(mnem, op, pos, body)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, fmt.Errorf("%s: operand %d (%s) is not an extended-layer operand: want a vector, general or opmask register, a base-relative memory operand or an immediate", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
if dec.hasMask {
|
||||||
|
ext.Mask, ext.HasMask = dec.mask, true
|
||||||
|
}
|
||||||
|
ext.Zeroing = dec.zeroing
|
||||||
|
ext.Round = dec.round
|
||||||
|
return ext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtImmediate converts a $ immediate into the layer's form. The
|
||||||
|
// parser folds a parenthesised constant expression in full and reads a bare
|
||||||
|
// literal greedily, dropping any trailing operator tokens: $255<<8 parses as
|
||||||
|
// 255 with the shift silently gone. Encoding that silent prefix would
|
||||||
|
// assemble what the text did not say, so an unparenthesised immediate is
|
||||||
|
// accepted only when its whole text reads back as one integer carrying the
|
||||||
|
// parser's value.
|
||||||
|
func amd64ExtImmediate(mnem string, op *ast.Operand, pos int) (arch.ExtOperand, error) {
|
||||||
|
if !op.Imm.HasVal {
|
||||||
|
return arch.ExtOperand{}, fmt.Errorf("%s: operand %d (%s) is not an immediate the layer can read", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
text := strings.Join(strings.Fields(strings.TrimPrefix(op.Raw, "$")), "")
|
||||||
|
if !strings.HasPrefix(text, "(") {
|
||||||
|
if _, parseErr := strconv.ParseInt(text, 0, 64); parseErr != nil {
|
||||||
|
return arch.ExtOperand{}, fmt.Errorf("%s: operand %d (%s) is not an immediate the layer can read", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := op.Imm.Val
|
||||||
|
if op.Imm.Neg {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtImm, Imm: v}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtRegister parses a register operand off a normalised operand body:
|
||||||
|
// the vector classes X0-X31, Y0-Y31 and Z0-Z31, the width-fixed general
|
||||||
|
// spellings RAX through R15 and EAX through R15D, and the opmask registers
|
||||||
|
// K0-K7. The register ranges are left to the encoding, whose diagnostics
|
||||||
|
// name them.
|
||||||
|
func amd64ExtRegister(mnem string, op *ast.Operand, pos int, body string) (arch.ExtOperand, bool) {
|
||||||
|
if body == "" {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
r, ok := ParseReg(body)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r.mask:
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtKReg, Reg: r.idx}, true
|
||||||
|
case r.isVec():
|
||||||
|
kind := arch.ExtXMM
|
||||||
|
switch r.size {
|
||||||
|
case 32:
|
||||||
|
kind = arch.ExtYMM
|
||||||
|
case 64:
|
||||||
|
kind = arch.ExtZMM
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: kind, Reg: r.idx}, true
|
||||||
|
case r.size == 8:
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtR64, Reg: r.idx}, true
|
||||||
|
case r.size == 4:
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtR32, Reg: r.idx}, true
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtMemory parses a base-relative memory operand off the parsed
|
||||||
|
// address: off(base) and off(base)(index*scale), the SIB shapes the layer's
|
||||||
|
// entries carry. The base and the index are general registers spelled in any
|
||||||
|
// width the house names offer, the displacement the leading signed term, and
|
||||||
|
// a group whose scale is not written scales by one, the choice the main
|
||||||
|
// amd64 paths make for the same spelling. Vector, opmask and segment
|
||||||
|
// registers are refused as base and index, and so is every frame form: the
|
||||||
|
// layer's memory operand is hardware addressing alone.
|
||||||
|
func amd64ExtMemory(mnem string, op *ast.Operand, pos int) (arch.ExtOperand, bool) {
|
||||||
|
a := op.Addr
|
||||||
|
if a.Range != nil || a.Base == "" {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
if a.Sym != nil && a.Sym.Pseudo != "" {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
base, ok := amd64ExtGprNumber(a.Base)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
ext := arch.ExtOperand{Kind: arch.ExtMem, Reg: base, Imm: a.Offset}
|
||||||
|
if a.Index != "" {
|
||||||
|
index, ok := amd64ExtGprNumber(a.Index)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
scale := a.Scale
|
||||||
|
if scale == 0 {
|
||||||
|
scale = 1
|
||||||
|
}
|
||||||
|
ext.Index, ext.Scale, ext.HasIndex = index, scale, true
|
||||||
|
}
|
||||||
|
return ext, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtGprNumber resolves one general-register spelling to its number:
|
||||||
|
// whatever the register table carries for indices 0-15, the vector, opmask,
|
||||||
|
// x87, MMX, segment and control-debug classes refused, so a vector register
|
||||||
|
// in a base or index position names itself rather than encoding as its
|
||||||
|
// same-numbered general register.
|
||||||
|
func amd64ExtGprNumber(name string) (int, bool) {
|
||||||
|
r, ok := ParseReg(name)
|
||||||
|
if !ok || r.mask || r.fp || r.mmx || r.seg != 0 || r.ctl != 0 || r.size > 8 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return r.idx, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtDecorations splits the brace decorations off a normalised operand
|
||||||
|
// text and returns the body before the first brace and the decorations they
|
||||||
|
// spell: the write mask {k1} through {k7}, zeroing {z}, the {1toN} broadcast
|
||||||
|
// and the rounding controls {sae} and {rn-sae} through {rz-sae}, matched
|
||||||
|
// case-insensitively the way the register spellings are. The mask, zeroing
|
||||||
|
// and rounding fields land on the operand the conversion builds; whether the
|
||||||
|
// position takes them is the encoding's judgement, whose diagnostics name the
|
||||||
|
// entry. The broadcast factor N is checked as a number and otherwise left to
|
||||||
|
// the entry: the layer's model carries the spelling, not the lane count.
|
||||||
|
func amd64ExtDecorations(mnem string, op *ast.Operand, pos int) (body string, dec amd64ExtDecor, err error) {
|
||||||
|
compact := strings.Join(strings.Fields(op.Raw), "")
|
||||||
|
i := strings.IndexByte(compact, '{')
|
||||||
|
if i < 0 {
|
||||||
|
return compact, dec, nil
|
||||||
|
}
|
||||||
|
body = compact[:i]
|
||||||
|
for i < len(compact) {
|
||||||
|
if compact[i] != '{' {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s): text between brace decorations", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
end := strings.IndexByte(compact[i:], '}')
|
||||||
|
if end < 0 {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s): brace decoration without a closing brace", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
content := strings.ToUpper(compact[i+1 : i+end])
|
||||||
|
switch {
|
||||||
|
case content == "Z":
|
||||||
|
if dec.zeroing {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two zeroing decorations", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.zeroing = true
|
||||||
|
case content == "SAE":
|
||||||
|
if dec.round != arch.ExtRoundNone {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two rounding controls", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.round = arch.ExtRoundSAE
|
||||||
|
case content == "RN-SAE":
|
||||||
|
if dec.round != arch.ExtRoundNone {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two rounding controls", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.round = arch.ExtRoundNearest
|
||||||
|
case content == "RD-SAE":
|
||||||
|
if dec.round != arch.ExtRoundNone {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two rounding controls", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.round = arch.ExtRoundDown
|
||||||
|
case content == "RU-SAE":
|
||||||
|
if dec.round != arch.ExtRoundNone {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two rounding controls", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.round = arch.ExtRoundUp
|
||||||
|
case content == "RZ-SAE":
|
||||||
|
if dec.round != arch.ExtRoundNone {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two rounding controls", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.round = arch.ExtRoundTruncate
|
||||||
|
case strings.HasPrefix(content, "K") && content != "K":
|
||||||
|
n, convErr := strconv.Atoi(content[1:])
|
||||||
|
if convErr != nil || n < 0 {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s): %q is not a mask decoration, want {k1} through {k7}", mnem, pos, op.Raw, content)
|
||||||
|
}
|
||||||
|
if dec.hasMask {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s) carries two write masks", mnem, pos, op.Raw)
|
||||||
|
}
|
||||||
|
dec.mask, dec.hasMask = n, true
|
||||||
|
case strings.HasPrefix(content, "1TO"):
|
||||||
|
if _, convErr := strconv.Atoi(content[3:]); convErr != nil {
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s): %q is not a broadcast decoration, want {1toN}", mnem, pos, op.Raw, content)
|
||||||
|
}
|
||||||
|
dec.broadcast = true
|
||||||
|
default:
|
||||||
|
return "", dec, fmt.Errorf("%s: operand %d (%s): {%s} is not a decoration the layer reads: want {k1} through {k7}, {z}, {1toN}, {sae} or {rn-sae} through {rz-sae}", mnem, pos, op.Raw, content)
|
||||||
|
}
|
||||||
|
i += end + 1
|
||||||
|
}
|
||||||
|
return body, dec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtDecor carries the brace decorations one operand's spelling names.
|
||||||
|
type amd64ExtDecor struct {
|
||||||
|
mask int
|
||||||
|
hasMask bool
|
||||||
|
zeroing bool
|
||||||
|
broadcast bool
|
||||||
|
round arch.ExtRounding
|
||||||
|
}
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assembleAmd64ExtBody parses src, assembles it for amd64 and returns the
|
||||||
|
// first function's body. Every statement must encode: a failure is the
|
||||||
|
// test's.
|
||||||
|
func assembleAmd64ExtBody(t *testing.T, src string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("ext_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("got %d functions, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
return img.Code[img.Funcs[0].Offset:][:img.Funcs[0].Size]
|
||||||
|
}
|
||||||
|
|
||||||
|
// assembleAmd64ExtError parses and assembles src and returns the assembler's
|
||||||
|
// error text.
|
||||||
|
func assembleAmd64ExtError(t *testing.T, src string) string {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("ext_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
_, err := AssembleFile(f)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("assembled, want an error")
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
const amd64ExtProbeHead = "#include \"textflag.h\"\nTEXT ·t(SB), NOSPLIT, $0\n"
|
||||||
|
|
||||||
|
// amd64ExtProbe assembles one statement alone and returns the function body:
|
||||||
|
// exactly the statement's bytes, no trailing RET.
|
||||||
|
func amd64ExtProbe(t *testing.T, stmt string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
return assembleAmd64ExtBody(t, amd64ExtProbeHead+"\t"+stmt+"\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionGolden drives the wired layer through the full
|
||||||
|
// assembler: text in, machine bytes out. One statement per family, the
|
||||||
|
// decorations the layer spells beside them, and the memory mechanism's
|
||||||
|
// canonical choices; each want is the byte string the registry's golden
|
||||||
|
// vectors in arch/amd64_ext_test.go and arch/amd64_ext_mem_test.go already
|
||||||
|
// pin, so these prove the text-to-bytes path lands on the same encoding the
|
||||||
|
// metadata layer produces.
|
||||||
|
func TestAmd64AssembleExtensionGolden(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
stmt string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// AVX512-BF16: the two converts and the dot product, the write mask
|
||||||
|
// riding the destination in braces.
|
||||||
|
{"VCVTNE2PS2BF16 Z5, Z4, Z6", "62f2574872f4"},
|
||||||
|
{"VCVTNE2PS2BF16 Z21, Z20, Z23", "62a2574072fc"},
|
||||||
|
{"VCVTNEPS2BF16 Z5, Y6", "62f27e4872f5"},
|
||||||
|
{"VCVTNEPS2BF16 Y5, X6{K6}", "62f27e2e72f5"},
|
||||||
|
{"VDPBF16PS Z5, Z4, Z6{K5}", "62f2564d52f4"},
|
||||||
|
// AVX512-VP2INTERSECT: sources first, the opmask destination last.
|
||||||
|
{"VP2INTERSECTD Y2, Y1, K2", "62f26f2868d1"},
|
||||||
|
// AVX512-FP16, the scalar core: high registers, memory and the
|
||||||
|
// general-register pairs in both directions.
|
||||||
|
{"VADDSH X29, X28, X30", "6205160058f4"},
|
||||||
|
{"VMINSH X5, X4, X6{SAE}", "62f556185df4"},
|
||||||
|
{"VMOVSH (R9), X30", "62457e081031"},
|
||||||
|
{"VCVTSH2SI (R9), R12", "6255fe082d21"},
|
||||||
|
{"VCVTSH2SI X30, EDX", "62957e082dd6"},
|
||||||
|
{"VCVTSI2SH X29, R12, X30", "624596002af4"},
|
||||||
|
{"VMOVW R12, X30", "62457d086ef4"},
|
||||||
|
{"VCMPSH $0x7b, X29, X28, K5", "62931600c2ec7b"},
|
||||||
|
{"VGETMANTSH $0x0b, X29, X28, X30", "6203140027f40b"},
|
||||||
|
// The packed FP16 arithmetic and the embedded rounding: {sae} and the
|
||||||
|
// four rounding modes compose with the write mask and zeroing.
|
||||||
|
{"VADDPH Z5, Z4, Z6{RN-SAE}", "62f5541858f4"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{RD-SAE}", "62f5543858f4"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{RU-SAE}", "62f5545858f4"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{RZ-SAE}", "62f5547858f4"},
|
||||||
|
{"VADDPH Z29, Z28, Z30{K7}{Z}", "620514c758f4"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{K7}{RZ-SAE}", "62f5547f58f4"},
|
||||||
|
{"VADDPH Z28, (R9), Z30{K7}{Z}", "62451cc75831"},
|
||||||
|
{"VSQRTPH Z29, Z30{K3}{Z}", "62057ccb51f5"},
|
||||||
|
{"VFMADD132PH Z29, Z28, Z30", "6206154098f4"},
|
||||||
|
// The packed conversions: full-width sources and the {1toN} broadcast
|
||||||
|
// over the integer sources.
|
||||||
|
{"VCVTPH2W Z5, Z6", "62f57d487df5"},
|
||||||
|
{"VCVTPH2QQ X5, Z6{RZ-SAE}", "62f57d787bf5"},
|
||||||
|
{"VCVTPH2PD X5, Z6", "62f57c485af5"},
|
||||||
|
{"VCVTDQ2PH (R9){1TO8}, Y30", "62457c585b31"},
|
||||||
|
{"VRNDSCALEPH $0x7b, Z5, Z6", "62f37c4808f57b"},
|
||||||
|
// The complex families and the imm8 minimum-or-maximum pair.
|
||||||
|
{"VFCMULCPH Z29, Z28, Z30", "62061740d6f4"},
|
||||||
|
{"VFMADDCPH Z29, Z28, Z30", "6206164056f4"},
|
||||||
|
{"VFCMADDCPH Z5, Z4, Z6{RN-SAE}", "62f6571856f4"},
|
||||||
|
{"VFMADDCSH X29, (R9), X30", "624616005731"},
|
||||||
|
{"VMINMAXPH $0x88, Z29, (R9), Z30", "62431440523188"},
|
||||||
|
{"VMINMAXSH $0x88, X28, (R9), X29", "62431c00532988"},
|
||||||
|
// AVX-VNNI-INT16: the VEX word, its 256-bit length and the memory
|
||||||
|
// source.
|
||||||
|
{"VPDPWSUD X2, X1, X3", "c4e26ad2d9"},
|
||||||
|
{"VPDPWUSDS Y10, Y15, Y8", "c4422dd3c7"},
|
||||||
|
{"VPDPWSUD X2, 127(RCX), X1", "c4e26ad2497f"},
|
||||||
|
// The memory mechanism through the ext statements: the disp8 and
|
||||||
|
// disp32 choices, the RSP-base SIB byte, RBP's forced displacement
|
||||||
|
// and the scaled index with its EVEX.X handling.
|
||||||
|
{"VMOVSH 127(RCX), X30", "62657e0810717f"},
|
||||||
|
{"VMOVSH 8128(RDX), X30", "62657e0810b2c01f0000"},
|
||||||
|
{"VMOVSH (R12), X30", "62457e08103424"},
|
||||||
|
{"VMOVSH (RBP), X30", "62657e08107500"},
|
||||||
|
{"VADDPH Z29, (RCX)(DX*1), Z30", "62651440583411"},
|
||||||
|
{"VADDPH Z29, (RCX)(R12*2), Z30", "62251440583461"},
|
||||||
|
{"VADDPH Z29, (RBP)(R14*8), Z30", "622514405874f500"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
body := amd64ExtProbe(t, tt.stmt)
|
||||||
|
if got := hex.EncodeToString(body); got != tt.want {
|
||||||
|
t.Errorf("%s:\n got %s\n want %s", tt.stmt, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionRegistryParity pins the layer's contract over a
|
||||||
|
// wider slice: every statement here assembles to exactly the bytes
|
||||||
|
// EncodeExtension produces for the model operands the statement spells, so
|
||||||
|
// the front end and the registry cannot drift apart unnoticed.
|
||||||
|
func TestAmd64AssembleExtensionRegistryParity(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
stmt string
|
||||||
|
mnem string
|
||||||
|
ops []arch.ExtOperand
|
||||||
|
}{
|
||||||
|
{"VCVTNE2PS2BF16 Z5, Z4, Z6", "VCVTNE2PS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtZmm(6)}},
|
||||||
|
{"VCVTNEPS2BF16 Y5, X6{K6}", "VCVTNEPS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(5), arch.ExtWriteMasked(arch.ExtXmm(6), 6, false)}},
|
||||||
|
{"VDPBF16PS Z5, Z4, Z6{K5}", "VDPBF16PS",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtWriteMasked(arch.ExtZmm(6), 5, false)}},
|
||||||
|
{"VP2INTERSECTD Y2, Y1, K2", "VP2INTERSECTD",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(2), arch.ExtYmm(1), arch.ExtMask(2)}},
|
||||||
|
{"VADDSH X29, X28, X30", "VADDSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)}},
|
||||||
|
{"VMINSH X5, X4, X6{SAE}", "VMINSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundSAE)}},
|
||||||
|
{"VCVTSI2SH X29, R12, X30", "VCVTSI2SH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtGpr64(12), arch.ExtXmm(30)}},
|
||||||
|
{"VCVTSH2SI X30, EDX", "VCVTSH2SI",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(30), arch.ExtGpr32(2)}},
|
||||||
|
{"VCMPSH $0x7b, X29, X28, K5", "VCMPSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtMask(5)}},
|
||||||
|
{"VGETMANTSH $0x0b, X29, X28, X30", "VGETMANTSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x0b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)}},
|
||||||
|
{"VADDPH Z5, Z4, Z6{K7}{RZ-SAE}", "VADDPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4),
|
||||||
|
arch.ExtRounded(arch.ExtWriteMasked(arch.ExtZmm(6), 7, false), arch.ExtRoundTruncate)}},
|
||||||
|
{"VADDPH Z28, (R9), Z30{K7}{Z}", "VADDPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(28), arch.ExtMemory(9, 0),
|
||||||
|
arch.ExtWriteMasked(arch.ExtZmm(30), 7, true)}},
|
||||||
|
{"VCVTDQ2PH (R9){1TO8}, Y30", "VCVTDQ2PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtBroadcast(9, 0), arch.ExtYmm(30)}},
|
||||||
|
{"VFMADD132PH Z29, Z28, Z30", "VFMADD132PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)}},
|
||||||
|
{"VFMADD231PH Y5, (RCX){1TO8}, Y6", "VFMADD231PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(5), arch.ExtBroadcast(1, 0), arch.ExtYmm(6)}},
|
||||||
|
{"VFCMADDCPH Z5, Z4, Z6{RN-SAE}", "VFCMADDCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundNearest)}},
|
||||||
|
{"VFMADDCSH X29, (R9), X30", "VFMADDCSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtMemory(9, 0), arch.ExtXmm(30)}},
|
||||||
|
{"VMINMAXPH $0x88, Z29, (R9), Z30", "VMINMAXPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtZmm(29), arch.ExtMemory(9, 0), arch.ExtZmm(30)}},
|
||||||
|
{"VPDPWSUD X2, 127(RCX), X1", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtMemory(1, 127), arch.ExtXmm(1)}},
|
||||||
|
{"VPDPWSUD X2, (RCX)(R12*2), X1", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtScaledMemory(1, 12, 2, 0), arch.ExtXmm(1)}},
|
||||||
|
{"VMOVSH X30, (R9)", "VMOVSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(30), arch.ExtMemory(9, 0)}},
|
||||||
|
{"VPDPWUSDS Y10, Y15, Y8", "VPDPWUSDS",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(10), arch.ExtYmm(15), arch.ExtYmm(8)}},
|
||||||
|
{"VMOVSH 8128(RDX), X30", "VMOVSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtMemory(2, 8128), arch.ExtXmm(30)}},
|
||||||
|
{"VADDPH Z29, (RCX)(R12*2), Z30", "VADDPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtScaledMemory(1, 12, 2, 0), arch.ExtZmm(30)}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
body := amd64ExtProbe(t, tt.stmt)
|
||||||
|
want, err := EncodeExtension(arch.AMD64, tt.mnem, tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: registry encode: %v", tt.stmt, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(body, want) {
|
||||||
|
t.Errorf("%s:\n got %x\n want %x (the registry encoding)", tt.stmt, body, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionRefusals pins the diagnostics a pinned statement
|
||||||
|
// gets from the layer instead of a scalar path's complaint, and the
|
||||||
|
// conversion's own diagnostics for spellings the layer cannot read. Where
|
||||||
|
// the Go toolchain knows a family member the shape of the message is its
|
||||||
|
// rejection bar; the FP16, BF16, VP2INTERSECT and VNNI-INT16 families take
|
||||||
|
// the GNU assembler's.
|
||||||
|
func TestAmd64AssembleExtensionRefusals(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
stmt string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"VADDPH Z33, Z1, Z2", "not an extended-layer operand"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{K0}", "outside the masking registers k1-k7"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{Z}", "zeroing without a write mask"},
|
||||||
|
{"VADDPH Y5, Y4, Y6{RZ-SAE}", "wants a ZMM register"},
|
||||||
|
{"VADDPH Z5, Z4, Z6{SAE}", "spells {sae} without a mode"},
|
||||||
|
{"VADDPH Z29, (RCX){RZ-SAE}, Z30", "the memory operand takes none"},
|
||||||
|
{"VFMULCPH Z5, (RCX){1TO8}, Z6", "the entry's memory operand takes none"},
|
||||||
|
{"VADDPH Z29, Z28, Z30{BOGUS}", "is not a decoration the layer reads"},
|
||||||
|
{"VADDPH Z29, Z28, Z30{K7}{K3}", "carries two write masks"},
|
||||||
|
{"VADDSH X5, X4, X6{K3}", "the entry's destination takes none"},
|
||||||
|
{"VCMPSH $300, X29, X28, K5", "outside the unsigned byte range"},
|
||||||
|
{"VGETMANTSH $0x20, X29, X28, X30", "the upper nibble of the mantissa control is reserved"},
|
||||||
|
{"VCVTSI2SH X29, X12, X30", "general register"},
|
||||||
|
{"VADDPH Z5, Z4", "got 2 operands"},
|
||||||
|
{"VMOVSH (Z4), X30", "not an extended-layer operand"},
|
||||||
|
{"VMOVSH foo+4(SB), X30", "not an extended-layer operand"},
|
||||||
|
{"VMOVSH 8(RCX)(DX*3), X30", "outside the byte multipliers"},
|
||||||
|
{"VMOVSH (K1), X30", "not an extended-layer operand"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := assembleAmd64ExtError(t, amd64ExtProbeHead+"\t"+tt.stmt+"\n")
|
||||||
|
if !strings.Contains(got, tt.want) {
|
||||||
|
t.Errorf("%s: error %q does not name %q", tt.stmt, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionLabelOffsets proves pass 1 and pass 2 agree on a
|
||||||
|
// function mixing two ext statements with a backward jump: the label sits
|
||||||
|
// exactly where the laid-down bytes put it, so the JMP's rel8 reaches it.
|
||||||
|
func TestAmd64AssembleExtensionLabelOffsets(t *testing.T) {
|
||||||
|
body := assembleAmd64ExtBody(t, amd64ExtProbeHead+`
|
||||||
|
VADDPH Z1, Z2, Z3
|
||||||
|
loop:
|
||||||
|
VFMADD132PH Z1, Z2, Z3
|
||||||
|
JMP loop
|
||||||
|
`)
|
||||||
|
// Two six-byte EVEX words, then the short JMP whose displacement
|
||||||
|
// measures from its own end (14) back to the label (6).
|
||||||
|
if len(body) != 14 {
|
||||||
|
t.Fatalf("body is %d bytes, want 14", len(body))
|
||||||
|
}
|
||||||
|
if body[12] != 0xEB || body[13] != 0xF8 {
|
||||||
|
t.Errorf("JMP encoded % x, want ebf8", body[12:14])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionLeavesTheMainEncoderAlone pins the non-invasion
|
||||||
|
// promise on the amd64 side: VPOPCNTD, an AVX-512 instruction the toolchain
|
||||||
|
// knows and the layer deliberately does not carry, encodes through the main
|
||||||
|
// EVEX path, byte for byte what that path produces on its own.
|
||||||
|
func TestAmd64AssembleExtensionLeavesTheMainEncoderAlone(t *testing.T) {
|
||||||
|
body := amd64ExtProbe(t, "VPOPCNTD Z1, Z2")
|
||||||
|
e := &enc{}
|
||||||
|
if err := e.encode("VPOPCNTD", []Operand{Reg{idx: 1, size: 64}, Reg{idx: 2, size: 64}}); err != nil {
|
||||||
|
t.Fatalf("main encoder: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(body, e.out) {
|
||||||
|
t.Errorf("VPOPCNTD Z1, Z2: got %x, want the main encoder's %x", body, e.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepClass recomputes the vector class an entry encodes, the read of
|
||||||
|
// the template's length field the arch package keeps private: EVEX.L'L in
|
||||||
|
// byte three, VEX.L in byte two.
|
||||||
|
func amd64SweepClass(in arch.ExtInstr) arch.ExtOperandKind {
|
||||||
|
if in.Vex {
|
||||||
|
if in.Bytes[2]&0x04 != 0 {
|
||||||
|
return arch.ExtYMM
|
||||||
|
}
|
||||||
|
return arch.ExtXMM
|
||||||
|
}
|
||||||
|
switch (in.Bytes[3] >> 5) & 3 {
|
||||||
|
case 0:
|
||||||
|
return arch.ExtXMM
|
||||||
|
case 1:
|
||||||
|
return arch.ExtYMM
|
||||||
|
default:
|
||||||
|
return arch.ExtZMM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepVec spells and models one vector register of the class, the
|
||||||
|
// house names X, Y and Z the layer's text forms carry.
|
||||||
|
func amd64SweepVec(kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) {
|
||||||
|
switch kind {
|
||||||
|
case arch.ExtXMM:
|
||||||
|
return fmt.Sprintf("X%d", n), arch.ExtXmm(n)
|
||||||
|
case arch.ExtYMM:
|
||||||
|
return fmt.Sprintf("Y%d", n), arch.ExtYmm(n)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Z%d", n), arch.ExtZmm(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepGpr spells and models the general register of an entry: the W bit
|
||||||
|
// picks the width, and an entry that ignores W takes the 64-bit spelling.
|
||||||
|
func amd64SweepGpr(in arch.ExtInstr) (string, arch.ExtOperand) {
|
||||||
|
if in.Wig || in.Bytes[2]&0x80 != 0 {
|
||||||
|
return "R12", arch.ExtGpr64(12)
|
||||||
|
}
|
||||||
|
return "R12D", arch.ExtGpr32(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepImm spells and models the control immediate of an entry: the
|
||||||
|
// mantissa control keeps its reserved upper nibble at zero, every other
|
||||||
|
// layout takes a whole byte.
|
||||||
|
func amd64SweepImm(in arch.ExtInstr) (string, arch.ExtOperand) {
|
||||||
|
if in.Imm8 == arch.ExtImm8GetMant {
|
||||||
|
return "$0x0b", arch.ExtImmediate(0x0b)
|
||||||
|
}
|
||||||
|
return "$0x7b", arch.ExtImmediate(0x7b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepDest spells and models the register destination with the
|
||||||
|
// decorations the entry carries on its register form: the write mask beside
|
||||||
|
// every masked entry, the rounding or the exception suppression beside every
|
||||||
|
// entry that takes one.
|
||||||
|
func amd64SweepDest(in arch.ExtInstr, kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) {
|
||||||
|
text, model := amd64SweepVec(kind, n)
|
||||||
|
dec := ""
|
||||||
|
if in.Mask {
|
||||||
|
dec += "{K5}"
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case in.Er:
|
||||||
|
dec += "{RZ-SAE}"
|
||||||
|
case in.Sae:
|
||||||
|
dec += "{SAE}"
|
||||||
|
}
|
||||||
|
if dec == "" {
|
||||||
|
return text, model
|
||||||
|
}
|
||||||
|
if in.Mask {
|
||||||
|
model = arch.ExtWriteMasked(model, 5, false)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case in.Er:
|
||||||
|
model = arch.ExtRounded(model, arch.ExtRoundTruncate)
|
||||||
|
case in.Sae:
|
||||||
|
model = arch.ExtRounded(model, arch.ExtRoundSAE)
|
||||||
|
}
|
||||||
|
return text + dec, model
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64SweepMaskedDest spells and models the destination with the write mask
|
||||||
|
// alone, the one decoration the memory shape keeps.
|
||||||
|
func amd64SweepMaskedDest(in arch.ExtInstr, kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) {
|
||||||
|
text, model := amd64SweepVec(kind, n)
|
||||||
|
if in.Mask {
|
||||||
|
return text + "{K5}", arch.ExtWriteMasked(model, 5, false)
|
||||||
|
}
|
||||||
|
return text, model
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64ExtSweepStatements builds, for the first registered entry of every
|
||||||
|
// distinct amd64 mnemonic, the register-form statement the sweep drives and,
|
||||||
|
// where the entry carries a memory position, the memory-form statement beside
|
||||||
|
// it. Each statement comes back with its mnemonic and the model operands the
|
||||||
|
// text spells, so the sweep can pin the assembled bytes against
|
||||||
|
// EncodeExtension. The statements follow the registry: a mnemonic registered
|
||||||
|
// on a form this builder knows lands in the sweep in the same change.
|
||||||
|
func amd64ExtSweepStatements() (stmts, mnems []string, models [][]arch.ExtOperand, distinct int) {
|
||||||
|
memText, memModel := "(R9)", arch.ExtMemory(9, 0)
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, in := range arch.Extensions(arch.AMD64) {
|
||||||
|
if seen[in.Name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[in.Name] = true
|
||||||
|
distinct++
|
||||||
|
class := amd64SweepClass(in)
|
||||||
|
// The two-vector forms narrow one side: the half form the
|
||||||
|
// destination, the wide form the source, and the quarter forms pin
|
||||||
|
// one side to the XMM class.
|
||||||
|
srcClass, destClass := class, class
|
||||||
|
switch in.Form {
|
||||||
|
case arch.ExtFormAmdVec2Half:
|
||||||
|
destClass = arch.ExtXMM
|
||||||
|
if class == arch.ExtZMM {
|
||||||
|
destClass = arch.ExtYMM
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdVec2Wide:
|
||||||
|
srcClass = arch.ExtXMM
|
||||||
|
if class == arch.ExtZMM {
|
||||||
|
srcClass = arch.ExtYMM
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdVec2Quarter:
|
||||||
|
srcClass = arch.ExtXMM
|
||||||
|
case arch.ExtFormAmdVec2ToQuarter:
|
||||||
|
destClass = arch.ExtXMM
|
||||||
|
}
|
||||||
|
srcText, srcModel := amd64SweepVec(srcClass, 1)
|
||||||
|
src2Text, src2Model := amd64SweepVec(class, 2)
|
||||||
|
gprText, gprModel := amd64SweepGpr(in)
|
||||||
|
immText, immModel := amd64SweepImm(in)
|
||||||
|
add := func(stmt, mnem string, ops ...arch.ExtOperand) {
|
||||||
|
stmts = append(stmts, stmt)
|
||||||
|
mnems = append(mnems, mnem)
|
||||||
|
models = append(models, ops)
|
||||||
|
}
|
||||||
|
switch in.Form {
|
||||||
|
case arch.ExtFormAmdVec3:
|
||||||
|
dText, dModel := amd64SweepDest(in, class, 3)
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, src2Text, dText), in.Name, srcModel, src2Model, dModel)
|
||||||
|
if in.Mem == 2 {
|
||||||
|
dText, dModel = amd64SweepMaskedDest(in, class, 3)
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, memText, dText), in.Name, srcModel, memModel, dModel)
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdVec2, arch.ExtFormAmdVec2Half, arch.ExtFormAmdVec2Wide,
|
||||||
|
arch.ExtFormAmdVec2Quarter, arch.ExtFormAmdVec2ToQuarter:
|
||||||
|
dText, dModel := amd64SweepDest(in, destClass, 2)
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, srcText, dText), in.Name, srcModel, dModel)
|
||||||
|
if in.Mem == 1 {
|
||||||
|
dText, dModel = amd64SweepMaskedDest(in, destClass, 2)
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, memText, dText), in.Name, memModel, dModel)
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdMask2:
|
||||||
|
add(fmt.Sprintf("%s %s, %s, K3", in.Name, srcText, src2Text), in.Name, srcModel, src2Model, arch.ExtMask(3))
|
||||||
|
case arch.ExtFormAmdVecGprVec:
|
||||||
|
dText, dModel := amd64SweepVec(class, 2)
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, gprText, dText), in.Name, srcModel, gprModel, dModel)
|
||||||
|
case arch.ExtFormAmdGprVec:
|
||||||
|
dText, dModel := amd64SweepVec(class, 1)
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, gprText, dText), in.Name, gprModel, dModel)
|
||||||
|
case arch.ExtFormAmdVecGpr:
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, srcText, gprText), in.Name, srcModel, gprModel)
|
||||||
|
case arch.ExtFormAmdVec3Imm:
|
||||||
|
dText, dModel := amd64SweepMaskedDest(in, class, 4)
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s, %s", in.Name, immText, srcText, src2Text, dText), in.Name, immModel, srcModel, src2Model, dModel)
|
||||||
|
if in.Mem == 3 {
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s, %s", in.Name, immText, srcText, memText, dText), in.Name, immModel, srcModel, memModel, dModel)
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdMask2Imm:
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s, K3", in.Name, immText, srcText, src2Text), in.Name, immModel, srcModel, src2Model, arch.ExtMask(3))
|
||||||
|
if in.Mem == 3 {
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s, K3", in.Name, immText, srcText, memText), in.Name, immModel, srcModel, memModel, arch.ExtMask(3))
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdVec2Imm:
|
||||||
|
dText, dModel := amd64SweepMaskedDest(in, class, 3)
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s", in.Name, immText, srcText, dText), in.Name, immModel, srcModel, dModel)
|
||||||
|
if in.Mem == 2 {
|
||||||
|
add(fmt.Sprintf("%s %s, %s, %s", in.Name, immText, memText, dText), in.Name, immModel, memModel, dModel)
|
||||||
|
}
|
||||||
|
case arch.ExtFormAmdMemVec:
|
||||||
|
dText, dModel := amd64SweepVec(class, 1)
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, memText, dText), in.Name, memModel, dModel)
|
||||||
|
case arch.ExtFormAmdVecMem:
|
||||||
|
add(fmt.Sprintf("%s %s, %s", in.Name, srcText, memText), in.Name, srcModel, memModel)
|
||||||
|
default:
|
||||||
|
stmts = append(stmts, "")
|
||||||
|
mnems = append(mnems, in.Name)
|
||||||
|
models = append(models, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stmts, mnems, models, distinct
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64AssembleExtensionSweep drives every distinct registered mnemonic
|
||||||
|
// through the full assembler from .s text: the register-form statement of the
|
||||||
|
// mnemonic's first entry, and the memory-form statement beside it where the
|
||||||
|
// entry carries a memory position. Every statement must assemble, and every
|
||||||
|
// body must equal EncodeExtension's encoding of the model operands the text
|
||||||
|
// spells, so the front end and the registry cannot drift apart on any family.
|
||||||
|
func TestAmd64AssembleExtensionSweep(t *testing.T) {
|
||||||
|
stmts, mnems, models, distinct := amd64ExtSweepStatements()
|
||||||
|
if distinct != 86 {
|
||||||
|
t.Errorf("the amd64 layer registers %d distinct mnemonics, want 86", distinct)
|
||||||
|
}
|
||||||
|
for i, stmt := range stmts {
|
||||||
|
if stmt == "" {
|
||||||
|
t.Errorf("%s registers a form the sweep builder does not spell", mnems[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body := amd64ExtProbe(t, stmt)
|
||||||
|
want, err := EncodeExtension(arch.AMD64, mnems[i], models[i]...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: registry encode: %v", stmt, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(body, want) {
|
||||||
|
t.Errorf("%s:\n got %x\n want %x (the registry encoding)", stmt, body, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// encodeAmd64Family routes a mnemonic through the amd64 corpus families the
|
||||||
|
// dedicated amd64_*.go files implement. The first family that owns the name
|
||||||
|
// decides the outcome: its bytes, or its error. A name no family claims
|
||||||
|
// falls through to the scalar dispatch in (*enc).encode untouched.
|
||||||
|
func (e *enc) encodeAmd64Family(upper string, ops []Operand) (bool, error) {
|
||||||
|
if ok, err := e.encodeX87(upper, ops); ok {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if ok, err := e.encodeSystem(upper, ops); ok {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if ok, err := e.encodeXsave(upper, ops); ok {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if ok, err := e.encodeSSEMore(upper, ops); ok {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64FamilyEncodable mirrors encodeAmd64Family for the lint-time
|
||||||
|
// predicate: true when some family owns the mnemonic, whatever the operand
|
||||||
|
// shapes. It must claim exactly the names encodeAmd64Family does.
|
||||||
|
func amd64FamilyEncodable(upper string) bool {
|
||||||
|
if _, ok := x87NoOperand[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := x87Arith[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := x87FCmov[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := x87Compare[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := x87MemUnary[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := x87Fxsav[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if upper == "FADDDP" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := systemNoOperand[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if b, size := splitSize(upper); size != 0 {
|
||||||
|
switch upper[len(upper)-1] {
|
||||||
|
case 'B', 'W', 'L', 'Q':
|
||||||
|
if _, ok := stringOp[b]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m, ok := sysRm[b]; ok && m.sized {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := nopWidth[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := cacheControl[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := movbeSize[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := randSource[base(upper, 6)]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := fsGsBase[base(upper, 8)]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := descTable[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := sysRm[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := selectorRead[base(upper, 3)]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := farSegLoad[base(upper, 3)]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if upper == "CMPXCHG8B" || upper == "CMPXCHG16B" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if upper == "INVPCID" || upper == "XABORT" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := xsaveTable[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if before, ok := strings.CutSuffix(upper, "64"); ok {
|
||||||
|
if _, ok := xsaveTable[before]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch upper {
|
||||||
|
case "EMMS", "PSHUFW", "PSLLO", "PSRLO", "PMOVMSKB", "MOVQOZX", "MOVZWW", "MOVSWW":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := sseMaskmov[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := mmxShiftImm[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := mmxShiftVar[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,566 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// This file implements the amd64 SIMD pieces the main tables lack: the MMX
|
||||||
|
// bank glue (EMMS, the masked stores, the MMX shifts and shuffle, the
|
||||||
|
// byte-mask extract), the MOVQ bank crossings' odd spellings and the leaf
|
||||||
|
// aliases. Every encoding here is pinned byte for byte against go tool asm
|
||||||
|
// through the corpus lines in amd64_sse_test.go.
|
||||||
|
|
||||||
|
// sseMaskmov maps the masked cache-line stores to their prefix and opcode:
|
||||||
|
// OP src, dst with the second operand in the reg field, no memory operand.
|
||||||
|
var sseMaskmov = map[string]sseBin{
|
||||||
|
"MASKMOVQ": {0, 0xF7, false},
|
||||||
|
"MASKMOVOU": {0x66, 0xF7, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseMoreBin maps the packed and scalar legacy binaries the main table
|
||||||
|
// lacks, reg = destination and r/m = source: the float comparisons, square
|
||||||
|
// roots and reciprocal estimates, the SSE3 horizontal arithmetic, the SSE4.1
|
||||||
|
// packed integers and the SSSE3 sign and horizontal ops. The MMX twins of
|
||||||
|
// the 0x66-prefixed members drop the prefix in the shared encoder.
|
||||||
|
var sseMoreBin = map[string]sseBin{
|
||||||
|
"COMISS": {0, 0x2F, false},
|
||||||
|
"UCOMISS": {0, 0x2E, false},
|
||||||
|
"UCOMISD": {0x66, 0x2E, false},
|
||||||
|
"SQRTPS": {0, 0x51, false},
|
||||||
|
"SQRTPD": {0x66, 0x51, false},
|
||||||
|
"SQRTSS": {0xF3, 0x51, false},
|
||||||
|
"RCPPS": {0, 0x53, false},
|
||||||
|
"RCPSS": {0xF3, 0x53, false},
|
||||||
|
"RSQRTPS": {0, 0x52, false},
|
||||||
|
"RSQRTSS": {0xF3, 0x52, false},
|
||||||
|
"ADDSUBPD": {0x66, 0xD0, false},
|
||||||
|
"ADDSUBPS": {0xF2, 0xD0, false},
|
||||||
|
"HADDPD": {0x66, 0x7C, false},
|
||||||
|
"HADDPS": {0xF2, 0x7C, false},
|
||||||
|
"HSUBPD": {0x66, 0x7D, false},
|
||||||
|
"HSUBPS": {0xF2, 0x7D, false},
|
||||||
|
"MOVDDUP": {0xF2, 0x12, false},
|
||||||
|
"MOVSHDUP": {0xF3, 0x16, false},
|
||||||
|
"MOVSLDUP": {0xF3, 0x12, false},
|
||||||
|
"LDDQU": {0xF2, 0xF0, false},
|
||||||
|
"MOVNTDQA": {0x66, 0x2A, true},
|
||||||
|
"PTEST": {0x66, 0x17, true},
|
||||||
|
"PABSB": {0x66, 0x1C, true},
|
||||||
|
"PABSW": {0x66, 0x1D, true},
|
||||||
|
"PABSD": {0x66, 0x1E, true},
|
||||||
|
"PACKSSWB": {0x66, 0x63, false},
|
||||||
|
"PACKUSWB": {0x66, 0x67, false},
|
||||||
|
"PACKSSLW": {0x66, 0x6B, false},
|
||||||
|
"PACKUSDW": {0x66, 0x2B, true},
|
||||||
|
"PADDSB": {0x66, 0xEC, false},
|
||||||
|
"PADDSW": {0x66, 0xED, false},
|
||||||
|
"PADDUSB": {0x66, 0xDC, false},
|
||||||
|
"PADDUSW": {0x66, 0xDD, false},
|
||||||
|
"PAVGB": {0x66, 0xE0, false},
|
||||||
|
"PAVGW": {0x66, 0xE3, false},
|
||||||
|
"PCMPEQQ": {0x66, 0x29, true},
|
||||||
|
"PCMPGTQ": {0x66, 0x37, true},
|
||||||
|
"PHADDW": {0x66, 0x01, true},
|
||||||
|
"PHADDD": {0x66, 0x02, true},
|
||||||
|
"PHADDSW": {0x66, 0x03, true},
|
||||||
|
"PHSUBW": {0x66, 0x05, true},
|
||||||
|
"PHSUBD": {0x66, 0x06, true},
|
||||||
|
"PHSUBSW": {0x66, 0x07, true},
|
||||||
|
"PHMINPOSUW": {0x66, 0x41, true},
|
||||||
|
"PMADDUBSW": {0x66, 0x04, true},
|
||||||
|
"PMADDWL": {0x66, 0xF5, false},
|
||||||
|
"PMAXSB": {0x66, 0x3C, true},
|
||||||
|
"PMAXSD": {0x66, 0x3D, true},
|
||||||
|
"PMAXSW": {0x66, 0xEE, false},
|
||||||
|
"PMAXUB": {0x66, 0xDE, false},
|
||||||
|
"PMAXUD": {0x66, 0x3F, true},
|
||||||
|
"PMAXUW": {0x66, 0x3E, true},
|
||||||
|
"PMINSB": {0x66, 0x38, true},
|
||||||
|
"PMINSD": {0x66, 0x39, true},
|
||||||
|
"PMINSW": {0x66, 0xEA, false},
|
||||||
|
"PMINUB": {0x66, 0xDA, false},
|
||||||
|
"PMINUD": {0x66, 0x3B, true},
|
||||||
|
"PMINUW": {0x66, 0x3A, true},
|
||||||
|
"PMULDQ": {0x66, 0x28, true},
|
||||||
|
"PMULLD": {0x66, 0x40, true},
|
||||||
|
"PMULHRSW": {0x66, 0x0B, true},
|
||||||
|
"PMULHUW": {0x66, 0xE4, false},
|
||||||
|
"PMULHW": {0x66, 0xE5, false},
|
||||||
|
"PMULLW": {0x66, 0xD5, false},
|
||||||
|
"PMULULQ": {0x66, 0xF4, false},
|
||||||
|
"PCMPGTL": {0x66, 0x66, false},
|
||||||
|
"PMOVSXBD": {0x66, 0x21, true},
|
||||||
|
"PMOVSXBQ": {0x66, 0x22, true},
|
||||||
|
"PMOVSXBW": {0x66, 0x20, true},
|
||||||
|
"PMOVSXDQ": {0x66, 0x25, true},
|
||||||
|
"PMOVSXWD": {0x66, 0x23, true},
|
||||||
|
"PMOVSXWQ": {0x66, 0x24, true},
|
||||||
|
"PMOVZXBD": {0x66, 0x31, true},
|
||||||
|
"PMOVZXBQ": {0x66, 0x32, true},
|
||||||
|
"PMOVZXBW": {0x66, 0x30, true},
|
||||||
|
"PMOVZXDQ": {0x66, 0x35, true},
|
||||||
|
"PMOVZXWD": {0x66, 0x33, true},
|
||||||
|
"PMOVZXWQ": {0x66, 0x34, true},
|
||||||
|
// The conversion aliases the Plan 9 table spells with an L: the dword
|
||||||
|
// sources and destinations of the packed integer/float converts.
|
||||||
|
"CVTPL2PD": {0xF3, 0xE6, false},
|
||||||
|
"CVTPL2PS": {0, 0x5B, false},
|
||||||
|
"CVTPD2PL": {0xF2, 0xE6, false},
|
||||||
|
"CVTPS2PL": {0x66, 0x5B, false},
|
||||||
|
"CVTTPD2PL": {0x66, 0xE6, false},
|
||||||
|
"CVTTPS2PL": {0xF3, 0x5B, false},
|
||||||
|
"PSADBW": {0x66, 0xF6, false},
|
||||||
|
"PSUBSB": {0x66, 0xE8, false},
|
||||||
|
"PSUBSW": {0x66, 0xE9, false},
|
||||||
|
"PSUBUSB": {0x66, 0xD8, false},
|
||||||
|
"PSUBUSW": {0x66, 0xD9, false},
|
||||||
|
"PSIGNB": {0x66, 0x08, true},
|
||||||
|
"PSIGNW": {0x66, 0x09, true},
|
||||||
|
"PSIGND": {0x66, 0x0A, true},
|
||||||
|
"PUNPCKHBW": {0x66, 0x68, false},
|
||||||
|
"PUNPCKHLQ": {0x66, 0x6A, false},
|
||||||
|
"PUNPCKHQDQ": {0x66, 0x6D, false},
|
||||||
|
"PUNPCKHWL": {0x66, 0x69, false},
|
||||||
|
"PUNPCKLLQ": {0x66, 0x62, false},
|
||||||
|
"PUNPCKLQDQ": {0x66, 0x6C, false},
|
||||||
|
"PUNPCKLWL": {0x66, 0x61, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseMoreImm3 maps the imm8-controlled three-operand instructions the main
|
||||||
|
// table lacks: OP $imm, src, dst.
|
||||||
|
var sseMoreImm3 = map[string]sseImm3{
|
||||||
|
"ROUNDPS": {0x66, 0x08, true},
|
||||||
|
"ROUNDPD": {0x66, 0x09, true},
|
||||||
|
"ROUNDSS": {0x66, 0x0A, true},
|
||||||
|
"ROUNDSD": {0x66, 0x0B, true},
|
||||||
|
"DPPS": {0x66, 0x40, true},
|
||||||
|
"DPPD": {0x66, 0x41, true},
|
||||||
|
"BLENDPS": {0x66, 0x0C, true},
|
||||||
|
"BLENDPD": {0x66, 0x0D, true},
|
||||||
|
"INSERTPS": {0x66, 0x21, true},
|
||||||
|
"MPSADBW": {0x66, 0x42, true},
|
||||||
|
"PCMPESTRM": {0x66, 0x60, true},
|
||||||
|
"PCMPESTRI": {0x66, 0x61, true},
|
||||||
|
"PCMPISTRM": {0x66, 0x62, true},
|
||||||
|
"PCMPISTRI": {0x66, 0x63, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseBlendv maps the variable blends whose implicit mask is X0: the first
|
||||||
|
// operand must be the literal X0, the register the hardware reads.
|
||||||
|
var sseBlendv = map[string]sseBin{
|
||||||
|
"BLENDVPS": {0x66, 0x14, true},
|
||||||
|
"BLENDVPD": {0x66, 0x15, true},
|
||||||
|
"PBLENDVB": {0x66, 0x10, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseHighLow maps the high/low half moves to their load/store opcode pair.
|
||||||
|
// A memory source loads (reg = destination), a memory destination stores
|
||||||
|
// (reg = the register source).
|
||||||
|
var sseHighLow = map[string]sseMove{
|
||||||
|
"MOVHPD": {0x66, 0x16, 0x17},
|
||||||
|
"MOVHPS": {0, 0x16, 0x17},
|
||||||
|
"MOVLPD": {0x66, 0x12, 0x13},
|
||||||
|
"MOVLPS": {0, 0x12, 0x13},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseRegReg maps the register-to-register half moves, register destination
|
||||||
|
// and register source alone: MOVHLPS and MOVLHPS.
|
||||||
|
var sseRegReg = map[string]sseBin{
|
||||||
|
"MOVHLPS": {0, 0x12, false},
|
||||||
|
"MOVLHPS": {0, 0x16, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseMovmsk maps the sign-mask extractions to a GPR: OP vec, gpr.
|
||||||
|
var sseMovmsk = map[string]sseBin{
|
||||||
|
"MOVMSKPS": {0, 0x50, false},
|
||||||
|
"MOVMSKPD": {0x66, 0x50, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseMovnt maps the non-temporal stores, OP reg, mem, plus MOVNTDQA's load
|
||||||
|
// (which rides sseMoreBin).
|
||||||
|
var sseMovnt = map[string]sseBin{
|
||||||
|
"MOVNTPS": {0, 0x2B, false},
|
||||||
|
"MOVNTPD": {0x66, 0x2B, false},
|
||||||
|
"MOVNTQ": {0, 0xE7, false},
|
||||||
|
"MOVNTO": {0x66, 0xE7, false},
|
||||||
|
"MOVNTIL": {0, 0xC3, false},
|
||||||
|
"MOVNTIQ": {0, 0xC3, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmxShiftImm lists the packed integer shifts whose immediate form the MMX
|
||||||
|
// bank spells without the 0x66 prefix; the digit rides the 0F 71/72/73
|
||||||
|
// group, the same /digits the XMM forms carry.
|
||||||
|
var mmxShiftImm = map[string]sseShift{
|
||||||
|
"PSLLW": {0x71, 6},
|
||||||
|
"PSRLW": {0x71, 2},
|
||||||
|
"PSRAW": {0x71, 4},
|
||||||
|
"PSLLL": {0x72, 6},
|
||||||
|
"PSRLL": {0x72, 2},
|
||||||
|
"PSRAL": {0x72, 4},
|
||||||
|
"PSLLQ": {0x73, 6},
|
||||||
|
"PSRLQ": {0x73, 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmxShiftVar lists the variable-count forms over the MMX bank, the same
|
||||||
|
// opcodes the XMM variable shifts ride, prefix dropped.
|
||||||
|
var mmxShiftVar = map[string]byte{
|
||||||
|
"PSLLW": 0xF1,
|
||||||
|
"PSRLW": 0xD1,
|
||||||
|
"PSRAW": 0xE1,
|
||||||
|
"PSLLL": 0xF2,
|
||||||
|
"PSRLL": 0xD2,
|
||||||
|
"PSRAL": 0xE2,
|
||||||
|
"PSLLQ": 0xF3,
|
||||||
|
"PSRLQ": 0xD3,
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseOctaShift lists the octa byte shifts' extra spellings: PSLLO and PSRLO
|
||||||
|
// are the Plan 9 names of PSLLDQ/PSRLDQ, XMM only.
|
||||||
|
var sseOctaShift = map[string]sseShift{
|
||||||
|
"PSLLO": {0x73, 7},
|
||||||
|
"PSRLO": {0x73, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMmx reports whether the operand is an MMX register.
|
||||||
|
func isMmx(op Operand) bool {
|
||||||
|
r, ok := op.(Reg)
|
||||||
|
return ok && r.mmx
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEMore encodes the MMX glue and the leaf SIMD spellings. It
|
||||||
|
// reports whether the mnemonic belongs to the layer; a false result hands
|
||||||
|
// the mnemonic back to the caller.
|
||||||
|
func (e *enc) encodeSSEMore(upper string, ops []Operand) (bool, error) {
|
||||||
|
if upper == "EMMS" {
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return true, fmt.Errorf("EMMS takes no operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
return true, e.emit(&instr{opcode: []byte{0x0F, 0x77}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
if m, ok := sseMaskmov[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, ok1 := ops[0].(Reg)
|
||||||
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
|
if !ok1 || !ok2 || !srcReg.isVec() && !srcReg.mmx || !dstReg.isVec() && !dstReg.mmx {
|
||||||
|
return true, fmt.Errorf("%s takes two vector or MMX registers", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, srcReg, 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// The packed shifts over the MMX bank drop the 0x66 prefix the XMM forms
|
||||||
|
// carry; only a shift name enters the MMX path, the XMM spellings of the
|
||||||
|
// shifts and every other packed binary fall through to the main tables.
|
||||||
|
if _, isShift := mmxShiftImm[upper]; isShift {
|
||||||
|
if e.encodeMmxShiftGate(ops) {
|
||||||
|
return e.encodeMmxShift(upper, ops)
|
||||||
|
}
|
||||||
|
} else if _, isVar := mmxShiftVar[upper]; isVar {
|
||||||
|
if e.encodeMmxShiftGate(ops) {
|
||||||
|
return e.encodeMmxShift(upper, ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PSHUFW is the MMX word shuffle, 0F 70 with no prefix: the XMM twins
|
||||||
|
// (PSHUFD and friends) dispatch through the main shuffle table.
|
||||||
|
if upper == "PSHUFW" {
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return true, fmt.Errorf("PSHUFW expects 3 operands ($imm, src, dst), got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return true, fmt.Errorf("PSHUFW needs an imm8 first operand")
|
||||||
|
}
|
||||||
|
immByte, err := imm8(int64(imm))
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
dstReg, ok2 := ops[2].(Reg)
|
||||||
|
if !ok2 || !dstReg.mmx {
|
||||||
|
return true, fmt.Errorf("PSHUFW destination must be an MMX register")
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x70}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, ops[1], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
i.imm = []byte{immByte}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
if spec, ok := sseOctaShift[upper]; ok {
|
||||||
|
return e.encodeMmxShiftForm(upper, spec, 0x66, ops)
|
||||||
|
}
|
||||||
|
// The byte-mask extract over the MMX bank rides the same 0F D7 opcode
|
||||||
|
// without the prefix; the XMM spelling falls through.
|
||||||
|
if upper == "PMOVMSKB" && len(ops) == 2 {
|
||||||
|
if srcReg, ok := ops[0].(Reg); ok && srcReg.mmx {
|
||||||
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
|
if !ok2 || dstReg.isVec() || dstReg.mmx {
|
||||||
|
return true, fmt.Errorf("%s destination must be a general register", upper)
|
||||||
|
}
|
||||||
|
i := newInstr(4, []byte{0x0F, 0xD7})
|
||||||
|
if err := setRM(i, dstReg, srcReg, 4); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// MOVQOZX is the octa-to-quad zero-extend load, F3 0F D6: an MMX or
|
||||||
|
// memory source into an XMM destination, the MOVQ2DQ opcode.
|
||||||
|
if upper == "MOVQOZX" {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("MOVQOZX expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || !dstReg.isVec() {
|
||||||
|
return true, fmt.Errorf("MOVQOZX destination must be an XMM register")
|
||||||
|
}
|
||||||
|
switch ops[0].(type) {
|
||||||
|
case Reg, Mem, sbMem:
|
||||||
|
default:
|
||||||
|
return true, fmt.Errorf("MOVQOZX source must be an MMX register or memory")
|
||||||
|
}
|
||||||
|
i := &instr{prefix: 0xF3, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// MOVZWW and MOVSWW are the word zero/sign-extend moves under aliases:
|
||||||
|
// the MOVWLZX and MOVWLSX opcodes carrying the word width's 0x66 prefix.
|
||||||
|
switch upper {
|
||||||
|
case "MOVZWW", "MOVSWW":
|
||||||
|
op := []byte{0x0F, 0xB7}
|
||||||
|
if upper == "MOVSWW" {
|
||||||
|
op[1] = 0xBF
|
||||||
|
}
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok {
|
||||||
|
return true, fmt.Errorf("%s destination must be a register", upper)
|
||||||
|
}
|
||||||
|
i := newInstr(2, op)
|
||||||
|
if err := setRM(i, dstReg, ops[0], 2); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// The packed and scalar binaries: reg = destination, r/m = source, the
|
||||||
|
// shared encoder carrying the MMX prefix drop.
|
||||||
|
if m, ok := sseMoreBin[upper]; ok {
|
||||||
|
return true, e.encodeSSEBin(m, ops)
|
||||||
|
}
|
||||||
|
// The imm8-controlled instructions: OP $imm, src, dst.
|
||||||
|
if m, ok := sseMoreImm3[upper]; ok {
|
||||||
|
return true, e.encodeSSEImm3(m, ops)
|
||||||
|
}
|
||||||
|
// The variable blends with their implicit X0 mask: the first operand is
|
||||||
|
// the literal X0, the register the encoding leaves out.
|
||||||
|
if m, ok := sseBlendv[upper]; ok {
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return true, fmt.Errorf("%s expects 3 operands (X0, src, dst), got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
x0, ok := ops[0].(Reg)
|
||||||
|
if !ok || !x0.isVec() || x0.idx != 0 || x0.size != 16 {
|
||||||
|
return true, fmt.Errorf("%s first operand must be X0", upper)
|
||||||
|
}
|
||||||
|
return true, e.encodeSSEBin(m, ops[1:])
|
||||||
|
}
|
||||||
|
// The high/low half moves split by direction: a memory source loads, a
|
||||||
|
// memory destination stores, both two-operand.
|
||||||
|
if m, ok := sseHighLow[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, srcVec := vecReg(ops[0])
|
||||||
|
dstReg, dstVec := vecReg(ops[1])
|
||||||
|
var op byte
|
||||||
|
var reg Reg
|
||||||
|
var rm Operand
|
||||||
|
switch {
|
||||||
|
case srcVec && isX86Mem(ops[1]):
|
||||||
|
op, reg, rm = m.store, srcReg, ops[1]
|
||||||
|
case dstVec && isX86Mem(ops[0]):
|
||||||
|
op, reg, rm = m.load, dstReg, ops[0]
|
||||||
|
default:
|
||||||
|
return true, fmt.Errorf("%s takes one vector register and one memory operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, reg, rm, 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// The register-to-register half moves.
|
||||||
|
if m, ok := sseRegReg[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, srcVec := vecReg(ops[0])
|
||||||
|
dstReg, dstVec := vecReg(ops[1])
|
||||||
|
if !srcVec || !dstVec {
|
||||||
|
return true, fmt.Errorf("%s takes two XMM registers", upper)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, srcReg, 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// The sign-mask extractions: the vector source's sign bits pack into a
|
||||||
|
// general register.
|
||||||
|
if m, ok := sseMovmsk[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, srcVec := vecReg(ops[0])
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !srcVec || !ok || dstReg.isVec() || dstReg.mmx {
|
||||||
|
return true, fmt.Errorf("%s takes a vector register and a general register", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, srcReg, 4); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// The non-temporal stores: the register source rides reg, the memory
|
||||||
|
// destination r/m; MOVNTIL/IQ store from a general register and MOVNTIQ
|
||||||
|
// carries REX.W.
|
||||||
|
if m, ok := sseMovnt[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
var srcReg Reg
|
||||||
|
switch r := ops[0].(type) {
|
||||||
|
case Reg:
|
||||||
|
if upper == "MOVNTIL" || upper == "MOVNTIQ" {
|
||||||
|
if r.isVec() || r.mmx {
|
||||||
|
return true, fmt.Errorf("%s source must be a general register", upper)
|
||||||
|
}
|
||||||
|
srcReg = r
|
||||||
|
} else {
|
||||||
|
if !r.isVec() && !r.mmx {
|
||||||
|
return true, fmt.Errorf("%s source must be a vector or MMX register", upper)
|
||||||
|
}
|
||||||
|
srcReg = r
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return true, fmt.Errorf("%s source must be a register", upper)
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[1]) {
|
||||||
|
return true, fmt.Errorf("%s destination must be memory", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1, rexW: upper == "MOVNTIQ"}
|
||||||
|
if err := setRM(i, srcReg, ops[1], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// EXTRACTPS is the lane extract to a GPR or memory, the PEXTR layout.
|
||||||
|
if upper == "EXTRACTPS" {
|
||||||
|
return true, e.encodeSSEExtract(sseExtract{op: []byte{0x0F, 0x3A, 0x17}}, ops)
|
||||||
|
}
|
||||||
|
// CVTSL2SS and CVTSQ2SS are the integer-to-scalar-single converts, the
|
||||||
|
// CVTSL2SD pair's F3 twin: F3 0F 2A with reg = XMM destination, REX.W
|
||||||
|
// on the quad source spelling.
|
||||||
|
if upper == "CVTSL2SS" || upper == "CVTSQ2SS" {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || !dstReg.isVec() {
|
||||||
|
return true, fmt.Errorf("%s destination must be a vector register", upper)
|
||||||
|
}
|
||||||
|
i := newInstr(0, []byte{0x0F, 0x2A})
|
||||||
|
i.rexW = upper == "CVTSQ2SS"
|
||||||
|
i.prefix = 0xF3
|
||||||
|
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeMmxShiftGate reports whether the shift's destination operand is an
|
||||||
|
// MMX register, the case the bank's own prefix-free forms cover.
|
||||||
|
func (e *enc) encodeMmxShiftGate(ops []Operand) bool {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
return ok && dstReg.mmx
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeMmxShift routes the MMX shift between its immediate form
|
||||||
|
// (OP $imm, dst, the 0F 71/72/73 /digit group) and its variable-count form
|
||||||
|
// (OP count, dst, the 0F D1-F3 row).
|
||||||
|
func (e *enc) encodeMmxShift(upper string, ops []Operand) (bool, error) {
|
||||||
|
if spec, ok := mmxShiftImm[upper]; ok {
|
||||||
|
if _, isImm := ops[0].(Imm); isImm {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
immByte, err := imm8(int64(ops[0].(Imm)))
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, spec.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, spec.digit, ops[1], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
i.imm = []byte{immByte}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op, ok := mmxShiftVar[upper]; ok {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !vecOrMem(ops[0]) && !isMmx(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s count must be an immediate, an MMX register or memory", upper)
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || !dstReg.mmx {
|
||||||
|
return true, fmt.Errorf("%s destination must be an MMX register", upper)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
return true, fmt.Errorf("unsupported instruction %q", upper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeMmxShiftForm emits one XMM octa shift: OP $imm, dst, the 0x66
|
||||||
|
// prefix carried.
|
||||||
|
func (e *enc) encodeMmxShiftForm(upper string, spec sseShift, prefix byte, ops []Operand) (bool, error) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return true, fmt.Errorf("%s needs an immediate count", upper)
|
||||||
|
}
|
||||||
|
immByte, err := imm8(int64(imm))
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
|
if !ok2 || !dstReg.isVec() {
|
||||||
|
return true, fmt.Errorf("%s destination must be the second, vector operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: prefix, opcode: []byte{0x0F, spec.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, spec.digit, dstReg, 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
i.imm = []byte{immByte}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,467 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// This file implements the system, flag, string and segment families the Go
|
||||||
|
// assembler carries: the no-operand controls, the string primitives, the
|
||||||
|
// sign-extension pair, the multi-byte no-ops, the cache controls, MOVBE, the
|
||||||
|
// compare-exchange doubles, the random source pair, the FS/GS base pair, the
|
||||||
|
// descriptor-table controls and the LAR/LSL selector reads. Every encoding
|
||||||
|
// here is pinned byte for byte against go tool asm through the corpus lines
|
||||||
|
// in amd64_system_test.go.
|
||||||
|
|
||||||
|
// systemNoOperand maps a fixed no-operand mnemonic to its opcode bytes, the
|
||||||
|
// prefixes spelled out in full.
|
||||||
|
var systemNoOperand = map[string][]byte{
|
||||||
|
"CLC": {0xF8},
|
||||||
|
"STC": {0xF9},
|
||||||
|
"CMC": {0xF5},
|
||||||
|
"CLI": {0xFA},
|
||||||
|
"STI": {0xFB},
|
||||||
|
"HLT": {0xF4},
|
||||||
|
"ICEBP": {0xF1},
|
||||||
|
"XLAT": {0xD7},
|
||||||
|
"LAHF": {0x9F},
|
||||||
|
"SAHF": {0x9E},
|
||||||
|
"PUSHFW": {0x66, 0x9C},
|
||||||
|
"POPFW": {0x66, 0x9D},
|
||||||
|
"IRETW": {0x66, 0xCF},
|
||||||
|
"IRETL": {0xCF},
|
||||||
|
"IRETQ": {0x48, 0xCF},
|
||||||
|
"UD1": {0x0F, 0xB9},
|
||||||
|
"UD2": {0x0F, 0x0B},
|
||||||
|
"CLAC": {0x0F, 0x01, 0xCA},
|
||||||
|
"STAC": {0x0F, 0x01, 0xCB},
|
||||||
|
"CLTS": {0x0F, 0x06},
|
||||||
|
"INVD": {0x0F, 0x08},
|
||||||
|
"WBINVD": {0x0F, 0x09},
|
||||||
|
"SWAPGS": {0x0F, 0x01, 0xF8},
|
||||||
|
"RSM": {0x0F, 0xAA},
|
||||||
|
"MONITOR": {0x0F, 0x01, 0xC8},
|
||||||
|
"MWAIT": {0x0F, 0x01, 0xC9},
|
||||||
|
"RDMSR": {0x0F, 0x32},
|
||||||
|
"WRMSR": {0x0F, 0x30},
|
||||||
|
"RDPMC": {0x0F, 0x33},
|
||||||
|
"RDPKRU": {0x0F, 0x01, 0xEE},
|
||||||
|
"WRPKRU": {0x0F, 0x01, 0xEF},
|
||||||
|
"XSETBV": {0x0F, 0x01, 0xD1},
|
||||||
|
"SYSENTER": {0x0F, 0x34},
|
||||||
|
"SYSENTER64": {0x48, 0x0F, 0x34},
|
||||||
|
"SYSEXIT": {0x0F, 0x35},
|
||||||
|
"SYSEXIT64": {0x48, 0x0F, 0x35},
|
||||||
|
"SYSRET": {0x0F, 0x07},
|
||||||
|
"LEAVE": {0xC9},
|
||||||
|
"LEAVEQ": {0xC9},
|
||||||
|
"XEND": {0x0F, 0x01, 0xD5},
|
||||||
|
"XTEST": {0x0F, 0x01, 0xD6},
|
||||||
|
"CBW": {0x66, 0x98},
|
||||||
|
"CWDE": {0x98},
|
||||||
|
"CDQE": {0x48, 0x98},
|
||||||
|
"CWD": {0x66, 0x99},
|
||||||
|
"CDQ": {0x99},
|
||||||
|
"CQO": {0x48, 0x99},
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringOp maps the string-primitive bases to their 32-bit opcode; the byte
|
||||||
|
// form is one lower, the word spelling carries 0x66 and the quad spelling
|
||||||
|
// REX.W, exactly the prefix ladder newInstr applies.
|
||||||
|
var stringOp = map[string]byte{
|
||||||
|
"CMPS": 0xA7,
|
||||||
|
"INS": 0x6D,
|
||||||
|
"LODS": 0xAD,
|
||||||
|
"OUTS": 0x6F,
|
||||||
|
"SCAS": 0xAF,
|
||||||
|
}
|
||||||
|
|
||||||
|
// nopWidth maps the multi-byte no-op spellings to their operand size.
|
||||||
|
var nopWidth = map[string]int{
|
||||||
|
"NOPW": 2,
|
||||||
|
"NOPL": 4,
|
||||||
|
"NOPQ": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheControl maps the one-memory-operand cache controls to their mandatory
|
||||||
|
// prefix, opcode group and /digit.
|
||||||
|
var cacheControl = map[string]struct {
|
||||||
|
prefix byte
|
||||||
|
op []byte
|
||||||
|
digit int
|
||||||
|
}{
|
||||||
|
"CLFLUSH": {0, []byte{0x0F, 0xAE}, 7},
|
||||||
|
"CLFLUSHOPT": {0x66, []byte{0x0F, 0xAE}, 7},
|
||||||
|
"INVLPG": {0, []byte{0x0F, 0x01}, 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
// movbeSize maps the MOVBE spellings to their operand size.
|
||||||
|
var movbeSize = map[string]int{
|
||||||
|
"MOVBEW": 2,
|
||||||
|
"MOVBEL": 4,
|
||||||
|
"MOVBEQ": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// randSource maps the random-source bases to their /digit (RDRAND /6,
|
||||||
|
// RDSEED /7); the destination register rides r/m, mod 11.
|
||||||
|
var randSource = map[string]int{
|
||||||
|
"RDRAND": 6,
|
||||||
|
"RDSEED": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
// fsGsBase maps the FS/GS base accessors to their /digit in the F3-prefixed
|
||||||
|
// 0F AE group; the L and Q spellings exist.
|
||||||
|
var fsGsBase = map[string]int{
|
||||||
|
"RDFSBASE": 0,
|
||||||
|
"RDGSBASE": 1,
|
||||||
|
"WRFSBASE": 2,
|
||||||
|
"WRGSBASE": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
// descTable maps the descriptor-table accesses to their /digit in 0F 01;
|
||||||
|
// each takes one memory operand alone.
|
||||||
|
var descTable = map[string]int{
|
||||||
|
"LGDT": 2,
|
||||||
|
"LIDT": 3,
|
||||||
|
"SGDT": 0,
|
||||||
|
"SIDT": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysRmEntry is one 0F 00/01 register-or-memory access. sized marks the
|
||||||
|
// members whose trailing width letter (SLDTW, STRQ, SMSWL) carries the width
|
||||||
|
// prefix ladder; the rest are fixed-width single names.
|
||||||
|
type sysRmEntry struct {
|
||||||
|
group byte
|
||||||
|
digit int
|
||||||
|
sized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysRm maps the system register accesses LLDT/LTR/VERR/VERW/SLDT/STR (group
|
||||||
|
// 0F 00), LMSW/SMSW (0F 01).
|
||||||
|
var sysRm = map[string]sysRmEntry{
|
||||||
|
"LLDT": {0x00, 2, false},
|
||||||
|
"LTR": {0x00, 3, false},
|
||||||
|
"VERR": {0x00, 4, false},
|
||||||
|
"VERW": {0x00, 5, false},
|
||||||
|
"SLDT": {0x00, 0, true},
|
||||||
|
"STR": {0x00, 1, true},
|
||||||
|
"LMSW": {0x01, 6, false},
|
||||||
|
"SMSW": {0x01, 4, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectorRead maps the selector reads LAR and LSL to their opcodes; both
|
||||||
|
// load the destination register from an r/m selector, width prefixes per the
|
||||||
|
// suffix.
|
||||||
|
var selectorRead = map[string]byte{
|
||||||
|
"LAR": 0x02,
|
||||||
|
"LSL": 0x03,
|
||||||
|
}
|
||||||
|
|
||||||
|
// farSegLoad maps the far-segment loads to their opcodes; memory source
|
||||||
|
// alone, destination register, width prefixes per the suffix.
|
||||||
|
var farSegLoad = map[string]byte{
|
||||||
|
"LFS": 0xB4,
|
||||||
|
"LGS": 0xB5,
|
||||||
|
"LSS": 0xB2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystem encodes the system, flag, string and segment families. It
|
||||||
|
// reports whether the mnemonic belongs to the family.
|
||||||
|
func (e *enc) encodeSystem(upper string, ops []Operand) (bool, error) {
|
||||||
|
if op, ok := systemNoOperand[upper]; ok {
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return true, fmt.Errorf("%s takes no operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
return true, e.emit(&instr{opcode: append([]byte(nil), op...), modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
// The string primitives carry a B/W/L/Q suffix only; CMPSD and friends
|
||||||
|
// are the SSE compare family's names and must reach their own dispatch.
|
||||||
|
if b, size := splitSize(upper); size != 0 {
|
||||||
|
switch upper[len(upper)-1] {
|
||||||
|
case 'B', 'W', 'L', 'Q':
|
||||||
|
if op32, ok := stringOp[b]; ok {
|
||||||
|
return true, e.encodeSystemString(upper, op32, ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if size, ok := nopWidth[upper]; ok {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, 0x1F})
|
||||||
|
if err := setRMDigit(i, 0, ops[0], size); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
if m, ok := cacheControl[upper]; ok {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: m.op, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
if _, ok := movbeSize[upper]; ok {
|
||||||
|
return true, e.encodeSystemMovbe(upper, ops)
|
||||||
|
}
|
||||||
|
if digit, ok := randSource[base(upper, 6)]; ok {
|
||||||
|
return true, e.encodeSystemRand(upper, digit, ops)
|
||||||
|
}
|
||||||
|
if digit, ok := fsGsBase[base(upper, 8)]; ok {
|
||||||
|
return true, e.encodeSystemFsGsBase(upper, digit, ops)
|
||||||
|
}
|
||||||
|
if digit, ok := descTable[upper]; ok {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x01}, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, digit, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
if m, ok := sysRm[upper]; ok {
|
||||||
|
return true, e.encodeSystemRm(upper, m, ops)
|
||||||
|
}
|
||||||
|
if b, size := splitSize(upper); size != 0 {
|
||||||
|
if m, ok := sysRm[b]; ok && m.sized {
|
||||||
|
return true, e.encodeSystemRm(upper, m, ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op, ok := selectorRead[base(upper, 3)]; ok {
|
||||||
|
return true, e.encodeSystemSelectorRead(upper, op, ops)
|
||||||
|
}
|
||||||
|
if op, ok := farSegLoad[base(upper, 3)]; ok {
|
||||||
|
return true, e.encodeSystemFarLoad(upper, op, ops)
|
||||||
|
}
|
||||||
|
if upper == "CMPXCHG8B" || upper == "CMPXCHG16B" {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := newInstr(0, []byte{0x0F, 0xC7})
|
||||||
|
i.rexW = upper == "CMPXCHG16B"
|
||||||
|
if err := setRMDigit(i, 1, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// INVPCID invalidates a translation-cache entry: 66 0F38 82 with the
|
||||||
|
// type in a general register and the descriptor in memory.
|
||||||
|
if upper == "INVPCID" {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("INVPCID expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("INVPCID requires a memory descriptor first")
|
||||||
|
}
|
||||||
|
srcReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || srcReg.isVec() {
|
||||||
|
return true, fmt.Errorf("INVPCID: the second operand must be a general register")
|
||||||
|
}
|
||||||
|
i := &instr{prefix: 0x66, opcode: []byte{0x0F, 0x38, 0x82}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, srcReg, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
// XABORT carries its imm8 status byte in the C6 F8 group form.
|
||||||
|
if upper == "XABORT" {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("XABORT expects 1 immediate operand, got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return true, fmt.Errorf("XABORT requires an immediate")
|
||||||
|
}
|
||||||
|
immByte, err := imm8(int64(imm))
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(&instr{opcode: []byte{0xC6, 0xF8}, modrm: -1, sib: -1, imm: []byte{immByte}})
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// base returns the first n characters of an upper-case mnemonic, or the empty
|
||||||
|
// string when the mnemonic is shorter: the safe head lookup for the families
|
||||||
|
// whose width suffix rides the tail (RDRANDW, RDFSBASEQ, LARW).
|
||||||
|
func base(upper string, n int) string {
|
||||||
|
if len(upper) <= n {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return upper[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemString encodes a string primitive: no operands, the width
|
||||||
|
// suffix picks the byte form, the 0x66 prefix or REX.W. Only the B/W/L/Q
|
||||||
|
// suffixes belong to the family: CMPSD and friends are the SSE compare
|
||||||
|
// family's names and must reach their own dispatch.
|
||||||
|
func (e *enc) encodeSystemString(upper string, op32 byte, ops []Operand) error {
|
||||||
|
switch upper[len(upper)-1] {
|
||||||
|
case 'B', 'W', 'L', 'Q':
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported instruction %q", upper)
|
||||||
|
}
|
||||||
|
b, size := splitSize(upper)
|
||||||
|
if _, ok := stringOp[b]; !ok || size == 0 {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", upper)
|
||||||
|
}
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return fmt.Errorf("%s takes no operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
// The byte spelling is the 32-bit opcode minus one; the W and Q forms
|
||||||
|
// ride newInstr's prefix ladder, the L form the bare opcode.
|
||||||
|
op := op32
|
||||||
|
if size == 1 {
|
||||||
|
op--
|
||||||
|
}
|
||||||
|
return e.emit(newInstr(size, []byte{op}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemMovbe encodes MOVBE: a register source stores (F1, reg = the
|
||||||
|
// register, r/m = memory), a register destination loads (F0, same fields).
|
||||||
|
func (e *enc) encodeSystemMovbe(mnem string, ops []Operand) error {
|
||||||
|
size := movbeSize[mnem]
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, srcIsReg := ops[0].(Reg)
|
||||||
|
dstReg, dstIsReg := ops[1].(Reg)
|
||||||
|
var op byte
|
||||||
|
var reg Reg
|
||||||
|
var rm Operand
|
||||||
|
switch {
|
||||||
|
case srcIsReg && isX86Mem(ops[1]):
|
||||||
|
op, reg, rm = 0xF1, srcReg, ops[1] // store
|
||||||
|
case dstIsReg && isX86Mem(ops[0]):
|
||||||
|
op, reg, rm = 0xF0, dstReg, ops[0] // load
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s takes one register and one memory operand", mnem)
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, 0x38, op})
|
||||||
|
if err := setRM(i, reg, rm, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemRand encodes RDRAND/RDSEED: the single register operand rides
|
||||||
|
// r/m under the /digit, mod 11, with the width prefix the suffix picks.
|
||||||
|
func (e *enc) encodeSystemRand(mnem string, digit int, ops []Operand) error {
|
||||||
|
b, size := splitSize(mnem)
|
||||||
|
if _, ok := randSource[b]; !ok || size == 0 {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
|
}
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 register operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[0].(Reg)
|
||||||
|
if !ok || dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s destination must be a general register", mnem)
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, 0xC7})
|
||||||
|
if err := setRMDigit(i, digit, dstReg, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemFsGsBase encodes the FS/GS base accessors: F3-prefixed 0F AE
|
||||||
|
// under the /digit, the register in r/m; the Q spellings add REX.W.
|
||||||
|
func (e *enc) encodeSystemFsGsBase(mnem string, digit int, ops []Operand) error {
|
||||||
|
b, size := splitSize(mnem)
|
||||||
|
if _, ok := fsGsBase[b]; !ok || (size != 4 && size != 8) {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
|
}
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 register operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[0].(Reg)
|
||||||
|
if !ok || dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s destination must be a general register", mnem)
|
||||||
|
}
|
||||||
|
i := newInstr(0, []byte{0x0F, 0xAE})
|
||||||
|
i.prefix = 0xF3
|
||||||
|
i.rexW = size == 8
|
||||||
|
if err := setRMDigit(i, digit, dstReg, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemRm encodes a 0F 00/01 r/m access: the operand is a register or
|
||||||
|
// memory; the sized members carry the width prefix ladder the suffix fixes.
|
||||||
|
func (e *enc) encodeSystemRm(mnem string, m sysRmEntry, ops []Operand) error {
|
||||||
|
size := 0
|
||||||
|
if m.sized {
|
||||||
|
_, size = splitSize(mnem)
|
||||||
|
if size == 0 {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, m.group})
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemSelectorRead encodes LAR/LSL: the destination register loads
|
||||||
|
// from an r/m selector, width prefixes per the suffix.
|
||||||
|
func (e *enc) encodeSystemSelectorRead(mnem string, op byte, ops []Operand) error {
|
||||||
|
b, size := splitSize(mnem)
|
||||||
|
if _, ok := selectorRead[b]; !ok || size == 0 {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
|
}
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s destination must be a general register", mnem)
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, op})
|
||||||
|
if err := setRM(i, dstReg, ops[0], size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSystemFarLoad encodes LFS/LGS/LSS: the destination register loads a
|
||||||
|
// far pointer from memory, width prefixes per the suffix.
|
||||||
|
func (e *enc) encodeSystemFarLoad(mnem string, op byte, ops []Operand) error {
|
||||||
|
b, size := splitSize(mnem)
|
||||||
|
if _, ok := farSegLoad[b]; !ok || size == 0 {
|
||||||
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
|
}
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return fmt.Errorf("%s requires a memory source", mnem)
|
||||||
|
}
|
||||||
|
dstReg, ok := ops[1].(Reg)
|
||||||
|
if !ok || dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s destination must be a general register", mnem)
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{0x0F, op})
|
||||||
|
if err := setRM(i, dstReg, ops[0], size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// amd64SystemCorpus holds every line the Go toolchain's own
|
||||||
|
// amd64enc.s carries for the system, flag, string and segment families, with the bytes go tool asm
|
||||||
|
// emits for each: the differential ground truth the family is proven
|
||||||
|
// against, line for line.
|
||||||
|
var amd64SystemCorpus = []struct {
|
||||||
|
line string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"CBW", "66 98"},
|
||||||
|
{"CDQ", "99"},
|
||||||
|
{"CDQE", "48 98"},
|
||||||
|
{"CLAC", "0f 01 ca"},
|
||||||
|
{"CLC", "f8"},
|
||||||
|
{"CLFLUSH (BX)", "0f ae 3b"},
|
||||||
|
{"CLFLUSH (R11)", "41 0f ae 3b"},
|
||||||
|
{"CLFLUSHOPT (BX)", "66 0f ae 3b"},
|
||||||
|
{"CLFLUSHOPT (R11)", "66 41 0f ae 3b"},
|
||||||
|
{"CLI", "fa"},
|
||||||
|
{"CLTS", "0f 06"},
|
||||||
|
{"CMC", "f5"},
|
||||||
|
{"CMPSB", "a6"},
|
||||||
|
{"CMPSL", "a7"},
|
||||||
|
{"CMPSQ", "48 a7"},
|
||||||
|
{"CMPSW", "66 a7"},
|
||||||
|
{"CMPXCHG16B (BX)", "48 0f c7 0b"},
|
||||||
|
{"CMPXCHG16B (R11)", "49 0f c7 0b"},
|
||||||
|
{"CMPXCHG8B (BX)", "0f c7 0b"},
|
||||||
|
{"CMPXCHG8B (R11)", "41 0f c7 0b"},
|
||||||
|
{"CQO", "48 99"},
|
||||||
|
{"CWD", "66 99"},
|
||||||
|
{"CWDE", "98"},
|
||||||
|
{"HLT", "f4"},
|
||||||
|
{"ICEBP", "f1"},
|
||||||
|
{"INSB", "6c"},
|
||||||
|
{"INSL", "6d"},
|
||||||
|
{"INSW", "66 6d"},
|
||||||
|
{"INVD", "0f 08"},
|
||||||
|
{"INVLPG (BX)", "0f 01 3b"},
|
||||||
|
{"INVLPG (R11)", "41 0f 01 3b"},
|
||||||
|
{"IRETW", "66 cf"},
|
||||||
|
{"IRETL", "cf"},
|
||||||
|
{"IRETQ", "48 cf"},
|
||||||
|
{"LAHF", "9f"},
|
||||||
|
{"LARW (BX), DX", "66 0f 02 13"},
|
||||||
|
{"LARW (R11), DX", "66 41 0f 02 13"},
|
||||||
|
{"LARW DX, DX", "66 0f 02 d2"},
|
||||||
|
{"LARW R11, DX", "66 41 0f 02 d3"},
|
||||||
|
{"LARW (BX), R11", "66 44 0f 02 1b"},
|
||||||
|
{"LARW (R11), R11", "66 45 0f 02 1b"},
|
||||||
|
{"LARW DX, R11", "66 44 0f 02 da"},
|
||||||
|
{"LARW R11, R11", "66 45 0f 02 db"},
|
||||||
|
{"LARL (BX), DX", "0f 02 13"},
|
||||||
|
{"LARL (R11), DX", "41 0f 02 13"},
|
||||||
|
{"LARL DX, DX", "0f 02 d2"},
|
||||||
|
{"LARL R11, DX", "41 0f 02 d3"},
|
||||||
|
{"LARL (BX), R11", "44 0f 02 1b"},
|
||||||
|
{"LARL (R11), R11", "45 0f 02 1b"},
|
||||||
|
{"LARL DX, R11", "44 0f 02 da"},
|
||||||
|
{"LARL R11, R11", "45 0f 02 db"},
|
||||||
|
{"LARQ (BX), DX", "48 0f 02 13"},
|
||||||
|
{"LARQ (R11), DX", "49 0f 02 13"},
|
||||||
|
{"LARQ DX, DX", "48 0f 02 d2"},
|
||||||
|
{"LARQ R11, DX", "49 0f 02 d3"},
|
||||||
|
{"LARQ (BX), R11", "4c 0f 02 1b"},
|
||||||
|
{"LARQ (R11), R11", "4d 0f 02 1b"},
|
||||||
|
{"LARQ DX, R11", "4c 0f 02 da"},
|
||||||
|
{"LARQ R11, R11", "4d 0f 02 db"},
|
||||||
|
{"LFSW (BX), DX", "66 0f b4 13"},
|
||||||
|
{"LFSW (R11), DX", "66 41 0f b4 13"},
|
||||||
|
{"LFSW (BX), R11", "66 44 0f b4 1b"},
|
||||||
|
{"LFSW (R11), R11", "66 45 0f b4 1b"},
|
||||||
|
{"LFSL (BX), DX", "0f b4 13"},
|
||||||
|
{"LFSL (R11), DX", "41 0f b4 13"},
|
||||||
|
{"LFSL (BX), R11", "44 0f b4 1b"},
|
||||||
|
{"LFSL (R11), R11", "45 0f b4 1b"},
|
||||||
|
{"LFSQ (BX), DX", "48 0f b4 13"},
|
||||||
|
{"LFSQ (R11), DX", "49 0f b4 13"},
|
||||||
|
{"LFSQ (BX), R11", "4c 0f b4 1b"},
|
||||||
|
{"LFSQ (R11), R11", "4d 0f b4 1b"},
|
||||||
|
{"LGDT (BX)", "0f 01 13"},
|
||||||
|
{"LGDT (R11)", "41 0f 01 13"},
|
||||||
|
{"LGSW (BX), DX", "66 0f b5 13"},
|
||||||
|
{"LGSW (R11), DX", "66 41 0f b5 13"},
|
||||||
|
{"LGSW (BX), R11", "66 44 0f b5 1b"},
|
||||||
|
{"LGSW (R11), R11", "66 45 0f b5 1b"},
|
||||||
|
{"LGSL (BX), DX", "0f b5 13"},
|
||||||
|
{"LGSL (R11), DX", "41 0f b5 13"},
|
||||||
|
{"LGSL (BX), R11", "44 0f b5 1b"},
|
||||||
|
{"LGSL (R11), R11", "45 0f b5 1b"},
|
||||||
|
{"LGSQ (BX), DX", "48 0f b5 13"},
|
||||||
|
{"LGSQ (R11), DX", "49 0f b5 13"},
|
||||||
|
{"LGSQ (BX), R11", "4c 0f b5 1b"},
|
||||||
|
{"LGSQ (R11), R11", "4d 0f b5 1b"},
|
||||||
|
{"LIDT (BX)", "0f 01 1b"},
|
||||||
|
{"LIDT (R11)", "41 0f 01 1b"},
|
||||||
|
{"LLDT (BX)", "0f 00 13"},
|
||||||
|
{"LLDT (R11)", "41 0f 00 13"},
|
||||||
|
{"LLDT DX", "0f 00 d2"},
|
||||||
|
{"LLDT R11", "41 0f 00 d3"},
|
||||||
|
{"LMSW (BX)", "0f 01 33"},
|
||||||
|
{"LMSW (R11)", "41 0f 01 33"},
|
||||||
|
{"LMSW DX", "0f 01 f2"},
|
||||||
|
{"LMSW R11", "41 0f 01 f3"},
|
||||||
|
{"LODSB", "ac"},
|
||||||
|
{"LODSL", "ad"},
|
||||||
|
{"LODSQ", "48 ad"},
|
||||||
|
{"LODSW", "66 ad"},
|
||||||
|
{"LSLW (BX), DX", "66 0f 03 13"},
|
||||||
|
{"LSLW (R11), DX", "66 41 0f 03 13"},
|
||||||
|
{"LSLW DX, DX", "66 0f 03 d2"},
|
||||||
|
{"LSLW R11, DX", "66 41 0f 03 d3"},
|
||||||
|
{"LSLW (BX), R11", "66 44 0f 03 1b"},
|
||||||
|
{"LSLW (R11), R11", "66 45 0f 03 1b"},
|
||||||
|
{"LSLW DX, R11", "66 44 0f 03 da"},
|
||||||
|
{"LSLW R11, R11", "66 45 0f 03 db"},
|
||||||
|
{"LSLL (BX), DX", "0f 03 13"},
|
||||||
|
{"LSLL (R11), DX", "41 0f 03 13"},
|
||||||
|
{"LSLL DX, DX", "0f 03 d2"},
|
||||||
|
{"LSLL R11, DX", "41 0f 03 d3"},
|
||||||
|
{"LSLL (BX), R11", "44 0f 03 1b"},
|
||||||
|
{"LSLL (R11), R11", "45 0f 03 1b"},
|
||||||
|
{"LSLL DX, R11", "44 0f 03 da"},
|
||||||
|
{"LSLL R11, R11", "45 0f 03 db"},
|
||||||
|
{"LSLQ (BX), DX", "48 0f 03 13"},
|
||||||
|
{"LSLQ (R11), DX", "49 0f 03 13"},
|
||||||
|
{"LSLQ DX, DX", "48 0f 03 d2"},
|
||||||
|
{"LSLQ R11, DX", "49 0f 03 d3"},
|
||||||
|
{"LSLQ (BX), R11", "4c 0f 03 1b"},
|
||||||
|
{"LSLQ (R11), R11", "4d 0f 03 1b"},
|
||||||
|
{"LSLQ DX, R11", "4c 0f 03 da"},
|
||||||
|
{"LSLQ R11, R11", "4d 0f 03 db"},
|
||||||
|
{"LSSW (BX), DX", "66 0f b2 13"},
|
||||||
|
{"LSSW (R11), DX", "66 41 0f b2 13"},
|
||||||
|
{"LSSW (BX), R11", "66 44 0f b2 1b"},
|
||||||
|
{"LSSW (R11), R11", "66 45 0f b2 1b"},
|
||||||
|
{"LSSL (BX), DX", "0f b2 13"},
|
||||||
|
{"LSSL (R11), DX", "41 0f b2 13"},
|
||||||
|
{"LSSL (BX), R11", "44 0f b2 1b"},
|
||||||
|
{"LSSL (R11), R11", "45 0f b2 1b"},
|
||||||
|
{"LSSQ (BX), DX", "48 0f b2 13"},
|
||||||
|
{"LSSQ (R11), DX", "49 0f b2 13"},
|
||||||
|
{"LSSQ (BX), R11", "4c 0f b2 1b"},
|
||||||
|
{"LSSQ (R11), R11", "4d 0f b2 1b"},
|
||||||
|
{"LTR (BX)", "0f 00 1b"},
|
||||||
|
{"LTR (R11)", "41 0f 00 1b"},
|
||||||
|
{"LTR DX", "0f 00 da"},
|
||||||
|
{"LTR R11", "41 0f 00 db"},
|
||||||
|
{"MONITOR", "0f 01 c8"},
|
||||||
|
{"MOVBEW DX, (BX)", "66 0f 38 f1 13"},
|
||||||
|
{"MOVBEW R11, (BX)", "66 44 0f 38 f1 1b"},
|
||||||
|
{"MOVBEW DX, (R11)", "66 41 0f 38 f1 13"},
|
||||||
|
{"MOVBEW R11, (R11)", "66 45 0f 38 f1 1b"},
|
||||||
|
{"MOVBEW (BX), DX", "66 0f 38 f0 13"},
|
||||||
|
{"MOVBEW (R11), DX", "66 41 0f 38 f0 13"},
|
||||||
|
{"MOVBEW (BX), R11", "66 44 0f 38 f0 1b"},
|
||||||
|
{"MOVBEW (R11), R11", "66 45 0f 38 f0 1b"},
|
||||||
|
{"MOVBEL DX, (BX)", "0f 38 f1 13"},
|
||||||
|
{"MOVBEL R11, (BX)", "44 0f 38 f1 1b"},
|
||||||
|
{"MOVBEL DX, (R11)", "41 0f 38 f1 13"},
|
||||||
|
{"MOVBEL R11, (R11)", "45 0f 38 f1 1b"},
|
||||||
|
{"MOVBEL (BX), DX", "0f 38 f0 13"},
|
||||||
|
{"MOVBEL (R11), DX", "41 0f 38 f0 13"},
|
||||||
|
{"MOVBEL (BX), R11", "44 0f 38 f0 1b"},
|
||||||
|
{"MOVBEL (R11), R11", "45 0f 38 f0 1b"},
|
||||||
|
{"MOVBEQ DX, (BX)", "48 0f 38 f1 13"},
|
||||||
|
{"MOVBEQ R11, (BX)", "4c 0f 38 f1 1b"},
|
||||||
|
{"MOVBEQ DX, (R11)", "49 0f 38 f1 13"},
|
||||||
|
{"MOVBEQ R11, (R11)", "4d 0f 38 f1 1b"},
|
||||||
|
{"MOVBEQ (BX), DX", "48 0f 38 f0 13"},
|
||||||
|
{"MOVBEQ (R11), DX", "49 0f 38 f0 13"},
|
||||||
|
{"MOVBEQ (BX), R11", "4c 0f 38 f0 1b"},
|
||||||
|
{"MOVBEQ (R11), R11", "4d 0f 38 f0 1b"},
|
||||||
|
{"MWAIT", "0f 01 c9"},
|
||||||
|
{"NOPW (BX)", "66 0f 1f 03"},
|
||||||
|
{"NOPW (R11)", "66 41 0f 1f 03"},
|
||||||
|
{"NOPW DX", "66 0f 1f c2"},
|
||||||
|
{"NOPW R11", "66 41 0f 1f c3"},
|
||||||
|
{"NOPL (BX)", "0f 1f 03"},
|
||||||
|
{"NOPL (R11)", "41 0f 1f 03"},
|
||||||
|
{"NOPL DX", "0f 1f c2"},
|
||||||
|
{"NOPL R11", "41 0f 1f c3"},
|
||||||
|
{"OUTSB", "6e"},
|
||||||
|
{"OUTSL", "6f"},
|
||||||
|
{"OUTSW", "66 6f"},
|
||||||
|
{"POPFW", "66 9d"},
|
||||||
|
{"PUSHFW", "66 9c"},
|
||||||
|
{"RDFSBASEL DX", "f3 0f ae c2"},
|
||||||
|
{"RDFSBASEL R11", "f3 41 0f ae c3"},
|
||||||
|
{"RDGSBASEL DX", "f3 0f ae ca"},
|
||||||
|
{"RDGSBASEL R11", "f3 41 0f ae cb"},
|
||||||
|
{"RDFSBASEQ DX", "f3 48 0f ae c2"},
|
||||||
|
{"RDFSBASEQ R11", "f3 49 0f ae c3"},
|
||||||
|
{"RDGSBASEQ DX", "f3 48 0f ae ca"},
|
||||||
|
{"RDGSBASEQ R11", "f3 49 0f ae cb"},
|
||||||
|
{"RDMSR", "0f 32"},
|
||||||
|
{"RDPKRU", "0f 01 ee"},
|
||||||
|
{"RDPMC", "0f 33"},
|
||||||
|
{"RDRANDW DX", "66 0f c7 f2"},
|
||||||
|
{"RDRANDW R11", "66 41 0f c7 f3"},
|
||||||
|
{"RDRANDL DX", "0f c7 f2"},
|
||||||
|
{"RDRANDL R11", "41 0f c7 f3"},
|
||||||
|
{"RDRANDQ DX", "48 0f c7 f2"},
|
||||||
|
{"RDRANDQ R11", "49 0f c7 f3"},
|
||||||
|
{"RDSEEDW DX", "66 0f c7 fa"},
|
||||||
|
{"RDSEEDW R11", "66 41 0f c7 fb"},
|
||||||
|
{"RDSEEDL DX", "0f c7 fa"},
|
||||||
|
{"RDSEEDL R11", "41 0f c7 fb"},
|
||||||
|
{"RDSEEDQ DX", "48 0f c7 fa"},
|
||||||
|
{"RDSEEDQ R11", "49 0f c7 fb"},
|
||||||
|
{"RSM", "0f aa"},
|
||||||
|
{"SAHF", "9e"},
|
||||||
|
{"SCASB", "ae"},
|
||||||
|
{"SCASL", "af"},
|
||||||
|
{"SCASQ", "48 af"},
|
||||||
|
{"SCASW", "66 af"},
|
||||||
|
{"SGDT (BX)", "0f 01 03"},
|
||||||
|
{"SGDT (R11)", "41 0f 01 03"},
|
||||||
|
{"SIDT (BX)", "0f 01 0b"},
|
||||||
|
{"SIDT (R11)", "41 0f 01 0b"},
|
||||||
|
{"SLDTW (BX)", "66 0f 00 03"},
|
||||||
|
{"SLDTW (R11)", "66 41 0f 00 03"},
|
||||||
|
{"SLDTW DX", "66 0f 00 c2"},
|
||||||
|
{"SLDTW R11", "66 41 0f 00 c3"},
|
||||||
|
{"SLDTL (BX)", "0f 00 03"},
|
||||||
|
{"SLDTL (R11)", "41 0f 00 03"},
|
||||||
|
{"SLDTL DX", "0f 00 c2"},
|
||||||
|
{"SLDTL R11", "41 0f 00 c3"},
|
||||||
|
{"SLDTQ (BX)", "48 0f 00 03"},
|
||||||
|
{"SLDTQ (R11)", "49 0f 00 03"},
|
||||||
|
{"SLDTQ DX", "48 0f 00 c2"},
|
||||||
|
{"SLDTQ R11", "49 0f 00 c3"},
|
||||||
|
{"SMSWW (BX)", "66 0f 01 23"},
|
||||||
|
{"SMSWW (R11)", "66 41 0f 01 23"},
|
||||||
|
{"SMSWW DX", "66 0f 01 e2"},
|
||||||
|
{"SMSWW R11", "66 41 0f 01 e3"},
|
||||||
|
{"SMSWL (BX)", "0f 01 23"},
|
||||||
|
{"SMSWL (R11)", "41 0f 01 23"},
|
||||||
|
{"SMSWL DX", "0f 01 e2"},
|
||||||
|
{"SMSWL R11", "41 0f 01 e3"},
|
||||||
|
{"SMSWQ (BX)", "48 0f 01 23"},
|
||||||
|
{"SMSWQ (R11)", "49 0f 01 23"},
|
||||||
|
{"SMSWQ DX", "48 0f 01 e2"},
|
||||||
|
{"SMSWQ R11", "49 0f 01 e3"},
|
||||||
|
{"STAC", "0f 01 cb"},
|
||||||
|
{"STC", "f9"},
|
||||||
|
{"STI", "fb"},
|
||||||
|
{"STRW (BX)", "66 0f 00 0b"},
|
||||||
|
{"STRW (R11)", "66 41 0f 00 0b"},
|
||||||
|
{"STRW DX", "66 0f 00 ca"},
|
||||||
|
{"STRW R11", "66 41 0f 00 cb"},
|
||||||
|
{"STRL (BX)", "0f 00 0b"},
|
||||||
|
{"STRL (R11)", "41 0f 00 0b"},
|
||||||
|
{"STRL DX", "0f 00 ca"},
|
||||||
|
{"STRL R11", "41 0f 00 cb"},
|
||||||
|
{"STRQ (BX)", "48 0f 00 0b"},
|
||||||
|
{"STRQ (R11)", "49 0f 00 0b"},
|
||||||
|
{"STRQ DX", "48 0f 00 ca"},
|
||||||
|
{"STRQ R11", "49 0f 00 cb"},
|
||||||
|
{"SWAPGS", "0f 01 f8"},
|
||||||
|
{"SYSENTER", "0f 34"},
|
||||||
|
{"SYSENTER64", "48 0f 34"},
|
||||||
|
{"SYSEXIT", "0f 35"},
|
||||||
|
{"SYSEXIT64", "48 0f 35"},
|
||||||
|
{"SYSRET", "0f 07"},
|
||||||
|
{"UD1", "0f b9"},
|
||||||
|
{"UD2", "0f 0b"},
|
||||||
|
{"VERR (BX)", "0f 00 23"},
|
||||||
|
{"VERR (R11)", "41 0f 00 23"},
|
||||||
|
{"VERR DX", "0f 00 e2"},
|
||||||
|
{"VERR R11", "41 0f 00 e3"},
|
||||||
|
{"VERW (BX)", "0f 00 2b"},
|
||||||
|
{"VERW (R11)", "41 0f 00 2b"},
|
||||||
|
{"VERW DX", "0f 00 ea"},
|
||||||
|
{"VERW R11", "41 0f 00 eb"},
|
||||||
|
{"WBINVD", "0f 09"},
|
||||||
|
{"WRFSBASEL DX", "f3 0f ae d2"},
|
||||||
|
{"WRFSBASEL R11", "f3 41 0f ae d3"},
|
||||||
|
{"WRGSBASEL DX", "f3 0f ae da"},
|
||||||
|
{"WRGSBASEL R11", "f3 41 0f ae db"},
|
||||||
|
{"WRFSBASEQ DX", "f3 48 0f ae d2"},
|
||||||
|
{"WRFSBASEQ R11", "f3 49 0f ae d3"},
|
||||||
|
{"WRGSBASEQ DX", "f3 48 0f ae da"},
|
||||||
|
{"WRGSBASEQ R11", "f3 49 0f ae db"},
|
||||||
|
{"WRMSR", "0f 30"},
|
||||||
|
{"WRPKRU", "0f 01 ef"},
|
||||||
|
{"XLAT", "d7"},
|
||||||
|
{"XSETBV", "0f 01 d1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64SystemCorpus assembles every corpus line and requires the same bytes
|
||||||
|
// go tool asm emits for it.
|
||||||
|
func TestAmd64SystemCorpus(t *testing.T) {
|
||||||
|
for _, tc := range amd64SystemCorpus {
|
||||||
|
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tc.line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexBytes(code); got != tc.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", tc.line, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,884 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// amd64VexCorpus holds every line the Go toolchain's own
|
||||||
|
// amd64enc.s carries for the VEX (AVX/AVX2) families and the general-register count forms, with the bytes go
|
||||||
|
// tool asm emits for each: the differential ground truth the layer is
|
||||||
|
// proven against, line for line.
|
||||||
|
var amd64VexCorpus = []struct {
|
||||||
|
line string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"BEXTRL R9, (BX), DX", "c4 e2 30 f7 13"},
|
||||||
|
{"BEXTRL R9, (BX), R11", "c4 62 30 f7 1b"},
|
||||||
|
{"BEXTRL R9, (R11), DX", "c4 c2 30 f7 13"},
|
||||||
|
{"BEXTRL R9, (R11), R11", "c4 42 30 f7 1b"},
|
||||||
|
{"BEXTRL R9, DX, DX", "c4 e2 30 f7 d2"},
|
||||||
|
{"BEXTRL R9, DX, R11", "c4 62 30 f7 da"},
|
||||||
|
{"BEXTRL R9, R11, DX", "c4 c2 30 f7 d3"},
|
||||||
|
{"BEXTRL R9, R11, R11", "c4 42 30 f7 db"},
|
||||||
|
{"BEXTRQ R14, (BX), DX", "c4 e2 88 f7 13"},
|
||||||
|
{"BEXTRQ R14, (BX), R11", "c4 62 88 f7 1b"},
|
||||||
|
{"BEXTRQ R14, (R11), DX", "c4 c2 88 f7 13"},
|
||||||
|
{"BEXTRQ R14, (R11), R11", "c4 42 88 f7 1b"},
|
||||||
|
{"BEXTRQ R14, DX, DX", "c4 e2 88 f7 d2"},
|
||||||
|
{"BEXTRQ R14, DX, R11", "c4 62 88 f7 da"},
|
||||||
|
{"BEXTRQ R14, R11, DX", "c4 c2 88 f7 d3"},
|
||||||
|
{"BEXTRQ R14, R11, R11", "c4 42 88 f7 db"},
|
||||||
|
{"BZHIL R9, (BX), DX", "c4 e2 30 f5 13"},
|
||||||
|
{"BZHIL R9, (BX), R11", "c4 62 30 f5 1b"},
|
||||||
|
{"BZHIL R9, (R11), DX", "c4 c2 30 f5 13"},
|
||||||
|
{"BZHIL R9, (R11), R11", "c4 42 30 f5 1b"},
|
||||||
|
{"BZHIL R9, DX, DX", "c4 e2 30 f5 d2"},
|
||||||
|
{"BZHIL R9, DX, R11", "c4 62 30 f5 da"},
|
||||||
|
{"BZHIL R9, R11, DX", "c4 c2 30 f5 d3"},
|
||||||
|
{"BZHIL R9, R11, R11", "c4 42 30 f5 db"},
|
||||||
|
{"BZHIQ R14, (BX), DX", "c4 e2 88 f5 13"},
|
||||||
|
{"BZHIQ R14, (BX), R11", "c4 62 88 f5 1b"},
|
||||||
|
{"BZHIQ R14, (R11), DX", "c4 c2 88 f5 13"},
|
||||||
|
{"BZHIQ R14, (R11), R11", "c4 42 88 f5 1b"},
|
||||||
|
{"BZHIQ R14, DX, DX", "c4 e2 88 f5 d2"},
|
||||||
|
{"BZHIQ R14, DX, R11", "c4 62 88 f5 da"},
|
||||||
|
{"BZHIQ R14, R11, DX", "c4 c2 88 f5 d3"},
|
||||||
|
{"BZHIQ R14, R11, R11", "c4 42 88 f5 db"},
|
||||||
|
{"SARXL R9, (BX), DX", "c4 e2 32 f7 13"},
|
||||||
|
{"SARXL R9, (BX), R11", "c4 62 32 f7 1b"},
|
||||||
|
{"SARXL R9, (R11), DX", "c4 c2 32 f7 13"},
|
||||||
|
{"SARXL R9, (R11), R11", "c4 42 32 f7 1b"},
|
||||||
|
{"SARXL R9, DX, DX", "c4 e2 32 f7 d2"},
|
||||||
|
{"SARXL R9, DX, R11", "c4 62 32 f7 da"},
|
||||||
|
{"SARXL R9, R11, DX", "c4 c2 32 f7 d3"},
|
||||||
|
{"SARXL R9, R11, R11", "c4 42 32 f7 db"},
|
||||||
|
{"SARXQ R14, (BX), DX", "c4 e2 8a f7 13"},
|
||||||
|
{"SARXQ R14, (BX), R11", "c4 62 8a f7 1b"},
|
||||||
|
{"SARXQ R14, (R11), DX", "c4 c2 8a f7 13"},
|
||||||
|
{"SARXQ R14, (R11), R11", "c4 42 8a f7 1b"},
|
||||||
|
{"SARXQ R14, DX, DX", "c4 e2 8a f7 d2"},
|
||||||
|
{"SARXQ R14, DX, R11", "c4 62 8a f7 da"},
|
||||||
|
{"SARXQ R14, R11, DX", "c4 c2 8a f7 d3"},
|
||||||
|
{"SARXQ R14, R11, R11", "c4 42 8a f7 db"},
|
||||||
|
{"SHLXL R9, (BX), DX", "c4 e2 31 f7 13"},
|
||||||
|
{"SHLXL R9, (BX), R11", "c4 62 31 f7 1b"},
|
||||||
|
{"SHLXL R9, (R11), DX", "c4 c2 31 f7 13"},
|
||||||
|
{"SHLXL R9, (R11), R11", "c4 42 31 f7 1b"},
|
||||||
|
{"SHLXL R9, DX, DX", "c4 e2 31 f7 d2"},
|
||||||
|
{"SHLXL R9, DX, R11", "c4 62 31 f7 da"},
|
||||||
|
{"SHLXL R9, R11, DX", "c4 c2 31 f7 d3"},
|
||||||
|
{"SHLXL R9, R11, R11", "c4 42 31 f7 db"},
|
||||||
|
{"SHLXQ R14, (BX), DX", "c4 e2 89 f7 13"},
|
||||||
|
{"SHLXQ R14, (BX), R11", "c4 62 89 f7 1b"},
|
||||||
|
{"SHLXQ R14, (R11), DX", "c4 c2 89 f7 13"},
|
||||||
|
{"SHLXQ R14, (R11), R11", "c4 42 89 f7 1b"},
|
||||||
|
{"SHLXQ R14, DX, DX", "c4 e2 89 f7 d2"},
|
||||||
|
{"SHLXQ R14, DX, R11", "c4 62 89 f7 da"},
|
||||||
|
{"SHLXQ R14, R11, DX", "c4 c2 89 f7 d3"},
|
||||||
|
{"SHLXQ R14, R11, R11", "c4 42 89 f7 db"},
|
||||||
|
{"SHRXL R9, (BX), DX", "c4 e2 33 f7 13"},
|
||||||
|
{"SHRXL R9, (BX), R11", "c4 62 33 f7 1b"},
|
||||||
|
{"SHRXL R9, (R11), DX", "c4 c2 33 f7 13"},
|
||||||
|
{"SHRXL R9, (R11), R11", "c4 42 33 f7 1b"},
|
||||||
|
{"SHRXL R9, DX, DX", "c4 e2 33 f7 d2"},
|
||||||
|
{"SHRXL R9, DX, R11", "c4 62 33 f7 da"},
|
||||||
|
{"SHRXL R9, R11, DX", "c4 c2 33 f7 d3"},
|
||||||
|
{"SHRXL R9, R11, R11", "c4 42 33 f7 db"},
|
||||||
|
{"SHRXQ R14, (BX), DX", "c4 e2 8b f7 13"},
|
||||||
|
{"SHRXQ R14, (BX), R11", "c4 62 8b f7 1b"},
|
||||||
|
{"SHRXQ R14, (R11), DX", "c4 c2 8b f7 13"},
|
||||||
|
{"SHRXQ R14, (R11), R11", "c4 42 8b f7 1b"},
|
||||||
|
{"SHRXQ R14, DX, DX", "c4 e2 8b f7 d2"},
|
||||||
|
{"SHRXQ R14, DX, R11", "c4 62 8b f7 da"},
|
||||||
|
{"SHRXQ R14, R11, DX", "c4 c2 8b f7 d3"},
|
||||||
|
{"SHRXQ R14, R11, R11", "c4 42 8b f7 db"},
|
||||||
|
{"VADDSUBPD (BX), X9, X11", "c5 31 d0 1b"},
|
||||||
|
{"VADDSUBPD (BX), X9, X2", "c5 b1 d0 13"},
|
||||||
|
{"VADDSUBPD (BX), Y15, Y11", "c5 05 d0 1b"},
|
||||||
|
{"VADDSUBPD (BX), Y15, Y2", "c5 85 d0 13"},
|
||||||
|
{"VADDSUBPD (R11), X9, X11", "c4 41 31 d0 1b"},
|
||||||
|
{"VADDSUBPD (R11), X9, X2", "c4 c1 31 d0 13"},
|
||||||
|
{"VADDSUBPD (R11), Y15, Y11", "c4 41 05 d0 1b"},
|
||||||
|
{"VADDSUBPD (R11), Y15, Y2", "c4 c1 05 d0 13"},
|
||||||
|
{"VADDSUBPD X11, X9, X11", "c4 41 31 d0 db"},
|
||||||
|
{"VADDSUBPD X11, X9, X2", "c4 c1 31 d0 d3"},
|
||||||
|
{"VADDSUBPD X2, X9, X11", "c5 31 d0 da"},
|
||||||
|
{"VADDSUBPD X2, X9, X2", "c5 b1 d0 d2"},
|
||||||
|
{"VADDSUBPD Y11, Y15, Y11", "c4 41 05 d0 db"},
|
||||||
|
{"VADDSUBPD Y11, Y15, Y2", "c4 c1 05 d0 d3"},
|
||||||
|
{"VADDSUBPD Y2, Y15, Y11", "c5 05 d0 da"},
|
||||||
|
{"VADDSUBPD Y2, Y15, Y2", "c5 85 d0 d2"},
|
||||||
|
{"VADDSUBPS (BX), X9, X11", "c5 33 d0 1b"},
|
||||||
|
{"VADDSUBPS (BX), X9, X2", "c5 b3 d0 13"},
|
||||||
|
{"VADDSUBPS (BX), Y15, Y11", "c5 07 d0 1b"},
|
||||||
|
{"VADDSUBPS (BX), Y15, Y2", "c5 87 d0 13"},
|
||||||
|
{"VADDSUBPS (R11), X9, X11", "c4 41 33 d0 1b"},
|
||||||
|
{"VADDSUBPS (R11), X9, X2", "c4 c1 33 d0 13"},
|
||||||
|
{"VADDSUBPS (R11), Y15, Y11", "c4 41 07 d0 1b"},
|
||||||
|
{"VADDSUBPS (R11), Y15, Y2", "c4 c1 07 d0 13"},
|
||||||
|
{"VADDSUBPS X11, X9, X11", "c4 41 33 d0 db"},
|
||||||
|
{"VADDSUBPS X11, X9, X2", "c4 c1 33 d0 d3"},
|
||||||
|
{"VADDSUBPS X2, X9, X11", "c5 33 d0 da"},
|
||||||
|
{"VADDSUBPS X2, X9, X2", "c5 b3 d0 d2"},
|
||||||
|
{"VADDSUBPS Y11, Y15, Y11", "c4 41 07 d0 db"},
|
||||||
|
{"VADDSUBPS Y11, Y15, Y2", "c4 c1 07 d0 d3"},
|
||||||
|
{"VADDSUBPS Y2, Y15, Y11", "c5 07 d0 da"},
|
||||||
|
{"VADDSUBPS Y2, Y15, Y2", "c5 87 d0 d2"},
|
||||||
|
{"VAESIMC (BX), X11", "c4 62 79 db 1b"},
|
||||||
|
{"VAESIMC (BX), X2", "c4 e2 79 db 13"},
|
||||||
|
{"VAESIMC (R11), X11", "c4 42 79 db 1b"},
|
||||||
|
{"VAESIMC (R11), X2", "c4 c2 79 db 13"},
|
||||||
|
{"VAESIMC X11, X11", "c4 42 79 db db"},
|
||||||
|
{"VAESIMC X11, X2", "c4 c2 79 db d3"},
|
||||||
|
{"VAESIMC X2, X11", "c4 62 79 db da"},
|
||||||
|
{"VAESIMC X2, X2", "c4 e2 79 db d2"},
|
||||||
|
{"VBLENDPD $7, (BX), X9, X11", "c4 63 31 0d 1b 07"},
|
||||||
|
{"VBLENDPD $7, (BX), X9, X2", "c4 e3 31 0d 13 07"},
|
||||||
|
{"VBLENDPD $7, (BX), Y15, Y11", "c4 63 05 0d 1b 07"},
|
||||||
|
{"VBLENDPD $7, (BX), Y15, Y2", "c4 e3 05 0d 13 07"},
|
||||||
|
{"VBLENDPD $7, (R11), X9, X11", "c4 43 31 0d 1b 07"},
|
||||||
|
{"VBLENDPD $7, (R11), X9, X2", "c4 c3 31 0d 13 07"},
|
||||||
|
{"VBLENDPD $7, (R11), Y15, Y11", "c4 43 05 0d 1b 07"},
|
||||||
|
{"VBLENDPD $7, (R11), Y15, Y2", "c4 c3 05 0d 13 07"},
|
||||||
|
{"VBLENDPD $7, X11, X9, X11", "c4 43 31 0d db 07"},
|
||||||
|
{"VBLENDPD $7, X11, X9, X2", "c4 c3 31 0d d3 07"},
|
||||||
|
{"VBLENDPD $7, X2, X9, X11", "c4 63 31 0d da 07"},
|
||||||
|
{"VBLENDPD $7, X2, X9, X2", "c4 e3 31 0d d2 07"},
|
||||||
|
{"VBLENDPD $7, Y11, Y15, Y11", "c4 43 05 0d db 07"},
|
||||||
|
{"VBLENDPD $7, Y11, Y15, Y2", "c4 c3 05 0d d3 07"},
|
||||||
|
{"VBLENDPD $7, Y2, Y15, Y11", "c4 63 05 0d da 07"},
|
||||||
|
{"VBLENDPD $7, Y2, Y15, Y2", "c4 e3 05 0d d2 07"},
|
||||||
|
{"VBLENDPS $7, (BX), X9, X11", "c4 63 31 0c 1b 07"},
|
||||||
|
{"VBLENDPS $7, (BX), X9, X2", "c4 e3 31 0c 13 07"},
|
||||||
|
{"VBLENDPS $7, (BX), Y15, Y11", "c4 63 05 0c 1b 07"},
|
||||||
|
{"VBLENDPS $7, (BX), Y15, Y2", "c4 e3 05 0c 13 07"},
|
||||||
|
{"VBLENDPS $7, (R11), X9, X11", "c4 43 31 0c 1b 07"},
|
||||||
|
{"VBLENDPS $7, (R11), X9, X2", "c4 c3 31 0c 13 07"},
|
||||||
|
{"VBLENDPS $7, (R11), Y15, Y11", "c4 43 05 0c 1b 07"},
|
||||||
|
{"VBLENDPS $7, (R11), Y15, Y2", "c4 c3 05 0c 13 07"},
|
||||||
|
{"VBLENDPS $7, X11, X9, X11", "c4 43 31 0c db 07"},
|
||||||
|
{"VBLENDPS $7, X11, X9, X2", "c4 c3 31 0c d3 07"},
|
||||||
|
{"VBLENDPS $7, X2, X9, X11", "c4 63 31 0c da 07"},
|
||||||
|
{"VBLENDPS $7, X2, X9, X2", "c4 e3 31 0c d2 07"},
|
||||||
|
{"VBLENDPS $7, Y11, Y15, Y11", "c4 43 05 0c db 07"},
|
||||||
|
{"VBLENDPS $7, Y11, Y15, Y2", "c4 c3 05 0c d3 07"},
|
||||||
|
{"VBLENDPS $7, Y2, Y15, Y11", "c4 63 05 0c da 07"},
|
||||||
|
{"VBLENDPS $7, Y2, Y15, Y2", "c4 e3 05 0c d2 07"},
|
||||||
|
{"VBLENDVPD X12, (BX), X9, X11", "c4 63 31 4b 1b c0"},
|
||||||
|
{"VBLENDVPD X12, (BX), X9, X2", "c4 e3 31 4b 13 c0"},
|
||||||
|
{"VBLENDVPD X12, (R11), X9, X11", "c4 43 31 4b 1b c0"},
|
||||||
|
{"VBLENDVPD X12, (R11), X9, X2", "c4 c3 31 4b 13 c0"},
|
||||||
|
{"VBLENDVPD X12, X11, X9, X11", "c4 43 31 4b db c0"},
|
||||||
|
{"VBLENDVPD X12, X11, X9, X2", "c4 c3 31 4b d3 c0"},
|
||||||
|
{"VBLENDVPD X12, X2, X9, X11", "c4 63 31 4b da c0"},
|
||||||
|
{"VBLENDVPD X12, X2, X9, X2", "c4 e3 31 4b d2 c0"},
|
||||||
|
{"VBLENDVPD Y13, (BX), Y15, Y11", "c4 63 05 4b 1b d0"},
|
||||||
|
{"VBLENDVPD Y13, (BX), Y15, Y2", "c4 e3 05 4b 13 d0"},
|
||||||
|
{"VBLENDVPD Y13, (R11), Y15, Y11", "c4 43 05 4b 1b d0"},
|
||||||
|
{"VBLENDVPD Y13, (R11), Y15, Y2", "c4 c3 05 4b 13 d0"},
|
||||||
|
{"VBLENDVPD Y13, Y11, Y15, Y11", "c4 43 05 4b db d0"},
|
||||||
|
{"VBLENDVPD Y13, Y11, Y15, Y2", "c4 c3 05 4b d3 d0"},
|
||||||
|
{"VBLENDVPD Y13, Y2, Y15, Y11", "c4 63 05 4b da d0"},
|
||||||
|
{"VBLENDVPD Y13, Y2, Y15, Y2", "c4 e3 05 4b d2 d0"},
|
||||||
|
{"VBLENDVPS X12, (BX), X9, X11", "c4 63 31 4a 1b c0"},
|
||||||
|
{"VBLENDVPS X12, (BX), X9, X2", "c4 e3 31 4a 13 c0"},
|
||||||
|
{"VBLENDVPS X12, (R11), X9, X11", "c4 43 31 4a 1b c0"},
|
||||||
|
{"VBLENDVPS X12, (R11), X9, X2", "c4 c3 31 4a 13 c0"},
|
||||||
|
{"VBLENDVPS X12, X11, X9, X11", "c4 43 31 4a db c0"},
|
||||||
|
{"VBLENDVPS X12, X11, X9, X2", "c4 c3 31 4a d3 c0"},
|
||||||
|
{"VBLENDVPS X12, X2, X9, X11", "c4 63 31 4a da c0"},
|
||||||
|
{"VBLENDVPS X12, X2, X9, X2", "c4 e3 31 4a d2 c0"},
|
||||||
|
{"VBLENDVPS Y13, (BX), Y15, Y11", "c4 63 05 4a 1b d0"},
|
||||||
|
{"VBLENDVPS Y13, (BX), Y15, Y2", "c4 e3 05 4a 13 d0"},
|
||||||
|
{"VBLENDVPS Y13, (R11), Y15, Y11", "c4 43 05 4a 1b d0"},
|
||||||
|
{"VBLENDVPS Y13, (R11), Y15, Y2", "c4 c3 05 4a 13 d0"},
|
||||||
|
{"VBLENDVPS Y13, Y11, Y15, Y11", "c4 43 05 4a db d0"},
|
||||||
|
{"VBLENDVPS Y13, Y11, Y15, Y2", "c4 c3 05 4a d3 d0"},
|
||||||
|
{"VBLENDVPS Y13, Y2, Y15, Y11", "c4 63 05 4a da d0"},
|
||||||
|
{"VBLENDVPS Y13, Y2, Y15, Y2", "c4 e3 05 4a d2 d0"},
|
||||||
|
{"VBROADCASTF128 (BX), Y11", "c4 62 7d 1a 1b"},
|
||||||
|
{"VBROADCASTF128 (BX), Y2", "c4 e2 7d 1a 13"},
|
||||||
|
{"VBROADCASTF128 (R11), Y11", "c4 42 7d 1a 1b"},
|
||||||
|
{"VBROADCASTF128 (R11), Y2", "c4 c2 7d 1a 13"},
|
||||||
|
{"VDPPD $7, (BX), X9, X11", "c4 63 31 41 1b 07"},
|
||||||
|
{"VDPPD $7, (BX), X9, X2", "c4 e3 31 41 13 07"},
|
||||||
|
{"VDPPD $7, (R11), X9, X11", "c4 43 31 41 1b 07"},
|
||||||
|
{"VDPPD $7, (R11), X9, X2", "c4 c3 31 41 13 07"},
|
||||||
|
{"VDPPD $7, X11, X9, X11", "c4 43 31 41 db 07"},
|
||||||
|
{"VDPPD $7, X11, X9, X2", "c4 c3 31 41 d3 07"},
|
||||||
|
{"VDPPD $7, X2, X9, X11", "c4 63 31 41 da 07"},
|
||||||
|
{"VDPPD $7, X2, X9, X2", "c4 e3 31 41 d2 07"},
|
||||||
|
{"VDPPS $7, (BX), X9, X11", "c4 63 31 40 1b 07"},
|
||||||
|
{"VDPPS $7, (BX), X9, X2", "c4 e3 31 40 13 07"},
|
||||||
|
{"VDPPS $7, (BX), Y15, Y11", "c4 63 05 40 1b 07"},
|
||||||
|
{"VDPPS $7, (BX), Y15, Y2", "c4 e3 05 40 13 07"},
|
||||||
|
{"VDPPS $7, (R11), X9, X11", "c4 43 31 40 1b 07"},
|
||||||
|
{"VDPPS $7, (R11), X9, X2", "c4 c3 31 40 13 07"},
|
||||||
|
{"VDPPS $7, (R11), Y15, Y11", "c4 43 05 40 1b 07"},
|
||||||
|
{"VDPPS $7, (R11), Y15, Y2", "c4 c3 05 40 13 07"},
|
||||||
|
{"VDPPS $7, X11, X9, X11", "c4 43 31 40 db 07"},
|
||||||
|
{"VDPPS $7, X11, X9, X2", "c4 c3 31 40 d3 07"},
|
||||||
|
{"VDPPS $7, X2, X9, X11", "c4 63 31 40 da 07"},
|
||||||
|
{"VDPPS $7, X2, X9, X2", "c4 e3 31 40 d2 07"},
|
||||||
|
{"VDPPS $7, Y11, Y15, Y11", "c4 43 05 40 db 07"},
|
||||||
|
{"VDPPS $7, Y11, Y15, Y2", "c4 c3 05 40 d3 07"},
|
||||||
|
{"VDPPS $7, Y2, Y15, Y11", "c4 63 05 40 da 07"},
|
||||||
|
{"VDPPS $7, Y2, Y15, Y2", "c4 e3 05 40 d2 07"},
|
||||||
|
{"VHADDPD (BX), X9, X11", "c5 31 7c 1b"},
|
||||||
|
{"VHADDPD (BX), X9, X2", "c5 b1 7c 13"},
|
||||||
|
{"VHADDPD (BX), Y15, Y11", "c5 05 7c 1b"},
|
||||||
|
{"VHADDPD (BX), Y15, Y2", "c5 85 7c 13"},
|
||||||
|
{"VHADDPD (R11), X9, X11", "c4 41 31 7c 1b"},
|
||||||
|
{"VHADDPD (R11), X9, X2", "c4 c1 31 7c 13"},
|
||||||
|
{"VHADDPD (R11), Y15, Y11", "c4 41 05 7c 1b"},
|
||||||
|
{"VHADDPD (R11), Y15, Y2", "c4 c1 05 7c 13"},
|
||||||
|
{"VHADDPD X11, X9, X11", "c4 41 31 7c db"},
|
||||||
|
{"VHADDPD X11, X9, X2", "c4 c1 31 7c d3"},
|
||||||
|
{"VHADDPD X2, X9, X11", "c5 31 7c da"},
|
||||||
|
{"VHADDPD X2, X9, X2", "c5 b1 7c d2"},
|
||||||
|
{"VHADDPD Y11, Y15, Y11", "c4 41 05 7c db"},
|
||||||
|
{"VHADDPD Y11, Y15, Y2", "c4 c1 05 7c d3"},
|
||||||
|
{"VHADDPD Y2, Y15, Y11", "c5 05 7c da"},
|
||||||
|
{"VHADDPD Y2, Y15, Y2", "c5 85 7c d2"},
|
||||||
|
{"VHADDPS (BX), X9, X11", "c5 33 7c 1b"},
|
||||||
|
{"VHADDPS (BX), X9, X2", "c5 b3 7c 13"},
|
||||||
|
{"VHADDPS (BX), Y15, Y11", "c5 07 7c 1b"},
|
||||||
|
{"VHADDPS (BX), Y15, Y2", "c5 87 7c 13"},
|
||||||
|
{"VHADDPS (R11), X9, X11", "c4 41 33 7c 1b"},
|
||||||
|
{"VHADDPS (R11), X9, X2", "c4 c1 33 7c 13"},
|
||||||
|
{"VHADDPS (R11), Y15, Y11", "c4 41 07 7c 1b"},
|
||||||
|
{"VHADDPS (R11), Y15, Y2", "c4 c1 07 7c 13"},
|
||||||
|
{"VHADDPS X11, X9, X11", "c4 41 33 7c db"},
|
||||||
|
{"VHADDPS X11, X9, X2", "c4 c1 33 7c d3"},
|
||||||
|
{"VHADDPS X2, X9, X11", "c5 33 7c da"},
|
||||||
|
{"VHADDPS X2, X9, X2", "c5 b3 7c d2"},
|
||||||
|
{"VHADDPS Y11, Y15, Y11", "c4 41 07 7c db"},
|
||||||
|
{"VHADDPS Y11, Y15, Y2", "c4 c1 07 7c d3"},
|
||||||
|
{"VHADDPS Y2, Y15, Y11", "c5 07 7c da"},
|
||||||
|
{"VHADDPS Y2, Y15, Y2", "c5 87 7c d2"},
|
||||||
|
{"VHSUBPD (BX), X9, X11", "c5 31 7d 1b"},
|
||||||
|
{"VHSUBPD (BX), X9, X2", "c5 b1 7d 13"},
|
||||||
|
{"VHSUBPD (BX), Y15, Y11", "c5 05 7d 1b"},
|
||||||
|
{"VHSUBPD (BX), Y15, Y2", "c5 85 7d 13"},
|
||||||
|
{"VHSUBPD (R11), X9, X11", "c4 41 31 7d 1b"},
|
||||||
|
{"VHSUBPD (R11), X9, X2", "c4 c1 31 7d 13"},
|
||||||
|
{"VHSUBPD (R11), Y15, Y11", "c4 41 05 7d 1b"},
|
||||||
|
{"VHSUBPD (R11), Y15, Y2", "c4 c1 05 7d 13"},
|
||||||
|
{"VHSUBPD X11, X9, X11", "c4 41 31 7d db"},
|
||||||
|
{"VHSUBPD X11, X9, X2", "c4 c1 31 7d d3"},
|
||||||
|
{"VHSUBPD X2, X9, X11", "c5 31 7d da"},
|
||||||
|
{"VHSUBPD X2, X9, X2", "c5 b1 7d d2"},
|
||||||
|
{"VHSUBPD Y11, Y15, Y11", "c4 41 05 7d db"},
|
||||||
|
{"VHSUBPD Y11, Y15, Y2", "c4 c1 05 7d d3"},
|
||||||
|
{"VHSUBPD Y2, Y15, Y11", "c5 05 7d da"},
|
||||||
|
{"VHSUBPD Y2, Y15, Y2", "c5 85 7d d2"},
|
||||||
|
{"VHSUBPS (BX), X9, X11", "c5 33 7d 1b"},
|
||||||
|
{"VHSUBPS (BX), X9, X2", "c5 b3 7d 13"},
|
||||||
|
{"VHSUBPS (BX), Y15, Y11", "c5 07 7d 1b"},
|
||||||
|
{"VHSUBPS (BX), Y15, Y2", "c5 87 7d 13"},
|
||||||
|
{"VHSUBPS (R11), X9, X11", "c4 41 33 7d 1b"},
|
||||||
|
{"VHSUBPS (R11), X9, X2", "c4 c1 33 7d 13"},
|
||||||
|
{"VHSUBPS (R11), Y15, Y11", "c4 41 07 7d 1b"},
|
||||||
|
{"VHSUBPS (R11), Y15, Y2", "c4 c1 07 7d 13"},
|
||||||
|
{"VHSUBPS X11, X9, X11", "c4 41 33 7d db"},
|
||||||
|
{"VHSUBPS X11, X9, X2", "c4 c1 33 7d d3"},
|
||||||
|
{"VHSUBPS X2, X9, X11", "c5 33 7d da"},
|
||||||
|
{"VHSUBPS X2, X9, X2", "c5 b3 7d d2"},
|
||||||
|
{"VHSUBPS Y11, Y15, Y11", "c4 41 07 7d db"},
|
||||||
|
{"VHSUBPS Y11, Y15, Y2", "c4 c1 07 7d d3"},
|
||||||
|
{"VHSUBPS Y2, Y15, Y11", "c5 07 7d da"},
|
||||||
|
{"VHSUBPS Y2, Y15, Y2", "c5 87 7d d2"},
|
||||||
|
{"VINSERTF128 $7, (BX), Y15, Y11", "c4 63 05 18 1b 07"},
|
||||||
|
{"VINSERTF128 $7, (BX), Y15, Y2", "c4 e3 05 18 13 07"},
|
||||||
|
{"VINSERTF128 $7, (R11), Y15, Y11", "c4 43 05 18 1b 07"},
|
||||||
|
{"VINSERTF128 $7, (R11), Y15, Y2", "c4 c3 05 18 13 07"},
|
||||||
|
{"VINSERTF128 $7, X11, Y15, Y11", "c4 43 05 18 db 07"},
|
||||||
|
{"VINSERTF128 $7, X11, Y15, Y2", "c4 c3 05 18 d3 07"},
|
||||||
|
{"VINSERTF128 $7, X2, Y15, Y11", "c4 63 05 18 da 07"},
|
||||||
|
{"VINSERTF128 $7, X2, Y15, Y2", "c4 e3 05 18 d2 07"},
|
||||||
|
{"VINSERTPS $7, (BX), X9, X11", "c4 63 31 21 1b 07"},
|
||||||
|
{"VINSERTPS $7, (BX), X9, X2", "c4 e3 31 21 13 07"},
|
||||||
|
{"VINSERTPS $7, (R11), X9, X11", "c4 43 31 21 1b 07"},
|
||||||
|
{"VINSERTPS $7, (R11), X9, X2", "c4 c3 31 21 13 07"},
|
||||||
|
{"VINSERTPS $7, X11, X9, X11", "c4 43 31 21 db 07"},
|
||||||
|
{"VINSERTPS $7, X11, X9, X2", "c4 c3 31 21 d3 07"},
|
||||||
|
{"VINSERTPS $7, X2, X9, X11", "c4 63 31 21 da 07"},
|
||||||
|
{"VINSERTPS $7, X2, X9, X2", "c4 e3 31 21 d2 07"},
|
||||||
|
{"VLDDQU (BX), X11", "c5 7b f0 1b"},
|
||||||
|
{"VLDDQU (BX), X2", "c5 fb f0 13"},
|
||||||
|
{"VLDDQU (BX), Y11", "c5 7f f0 1b"},
|
||||||
|
{"VLDDQU (BX), Y2", "c5 ff f0 13"},
|
||||||
|
{"VLDDQU (R11), X11", "c4 41 7b f0 1b"},
|
||||||
|
{"VLDDQU (R11), X2", "c4 c1 7b f0 13"},
|
||||||
|
{"VLDDQU (R11), Y11", "c4 41 7f f0 1b"},
|
||||||
|
{"VLDDQU (R11), Y2", "c4 c1 7f f0 13"},
|
||||||
|
{"VLDMXCSR (BX)", "c5 f8 ae 13"},
|
||||||
|
{"VLDMXCSR (R11)", "c4 c1 78 ae 13"},
|
||||||
|
{"VMASKMOVDQU X11, X11", "c4 41 79 f7 db"},
|
||||||
|
{"VMASKMOVDQU X11, X2", "c4 c1 79 f7 d3"},
|
||||||
|
{"VMASKMOVDQU X2, X11", "c5 79 f7 da"},
|
||||||
|
{"VMASKMOVDQU X2, X2", "c5 f9 f7 d2"},
|
||||||
|
{"VMASKMOVPD (BX), X9, X11", "c4 62 31 2d 1b"},
|
||||||
|
{"VMASKMOVPD (BX), X9, X2", "c4 e2 31 2d 13"},
|
||||||
|
{"VMASKMOVPD (BX), Y15, Y11", "c4 62 05 2d 1b"},
|
||||||
|
{"VMASKMOVPD (BX), Y15, Y2", "c4 e2 05 2d 13"},
|
||||||
|
{"VMASKMOVPD (R11), X9, X11", "c4 42 31 2d 1b"},
|
||||||
|
{"VMASKMOVPD (R11), X9, X2", "c4 c2 31 2d 13"},
|
||||||
|
{"VMASKMOVPD (R11), Y15, Y11", "c4 42 05 2d 1b"},
|
||||||
|
{"VMASKMOVPD (R11), Y15, Y2", "c4 c2 05 2d 13"},
|
||||||
|
{"VMASKMOVPD X11, X9, (BX)", "c4 62 31 2f 1b"},
|
||||||
|
{"VMASKMOVPD X11, X9, (R11)", "c4 42 31 2f 1b"},
|
||||||
|
{"VMASKMOVPD X2, X9, (BX)", "c4 e2 31 2f 13"},
|
||||||
|
{"VMASKMOVPD X2, X9, (R11)", "c4 c2 31 2f 13"},
|
||||||
|
{"VMASKMOVPD Y11, Y15, (BX)", "c4 62 05 2f 1b"},
|
||||||
|
{"VMASKMOVPD Y11, Y15, (R11)", "c4 42 05 2f 1b"},
|
||||||
|
{"VMASKMOVPD Y2, Y15, (BX)", "c4 e2 05 2f 13"},
|
||||||
|
{"VMASKMOVPD Y2, Y15, (R11)", "c4 c2 05 2f 13"},
|
||||||
|
{"VMASKMOVPS (BX), X9, X11", "c4 62 31 2c 1b"},
|
||||||
|
{"VMASKMOVPS (BX), X9, X2", "c4 e2 31 2c 13"},
|
||||||
|
{"VMASKMOVPS (BX), Y15, Y11", "c4 62 05 2c 1b"},
|
||||||
|
{"VMASKMOVPS (BX), Y15, Y2", "c4 e2 05 2c 13"},
|
||||||
|
{"VMASKMOVPS (R11), X9, X11", "c4 42 31 2c 1b"},
|
||||||
|
{"VMASKMOVPS (R11), X9, X2", "c4 c2 31 2c 13"},
|
||||||
|
{"VMASKMOVPS (R11), Y15, Y11", "c4 42 05 2c 1b"},
|
||||||
|
{"VMASKMOVPS (R11), Y15, Y2", "c4 c2 05 2c 13"},
|
||||||
|
{"VMASKMOVPS X11, X9, (BX)", "c4 62 31 2e 1b"},
|
||||||
|
{"VMASKMOVPS X11, X9, (R11)", "c4 42 31 2e 1b"},
|
||||||
|
{"VMASKMOVPS X2, X9, (BX)", "c4 e2 31 2e 13"},
|
||||||
|
{"VMASKMOVPS X2, X9, (R11)", "c4 c2 31 2e 13"},
|
||||||
|
{"VMASKMOVPS Y11, Y15, (BX)", "c4 62 05 2e 1b"},
|
||||||
|
{"VMASKMOVPS Y11, Y15, (R11)", "c4 42 05 2e 1b"},
|
||||||
|
{"VMASKMOVPS Y2, Y15, (BX)", "c4 e2 05 2e 13"},
|
||||||
|
{"VMASKMOVPS Y2, Y15, (R11)", "c4 c2 05 2e 13"},
|
||||||
|
{"VMOVHLPS X11, X9, X11", "c4 41 30 12 db"},
|
||||||
|
{"VMOVHLPS X11, X9, X2", "c4 c1 30 12 d3"},
|
||||||
|
{"VMOVHLPS X2, X9, X11", "c5 30 12 da"},
|
||||||
|
{"VMOVHLPS X2, X9, X2", "c5 b0 12 d2"},
|
||||||
|
{"VMOVLPS (BX), X9, X11", "c5 30 12 1b"},
|
||||||
|
{"VMOVLPS (BX), X9, X2", "c5 b0 12 13"},
|
||||||
|
{"VMOVLPS (R11), X9, X11", "c4 41 30 12 1b"},
|
||||||
|
{"VMOVLPS (R11), X9, X2", "c4 c1 30 12 13"},
|
||||||
|
{"VMOVLPS X11, (BX)", "c5 78 13 1b"},
|
||||||
|
{"VMOVLPS X11, (R11)", "c4 41 78 13 1b"},
|
||||||
|
{"VMOVLPS X2, (BX)", "c5 f8 13 13"},
|
||||||
|
{"VMOVLPS X2, (R11)", "c4 c1 78 13 13"},
|
||||||
|
{"VMOVMSKPD X11, DX", "c4 c1 79 50 d3"},
|
||||||
|
{"VMOVMSKPD X11, R11", "c4 41 79 50 db"},
|
||||||
|
{"VMOVMSKPD X2, DX", "c5 f9 50 d2"},
|
||||||
|
{"VMOVMSKPD X2, R11", "c5 79 50 da"},
|
||||||
|
{"VMOVMSKPD Y11, DX", "c4 c1 7d 50 d3"},
|
||||||
|
{"VMOVMSKPD Y11, R11", "c4 41 7d 50 db"},
|
||||||
|
{"VMOVMSKPD Y2, DX", "c5 fd 50 d2"},
|
||||||
|
{"VMOVMSKPD Y2, R11", "c5 7d 50 da"},
|
||||||
|
{"VMOVSD (BX), X11", "c5 7b 10 1b"},
|
||||||
|
{"VMOVSD (BX), X2", "c5 fb 10 13"},
|
||||||
|
{"VMOVSD (R11), X11", "c4 41 7b 10 1b"},
|
||||||
|
{"VMOVSD (R11), X2", "c4 c1 7b 10 13"},
|
||||||
|
{"VMOVSD X11, (BX)", "c5 7b 11 1b"},
|
||||||
|
{"VMOVSD X11, (R11)", "c4 41 7b 11 1b"},
|
||||||
|
{"VMOVSD X11, X9, X11", "c4 41 33 11 db"},
|
||||||
|
{"VMOVSD X11, X9, X2", "c5 33 11 da"},
|
||||||
|
{"VMOVSD X2, (BX)", "c5 fb 11 13"},
|
||||||
|
{"VMOVSD X2, (R11)", "c4 c1 7b 11 13"},
|
||||||
|
{"VMOVSD X2, X9, X11", "c4 c1 33 11 d3"},
|
||||||
|
{"VMOVSD X2, X9, X2", "c5 b3 11 d2"},
|
||||||
|
{"VMOVSS (BX), X11", "c5 7a 10 1b"},
|
||||||
|
{"VMOVSS (BX), X2", "c5 fa 10 13"},
|
||||||
|
{"VMOVSS (R11), X11", "c4 41 7a 10 1b"},
|
||||||
|
{"VMOVSS (R11), X2", "c4 c1 7a 10 13"},
|
||||||
|
{"VMOVSS X11, (BX)", "c5 7a 11 1b"},
|
||||||
|
{"VMOVSS X11, (R11)", "c4 41 7a 11 1b"},
|
||||||
|
{"VMOVSS X11, X9, X11", "c4 41 32 11 db"},
|
||||||
|
{"VMOVSS X11, X9, X2", "c5 32 11 da"},
|
||||||
|
{"VMOVSS X2, (BX)", "c5 fa 11 13"},
|
||||||
|
{"VMOVSS X2, (R11)", "c4 c1 7a 11 13"},
|
||||||
|
{"VMOVSS X2, X9, X11", "c4 c1 32 11 d3"},
|
||||||
|
{"VMOVSS X2, X9, X2", "c5 b2 11 d2"},
|
||||||
|
{"VMPSADBW $7, (BX), X9, X11", "c4 63 31 42 1b 07"},
|
||||||
|
{"VMPSADBW $7, (BX), X9, X2", "c4 e3 31 42 13 07"},
|
||||||
|
{"VMPSADBW $7, (BX), Y15, Y11", "c4 63 05 42 1b 07"},
|
||||||
|
{"VMPSADBW $7, (BX), Y15, Y2", "c4 e3 05 42 13 07"},
|
||||||
|
{"VMPSADBW $7, (R11), X9, X11", "c4 43 31 42 1b 07"},
|
||||||
|
{"VMPSADBW $7, (R11), X9, X2", "c4 c3 31 42 13 07"},
|
||||||
|
{"VMPSADBW $7, (R11), Y15, Y11", "c4 43 05 42 1b 07"},
|
||||||
|
{"VMPSADBW $7, (R11), Y15, Y2", "c4 c3 05 42 13 07"},
|
||||||
|
{"VMPSADBW $7, X11, X9, X11", "c4 43 31 42 db 07"},
|
||||||
|
{"VMPSADBW $7, X11, X9, X2", "c4 c3 31 42 d3 07"},
|
||||||
|
{"VMPSADBW $7, X2, X9, X11", "c4 63 31 42 da 07"},
|
||||||
|
{"VMPSADBW $7, X2, X9, X2", "c4 e3 31 42 d2 07"},
|
||||||
|
{"VMPSADBW $7, Y11, Y15, Y11", "c4 43 05 42 db 07"},
|
||||||
|
{"VMPSADBW $7, Y11, Y15, Y2", "c4 c3 05 42 d3 07"},
|
||||||
|
{"VMPSADBW $7, Y2, Y15, Y11", "c4 63 05 42 da 07"},
|
||||||
|
{"VMPSADBW $7, Y2, Y15, Y2", "c4 e3 05 42 d2 07"},
|
||||||
|
{"VPBLENDVB X12, (BX), X9, X11", "c4 63 31 4c 1b c0"},
|
||||||
|
{"VPBLENDVB X12, (BX), X9, X2", "c4 e3 31 4c 13 c0"},
|
||||||
|
{"VPBLENDVB X12, (R11), X9, X11", "c4 43 31 4c 1b c0"},
|
||||||
|
{"VPBLENDVB X12, (R11), X9, X2", "c4 c3 31 4c 13 c0"},
|
||||||
|
{"VPBLENDVB X12, X11, X9, X11", "c4 43 31 4c db c0"},
|
||||||
|
{"VPBLENDVB X12, X11, X9, X2", "c4 c3 31 4c d3 c0"},
|
||||||
|
{"VPBLENDVB X12, X2, X9, X11", "c4 63 31 4c da c0"},
|
||||||
|
{"VPBLENDVB X12, X2, X9, X2", "c4 e3 31 4c d2 c0"},
|
||||||
|
{"VPBLENDVB Y13, (BX), Y15, Y11", "c4 63 05 4c 1b d0"},
|
||||||
|
{"VPBLENDVB Y13, (BX), Y15, Y2", "c4 e3 05 4c 13 d0"},
|
||||||
|
{"VPBLENDVB Y13, (R11), Y15, Y11", "c4 43 05 4c 1b d0"},
|
||||||
|
{"VPBLENDVB Y13, (R11), Y15, Y2", "c4 c3 05 4c 13 d0"},
|
||||||
|
{"VPBLENDVB Y13, Y11, Y15, Y11", "c4 43 05 4c db d0"},
|
||||||
|
{"VPBLENDVB Y13, Y11, Y15, Y2", "c4 c3 05 4c d3 d0"},
|
||||||
|
{"VPBLENDVB Y13, Y2, Y15, Y11", "c4 63 05 4c da d0"},
|
||||||
|
{"VPBLENDVB Y13, Y2, Y15, Y2", "c4 e3 05 4c d2 d0"},
|
||||||
|
{"VPBLENDW $7, (BX), X9, X11", "c4 63 31 0e 1b 07"},
|
||||||
|
{"VPBLENDW $7, (BX), X9, X2", "c4 e3 31 0e 13 07"},
|
||||||
|
{"VPBLENDW $7, (BX), Y15, Y11", "c4 63 05 0e 1b 07"},
|
||||||
|
{"VPBLENDW $7, (BX), Y15, Y2", "c4 e3 05 0e 13 07"},
|
||||||
|
{"VPBLENDW $7, (R11), X9, X11", "c4 43 31 0e 1b 07"},
|
||||||
|
{"VPBLENDW $7, (R11), X9, X2", "c4 c3 31 0e 13 07"},
|
||||||
|
{"VPBLENDW $7, (R11), Y15, Y11", "c4 43 05 0e 1b 07"},
|
||||||
|
{"VPBLENDW $7, (R11), Y15, Y2", "c4 c3 05 0e 13 07"},
|
||||||
|
{"VPBLENDW $7, X11, X9, X11", "c4 43 31 0e db 07"},
|
||||||
|
{"VPBLENDW $7, X11, X9, X2", "c4 c3 31 0e d3 07"},
|
||||||
|
{"VPBLENDW $7, X2, X9, X11", "c4 63 31 0e da 07"},
|
||||||
|
{"VPBLENDW $7, X2, X9, X2", "c4 e3 31 0e d2 07"},
|
||||||
|
{"VPBLENDW $7, Y11, Y15, Y11", "c4 43 05 0e db 07"},
|
||||||
|
{"VPBLENDW $7, Y11, Y15, Y2", "c4 c3 05 0e d3 07"},
|
||||||
|
{"VPBLENDW $7, Y2, Y15, Y11", "c4 63 05 0e da 07"},
|
||||||
|
{"VPBLENDW $7, Y2, Y15, Y2", "c4 e3 05 0e d2 07"},
|
||||||
|
{"VPERMILPD $7, (BX), X11", "c4 63 79 05 1b 07"},
|
||||||
|
{"VPERMILPD $7, (BX), X2", "c4 e3 79 05 13 07"},
|
||||||
|
{"VPERMILPD $7, (BX), Y11", "c4 63 7d 05 1b 07"},
|
||||||
|
{"VPERMILPD $7, (BX), Y2", "c4 e3 7d 05 13 07"},
|
||||||
|
{"VPERMILPD $7, (R11), X11", "c4 43 79 05 1b 07"},
|
||||||
|
{"VPERMILPD $7, (R11), X2", "c4 c3 79 05 13 07"},
|
||||||
|
{"VPERMILPD $7, (R11), Y11", "c4 43 7d 05 1b 07"},
|
||||||
|
{"VPERMILPD $7, (R11), Y2", "c4 c3 7d 05 13 07"},
|
||||||
|
{"VPERMILPD $7, X11, X11", "c4 43 79 05 db 07"},
|
||||||
|
{"VPERMILPD $7, X11, X2", "c4 c3 79 05 d3 07"},
|
||||||
|
{"VPERMILPD $7, X2, X11", "c4 63 79 05 da 07"},
|
||||||
|
{"VPERMILPD $7, X2, X2", "c4 e3 79 05 d2 07"},
|
||||||
|
{"VPERMILPD $7, Y11, Y11", "c4 43 7d 05 db 07"},
|
||||||
|
{"VPERMILPD $7, Y11, Y2", "c4 c3 7d 05 d3 07"},
|
||||||
|
{"VPERMILPD $7, Y2, Y11", "c4 63 7d 05 da 07"},
|
||||||
|
{"VPERMILPD $7, Y2, Y2", "c4 e3 7d 05 d2 07"},
|
||||||
|
{"VPERMILPD (BX), X9, X11", "c4 62 31 0d 1b"},
|
||||||
|
{"VPERMILPD (BX), X9, X2", "c4 e2 31 0d 13"},
|
||||||
|
{"VPERMILPD (BX), Y15, Y11", "c4 62 05 0d 1b"},
|
||||||
|
{"VPERMILPD (BX), Y15, Y2", "c4 e2 05 0d 13"},
|
||||||
|
{"VPERMILPD (R11), X9, X11", "c4 42 31 0d 1b"},
|
||||||
|
{"VPERMILPD (R11), X9, X2", "c4 c2 31 0d 13"},
|
||||||
|
{"VPERMILPD (R11), Y15, Y11", "c4 42 05 0d 1b"},
|
||||||
|
{"VPERMILPD (R11), Y15, Y2", "c4 c2 05 0d 13"},
|
||||||
|
{"VPERMILPD X11, X9, X11", "c4 42 31 0d db"},
|
||||||
|
{"VPERMILPD X11, X9, X2", "c4 c2 31 0d d3"},
|
||||||
|
{"VPERMILPD X2, X9, X11", "c4 62 31 0d da"},
|
||||||
|
{"VPERMILPD X2, X9, X2", "c4 e2 31 0d d2"},
|
||||||
|
{"VPERMILPD Y11, Y15, Y11", "c4 42 05 0d db"},
|
||||||
|
{"VPERMILPD Y11, Y15, Y2", "c4 c2 05 0d d3"},
|
||||||
|
{"VPERMILPD Y2, Y15, Y11", "c4 62 05 0d da"},
|
||||||
|
{"VPERMILPD Y2, Y15, Y2", "c4 e2 05 0d d2"},
|
||||||
|
{"VPERMILPS $7, (BX), X11", "c4 63 79 04 1b 07"},
|
||||||
|
{"VPERMILPS $7, (BX), X2", "c4 e3 79 04 13 07"},
|
||||||
|
{"VPERMILPS $7, (BX), Y11", "c4 63 7d 04 1b 07"},
|
||||||
|
{"VPERMILPS $7, (BX), Y2", "c4 e3 7d 04 13 07"},
|
||||||
|
{"VPERMILPS $7, (R11), X11", "c4 43 79 04 1b 07"},
|
||||||
|
{"VPERMILPS $7, (R11), X2", "c4 c3 79 04 13 07"},
|
||||||
|
{"VPERMILPS $7, (R11), Y11", "c4 43 7d 04 1b 07"},
|
||||||
|
{"VPERMILPS $7, (R11), Y2", "c4 c3 7d 04 13 07"},
|
||||||
|
{"VPERMILPS $7, X11, X11", "c4 43 79 04 db 07"},
|
||||||
|
{"VPERMILPS $7, X11, X2", "c4 c3 79 04 d3 07"},
|
||||||
|
{"VPERMILPS $7, X2, X11", "c4 63 79 04 da 07"},
|
||||||
|
{"VPERMILPS $7, X2, X2", "c4 e3 79 04 d2 07"},
|
||||||
|
{"VPERMILPS $7, Y11, Y11", "c4 43 7d 04 db 07"},
|
||||||
|
{"VPERMILPS $7, Y11, Y2", "c4 c3 7d 04 d3 07"},
|
||||||
|
{"VPERMILPS $7, Y2, Y11", "c4 63 7d 04 da 07"},
|
||||||
|
{"VPERMILPS $7, Y2, Y2", "c4 e3 7d 04 d2 07"},
|
||||||
|
{"VPERMILPS (BX), X9, X11", "c4 62 31 0c 1b"},
|
||||||
|
{"VPERMILPS (BX), X9, X2", "c4 e2 31 0c 13"},
|
||||||
|
{"VPERMILPS (BX), Y15, Y11", "c4 62 05 0c 1b"},
|
||||||
|
{"VPERMILPS (BX), Y15, Y2", "c4 e2 05 0c 13"},
|
||||||
|
{"VPERMILPS (R11), X9, X11", "c4 42 31 0c 1b"},
|
||||||
|
{"VPERMILPS (R11), X9, X2", "c4 c2 31 0c 13"},
|
||||||
|
{"VPERMILPS (R11), Y15, Y11", "c4 42 05 0c 1b"},
|
||||||
|
{"VPERMILPS (R11), Y15, Y2", "c4 c2 05 0c 13"},
|
||||||
|
{"VPERMILPS X11, X9, X11", "c4 42 31 0c db"},
|
||||||
|
{"VPERMILPS X11, X9, X2", "c4 c2 31 0c d3"},
|
||||||
|
{"VPERMILPS X2, X9, X11", "c4 62 31 0c da"},
|
||||||
|
{"VPERMILPS X2, X9, X2", "c4 e2 31 0c d2"},
|
||||||
|
{"VPERMILPS Y11, Y15, Y11", "c4 42 05 0c db"},
|
||||||
|
{"VPERMILPS Y11, Y15, Y2", "c4 c2 05 0c d3"},
|
||||||
|
{"VPERMILPS Y2, Y15, Y11", "c4 62 05 0c da"},
|
||||||
|
{"VPERMILPS Y2, Y15, Y2", "c4 e2 05 0c d2"},
|
||||||
|
{"VPHADDD (BX), X9, X11", "c4 62 31 02 1b"},
|
||||||
|
{"VPHADDD (BX), X9, X2", "c4 e2 31 02 13"},
|
||||||
|
{"VPHADDD (BX), Y15, Y11", "c4 62 05 02 1b"},
|
||||||
|
{"VPHADDD (BX), Y15, Y2", "c4 e2 05 02 13"},
|
||||||
|
{"VPHADDD (R11), X9, X11", "c4 42 31 02 1b"},
|
||||||
|
{"VPHADDD (R11), X9, X2", "c4 c2 31 02 13"},
|
||||||
|
{"VPHADDD (R11), Y15, Y11", "c4 42 05 02 1b"},
|
||||||
|
{"VPHADDD (R11), Y15, Y2", "c4 c2 05 02 13"},
|
||||||
|
{"VPHADDD X11, X9, X11", "c4 42 31 02 db"},
|
||||||
|
{"VPHADDD X11, X9, X2", "c4 c2 31 02 d3"},
|
||||||
|
{"VPHADDD X2, X9, X11", "c4 62 31 02 da"},
|
||||||
|
{"VPHADDD X2, X9, X2", "c4 e2 31 02 d2"},
|
||||||
|
{"VPHADDD Y11, Y15, Y11", "c4 42 05 02 db"},
|
||||||
|
{"VPHADDD Y11, Y15, Y2", "c4 c2 05 02 d3"},
|
||||||
|
{"VPHADDD Y2, Y15, Y11", "c4 62 05 02 da"},
|
||||||
|
{"VPHADDD Y2, Y15, Y2", "c4 e2 05 02 d2"},
|
||||||
|
{"VPHADDSW (BX), X9, X11", "c4 62 31 03 1b"},
|
||||||
|
{"VPHADDSW (BX), X9, X2", "c4 e2 31 03 13"},
|
||||||
|
{"VPHADDSW (BX), Y15, Y11", "c4 62 05 03 1b"},
|
||||||
|
{"VPHADDSW (BX), Y15, Y2", "c4 e2 05 03 13"},
|
||||||
|
{"VPHADDSW (R11), X9, X11", "c4 42 31 03 1b"},
|
||||||
|
{"VPHADDSW (R11), X9, X2", "c4 c2 31 03 13"},
|
||||||
|
{"VPHADDSW (R11), Y15, Y11", "c4 42 05 03 1b"},
|
||||||
|
{"VPHADDSW (R11), Y15, Y2", "c4 c2 05 03 13"},
|
||||||
|
{"VPHADDSW X11, X9, X11", "c4 42 31 03 db"},
|
||||||
|
{"VPHADDSW X11, X9, X2", "c4 c2 31 03 d3"},
|
||||||
|
{"VPHADDSW X2, X9, X11", "c4 62 31 03 da"},
|
||||||
|
{"VPHADDSW X2, X9, X2", "c4 e2 31 03 d2"},
|
||||||
|
{"VPHADDSW Y11, Y15, Y11", "c4 42 05 03 db"},
|
||||||
|
{"VPHADDSW Y11, Y15, Y2", "c4 c2 05 03 d3"},
|
||||||
|
{"VPHADDSW Y2, Y15, Y11", "c4 62 05 03 da"},
|
||||||
|
{"VPHADDSW Y2, Y15, Y2", "c4 e2 05 03 d2"},
|
||||||
|
{"VPHADDW (BX), X9, X11", "c4 62 31 01 1b"},
|
||||||
|
{"VPHADDW (BX), X9, X2", "c4 e2 31 01 13"},
|
||||||
|
{"VPHADDW (BX), Y15, Y11", "c4 62 05 01 1b"},
|
||||||
|
{"VPHADDW (BX), Y15, Y2", "c4 e2 05 01 13"},
|
||||||
|
{"VPHADDW (R11), X9, X11", "c4 42 31 01 1b"},
|
||||||
|
{"VPHADDW (R11), X9, X2", "c4 c2 31 01 13"},
|
||||||
|
{"VPHADDW (R11), Y15, Y11", "c4 42 05 01 1b"},
|
||||||
|
{"VPHADDW (R11), Y15, Y2", "c4 c2 05 01 13"},
|
||||||
|
{"VPHADDW X11, X9, X11", "c4 42 31 01 db"},
|
||||||
|
{"VPHADDW X11, X9, X2", "c4 c2 31 01 d3"},
|
||||||
|
{"VPHADDW X2, X9, X11", "c4 62 31 01 da"},
|
||||||
|
{"VPHADDW X2, X9, X2", "c4 e2 31 01 d2"},
|
||||||
|
{"VPHADDW Y11, Y15, Y11", "c4 42 05 01 db"},
|
||||||
|
{"VPHADDW Y11, Y15, Y2", "c4 c2 05 01 d3"},
|
||||||
|
{"VPHADDW Y2, Y15, Y11", "c4 62 05 01 da"},
|
||||||
|
{"VPHADDW Y2, Y15, Y2", "c4 e2 05 01 d2"},
|
||||||
|
{"VPHMINPOSUW (BX), X11", "c4 62 79 41 1b"},
|
||||||
|
{"VPHMINPOSUW (BX), X2", "c4 e2 79 41 13"},
|
||||||
|
{"VPHMINPOSUW (R11), X11", "c4 42 79 41 1b"},
|
||||||
|
{"VPHMINPOSUW (R11), X2", "c4 c2 79 41 13"},
|
||||||
|
{"VPHMINPOSUW X11, X11", "c4 42 79 41 db"},
|
||||||
|
{"VPHMINPOSUW X11, X2", "c4 c2 79 41 d3"},
|
||||||
|
{"VPHMINPOSUW X2, X11", "c4 62 79 41 da"},
|
||||||
|
{"VPHMINPOSUW X2, X2", "c4 e2 79 41 d2"},
|
||||||
|
{"VPHSUBD (BX), X9, X11", "c4 62 31 06 1b"},
|
||||||
|
{"VPHSUBD (BX), X9, X2", "c4 e2 31 06 13"},
|
||||||
|
{"VPHSUBD (BX), Y15, Y11", "c4 62 05 06 1b"},
|
||||||
|
{"VPHSUBD (BX), Y15, Y2", "c4 e2 05 06 13"},
|
||||||
|
{"VPHSUBD (R11), X9, X11", "c4 42 31 06 1b"},
|
||||||
|
{"VPHSUBD (R11), X9, X2", "c4 c2 31 06 13"},
|
||||||
|
{"VPHSUBD (R11), Y15, Y11", "c4 42 05 06 1b"},
|
||||||
|
{"VPHSUBD (R11), Y15, Y2", "c4 c2 05 06 13"},
|
||||||
|
{"VPHSUBD X11, X9, X11", "c4 42 31 06 db"},
|
||||||
|
{"VPHSUBD X11, X9, X2", "c4 c2 31 06 d3"},
|
||||||
|
{"VPHSUBD X2, X9, X11", "c4 62 31 06 da"},
|
||||||
|
{"VPHSUBD X2, X9, X2", "c4 e2 31 06 d2"},
|
||||||
|
{"VPHSUBD Y11, Y15, Y11", "c4 42 05 06 db"},
|
||||||
|
{"VPHSUBD Y11, Y15, Y2", "c4 c2 05 06 d3"},
|
||||||
|
{"VPHSUBD Y2, Y15, Y11", "c4 62 05 06 da"},
|
||||||
|
{"VPHSUBD Y2, Y15, Y2", "c4 e2 05 06 d2"},
|
||||||
|
{"VPHSUBSW (BX), X9, X11", "c4 62 31 07 1b"},
|
||||||
|
{"VPHSUBSW (BX), X9, X2", "c4 e2 31 07 13"},
|
||||||
|
{"VPHSUBSW (BX), Y15, Y11", "c4 62 05 07 1b"},
|
||||||
|
{"VPHSUBSW (BX), Y15, Y2", "c4 e2 05 07 13"},
|
||||||
|
{"VPHSUBSW (R11), X9, X11", "c4 42 31 07 1b"},
|
||||||
|
{"VPHSUBSW (R11), X9, X2", "c4 c2 31 07 13"},
|
||||||
|
{"VPHSUBSW (R11), Y15, Y11", "c4 42 05 07 1b"},
|
||||||
|
{"VPHSUBSW (R11), Y15, Y2", "c4 c2 05 07 13"},
|
||||||
|
{"VPHSUBSW X11, X9, X11", "c4 42 31 07 db"},
|
||||||
|
{"VPHSUBSW X11, X9, X2", "c4 c2 31 07 d3"},
|
||||||
|
{"VPHSUBSW X2, X9, X11", "c4 62 31 07 da"},
|
||||||
|
{"VPHSUBSW X2, X9, X2", "c4 e2 31 07 d2"},
|
||||||
|
{"VPHSUBSW Y11, Y15, Y11", "c4 42 05 07 db"},
|
||||||
|
{"VPHSUBSW Y11, Y15, Y2", "c4 c2 05 07 d3"},
|
||||||
|
{"VPHSUBSW Y2, Y15, Y11", "c4 62 05 07 da"},
|
||||||
|
{"VPHSUBSW Y2, Y15, Y2", "c4 e2 05 07 d2"},
|
||||||
|
{"VPHSUBW (BX), X9, X11", "c4 62 31 05 1b"},
|
||||||
|
{"VPHSUBW (BX), X9, X2", "c4 e2 31 05 13"},
|
||||||
|
{"VPHSUBW (BX), Y15, Y11", "c4 62 05 05 1b"},
|
||||||
|
{"VPHSUBW (BX), Y15, Y2", "c4 e2 05 05 13"},
|
||||||
|
{"VPHSUBW (R11), X9, X11", "c4 42 31 05 1b"},
|
||||||
|
{"VPHSUBW (R11), X9, X2", "c4 c2 31 05 13"},
|
||||||
|
{"VPHSUBW (R11), Y15, Y11", "c4 42 05 05 1b"},
|
||||||
|
{"VPHSUBW (R11), Y15, Y2", "c4 c2 05 05 13"},
|
||||||
|
{"VPHSUBW X11, X9, X11", "c4 42 31 05 db"},
|
||||||
|
{"VPHSUBW X11, X9, X2", "c4 c2 31 05 d3"},
|
||||||
|
{"VPHSUBW X2, X9, X11", "c4 62 31 05 da"},
|
||||||
|
{"VPHSUBW X2, X9, X2", "c4 e2 31 05 d2"},
|
||||||
|
{"VPHSUBW Y11, Y15, Y11", "c4 42 05 05 db"},
|
||||||
|
{"VPHSUBW Y11, Y15, Y2", "c4 c2 05 05 d3"},
|
||||||
|
{"VPHSUBW Y2, Y15, Y11", "c4 62 05 05 da"},
|
||||||
|
{"VPHSUBW Y2, Y15, Y2", "c4 e2 05 05 d2"},
|
||||||
|
{"VPINSRB $7, (BX), X9, X11", "c4 63 31 20 1b 07"},
|
||||||
|
{"VPINSRB $7, (BX), X9, X2", "c4 e3 31 20 13 07"},
|
||||||
|
{"VPINSRB $7, (R11), X9, X11", "c4 43 31 20 1b 07"},
|
||||||
|
{"VPINSRB $7, (R11), X9, X2", "c4 c3 31 20 13 07"},
|
||||||
|
{"VPINSRB $7, DX, X9, X11", "c4 63 31 20 da 07"},
|
||||||
|
{"VPINSRB $7, DX, X9, X2", "c4 e3 31 20 d2 07"},
|
||||||
|
{"VPINSRB $7, R11, X9, X11", "c4 43 31 20 db 07"},
|
||||||
|
{"VPINSRB $7, R11, X9, X2", "c4 c3 31 20 d3 07"},
|
||||||
|
{"VPINSRW $7, (BX), X9, X11", "c5 31 c4 1b 07"},
|
||||||
|
{"VPINSRW $7, (BX), X9, X2", "c5 b1 c4 13 07"},
|
||||||
|
{"VPINSRW $7, (R11), X9, X11", "c4 41 31 c4 1b 07"},
|
||||||
|
{"VPINSRW $7, (R11), X9, X2", "c4 c1 31 c4 13 07"},
|
||||||
|
{"VPINSRW $7, DX, X9, X11", "c5 31 c4 da 07"},
|
||||||
|
{"VPINSRW $7, DX, X9, X2", "c5 b1 c4 d2 07"},
|
||||||
|
{"VPINSRW $7, R11, X9, X11", "c4 41 31 c4 db 07"},
|
||||||
|
{"VPINSRW $7, R11, X9, X2", "c4 c1 31 c4 d3 07"},
|
||||||
|
{"VPMASKMOVD (BX), X9, X11", "c4 62 31 8c 1b"},
|
||||||
|
{"VPMASKMOVD (BX), X9, X2", "c4 e2 31 8c 13"},
|
||||||
|
{"VPMASKMOVD (BX), Y15, Y11", "c4 62 05 8c 1b"},
|
||||||
|
{"VPMASKMOVD (BX), Y15, Y2", "c4 e2 05 8c 13"},
|
||||||
|
{"VPMASKMOVD (R11), X9, X11", "c4 42 31 8c 1b"},
|
||||||
|
{"VPMASKMOVD (R11), X9, X2", "c4 c2 31 8c 13"},
|
||||||
|
{"VPMASKMOVD (R11), Y15, Y11", "c4 42 05 8c 1b"},
|
||||||
|
{"VPMASKMOVD (R11), Y15, Y2", "c4 c2 05 8c 13"},
|
||||||
|
{"VPMASKMOVD X11, X9, (BX)", "c4 62 31 8e 1b"},
|
||||||
|
{"VPMASKMOVD X11, X9, (R11)", "c4 42 31 8e 1b"},
|
||||||
|
{"VPMASKMOVD X2, X9, (BX)", "c4 e2 31 8e 13"},
|
||||||
|
{"VPMASKMOVD X2, X9, (R11)", "c4 c2 31 8e 13"},
|
||||||
|
{"VPMASKMOVD Y11, Y15, (BX)", "c4 62 05 8e 1b"},
|
||||||
|
{"VPMASKMOVD Y11, Y15, (R11)", "c4 42 05 8e 1b"},
|
||||||
|
{"VPMASKMOVD Y2, Y15, (BX)", "c4 e2 05 8e 13"},
|
||||||
|
{"VPMASKMOVD Y2, Y15, (R11)", "c4 c2 05 8e 13"},
|
||||||
|
{"VPMASKMOVQ (BX), X9, X11", "c4 62 b1 8c 1b"},
|
||||||
|
{"VPMASKMOVQ (BX), X9, X2", "c4 e2 b1 8c 13"},
|
||||||
|
{"VPMASKMOVQ (BX), Y15, Y11", "c4 62 85 8c 1b"},
|
||||||
|
{"VPMASKMOVQ (BX), Y15, Y2", "c4 e2 85 8c 13"},
|
||||||
|
{"VPMASKMOVQ (R11), X9, X11", "c4 42 b1 8c 1b"},
|
||||||
|
{"VPMASKMOVQ (R11), X9, X2", "c4 c2 b1 8c 13"},
|
||||||
|
{"VPMASKMOVQ (R11), Y15, Y11", "c4 42 85 8c 1b"},
|
||||||
|
{"VPMASKMOVQ (R11), Y15, Y2", "c4 c2 85 8c 13"},
|
||||||
|
{"VPMASKMOVQ X11, X9, (BX)", "c4 62 b1 8e 1b"},
|
||||||
|
{"VPMASKMOVQ X11, X9, (R11)", "c4 42 b1 8e 1b"},
|
||||||
|
{"VPMASKMOVQ X2, X9, (BX)", "c4 e2 b1 8e 13"},
|
||||||
|
{"VPMASKMOVQ X2, X9, (R11)", "c4 c2 b1 8e 13"},
|
||||||
|
{"VPMASKMOVQ Y11, Y15, (BX)", "c4 62 85 8e 1b"},
|
||||||
|
{"VPMASKMOVQ Y11, Y15, (R11)", "c4 42 85 8e 1b"},
|
||||||
|
{"VPMASKMOVQ Y2, Y15, (BX)", "c4 e2 85 8e 13"},
|
||||||
|
{"VPMASKMOVQ Y2, Y15, (R11)", "c4 c2 85 8e 13"},
|
||||||
|
{"VPSIGNB (BX), X9, X11", "c4 62 31 08 1b"},
|
||||||
|
{"VPSIGNB (BX), X9, X2", "c4 e2 31 08 13"},
|
||||||
|
{"VPSIGNB (BX), Y15, Y11", "c4 62 05 08 1b"},
|
||||||
|
{"VPSIGNB (BX), Y15, Y2", "c4 e2 05 08 13"},
|
||||||
|
{"VPSIGNB (R11), X9, X11", "c4 42 31 08 1b"},
|
||||||
|
{"VPSIGNB (R11), X9, X2", "c4 c2 31 08 13"},
|
||||||
|
{"VPSIGNB (R11), Y15, Y11", "c4 42 05 08 1b"},
|
||||||
|
{"VPSIGNB (R11), Y15, Y2", "c4 c2 05 08 13"},
|
||||||
|
{"VPSIGNB X11, X9, X11", "c4 42 31 08 db"},
|
||||||
|
{"VPSIGNB X11, X9, X2", "c4 c2 31 08 d3"},
|
||||||
|
{"VPSIGNB X2, X9, X11", "c4 62 31 08 da"},
|
||||||
|
{"VPSIGNB X2, X9, X2", "c4 e2 31 08 d2"},
|
||||||
|
{"VPSIGNB Y11, Y15, Y11", "c4 42 05 08 db"},
|
||||||
|
{"VPSIGNB Y11, Y15, Y2", "c4 c2 05 08 d3"},
|
||||||
|
{"VPSIGNB Y2, Y15, Y11", "c4 62 05 08 da"},
|
||||||
|
{"VPSIGNB Y2, Y15, Y2", "c4 e2 05 08 d2"},
|
||||||
|
{"VPSIGND (BX), X9, X11", "c4 62 31 0a 1b"},
|
||||||
|
{"VPSIGND (BX), X9, X2", "c4 e2 31 0a 13"},
|
||||||
|
{"VPSIGND (BX), Y15, Y11", "c4 62 05 0a 1b"},
|
||||||
|
{"VPSIGND (BX), Y15, Y2", "c4 e2 05 0a 13"},
|
||||||
|
{"VPSIGND (R11), X9, X11", "c4 42 31 0a 1b"},
|
||||||
|
{"VPSIGND (R11), X9, X2", "c4 c2 31 0a 13"},
|
||||||
|
{"VPSIGND (R11), Y15, Y11", "c4 42 05 0a 1b"},
|
||||||
|
{"VPSIGND (R11), Y15, Y2", "c4 c2 05 0a 13"},
|
||||||
|
{"VPSIGND X11, X9, X11", "c4 42 31 0a db"},
|
||||||
|
{"VPSIGND X11, X9, X2", "c4 c2 31 0a d3"},
|
||||||
|
{"VPSIGND X2, X9, X11", "c4 62 31 0a da"},
|
||||||
|
{"VPSIGND X2, X9, X2", "c4 e2 31 0a d2"},
|
||||||
|
{"VPSIGND Y11, Y15, Y11", "c4 42 05 0a db"},
|
||||||
|
{"VPSIGND Y11, Y15, Y2", "c4 c2 05 0a d3"},
|
||||||
|
{"VPSIGND Y2, Y15, Y11", "c4 62 05 0a da"},
|
||||||
|
{"VPSIGND Y2, Y15, Y2", "c4 e2 05 0a d2"},
|
||||||
|
{"VPSIGNW (BX), X9, X11", "c4 62 31 09 1b"},
|
||||||
|
{"VPSIGNW (BX), X9, X2", "c4 e2 31 09 13"},
|
||||||
|
{"VPSIGNW (BX), Y15, Y11", "c4 62 05 09 1b"},
|
||||||
|
{"VPSIGNW (BX), Y15, Y2", "c4 e2 05 09 13"},
|
||||||
|
{"VPSIGNW (R11), X9, X11", "c4 42 31 09 1b"},
|
||||||
|
{"VPSIGNW (R11), X9, X2", "c4 c2 31 09 13"},
|
||||||
|
{"VPSIGNW (R11), Y15, Y11", "c4 42 05 09 1b"},
|
||||||
|
{"VPSIGNW (R11), Y15, Y2", "c4 c2 05 09 13"},
|
||||||
|
{"VPSIGNW X11, X9, X11", "c4 42 31 09 db"},
|
||||||
|
{"VPSIGNW X11, X9, X2", "c4 c2 31 09 d3"},
|
||||||
|
{"VPSIGNW X2, X9, X11", "c4 62 31 09 da"},
|
||||||
|
{"VPSIGNW X2, X9, X2", "c4 e2 31 09 d2"},
|
||||||
|
{"VPSIGNW Y11, Y15, Y11", "c4 42 05 09 db"},
|
||||||
|
{"VPSIGNW Y11, Y15, Y2", "c4 c2 05 09 d3"},
|
||||||
|
{"VPSIGNW Y2, Y15, Y11", "c4 62 05 09 da"},
|
||||||
|
{"VPSIGNW Y2, Y15, Y2", "c4 e2 05 09 d2"},
|
||||||
|
{"VPSLLW $7, X11, X9", "c4 c1 31 71 f3 07"},
|
||||||
|
{"VPSLLW $7, X2, X9", "c5 b1 71 f2 07"},
|
||||||
|
{"VPSLLW $7, Y11, Y15", "c4 c1 05 71 f3 07"},
|
||||||
|
{"VPSLLW $7, Y2, Y15", "c5 85 71 f2 07"},
|
||||||
|
{"VPSLLW (BX), X9, X11", "c5 31 f1 1b"},
|
||||||
|
{"VPSLLW (BX), X9, X2", "c5 b1 f1 13"},
|
||||||
|
{"VPSLLW (BX), Y15, Y11", "c5 05 f1 1b"},
|
||||||
|
{"VPSLLW (BX), Y15, Y2", "c5 85 f1 13"},
|
||||||
|
{"VPSLLW (R11), X9, X11", "c4 41 31 f1 1b"},
|
||||||
|
{"VPSLLW (R11), X9, X2", "c4 c1 31 f1 13"},
|
||||||
|
{"VPSLLW (R11), Y15, Y11", "c4 41 05 f1 1b"},
|
||||||
|
{"VPSLLW (R11), Y15, Y2", "c4 c1 05 f1 13"},
|
||||||
|
{"VPSLLW X11, X9, X11", "c4 41 31 f1 db"},
|
||||||
|
{"VPSLLW X11, X9, X2", "c4 c1 31 f1 d3"},
|
||||||
|
{"VPSLLW X11, Y15, Y11", "c4 41 05 f1 db"},
|
||||||
|
{"VPSLLW X11, Y15, Y2", "c4 c1 05 f1 d3"},
|
||||||
|
{"VPSLLW X2, X9, X11", "c5 31 f1 da"},
|
||||||
|
{"VPSLLW X2, X9, X2", "c5 b1 f1 d2"},
|
||||||
|
{"VPSLLW X2, Y15, Y11", "c5 05 f1 da"},
|
||||||
|
{"VPSLLW X2, Y15, Y2", "c5 85 f1 d2"},
|
||||||
|
{"VPSRAW $7, X11, X9", "c4 c1 31 71 e3 07"},
|
||||||
|
{"VPSRAW $7, X2, X9", "c5 b1 71 e2 07"},
|
||||||
|
{"VPSRAW $7, Y11, Y15", "c4 c1 05 71 e3 07"},
|
||||||
|
{"VPSRAW $7, Y2, Y15", "c5 85 71 e2 07"},
|
||||||
|
{"VPSRAW (BX), X9, X11", "c5 31 e1 1b"},
|
||||||
|
{"VPSRAW (BX), X9, X2", "c5 b1 e1 13"},
|
||||||
|
{"VPSRAW (BX), Y15, Y11", "c5 05 e1 1b"},
|
||||||
|
{"VPSRAW (BX), Y15, Y2", "c5 85 e1 13"},
|
||||||
|
{"VPSRAW (R11), X9, X11", "c4 41 31 e1 1b"},
|
||||||
|
{"VPSRAW (R11), X9, X2", "c4 c1 31 e1 13"},
|
||||||
|
{"VPSRAW (R11), Y15, Y11", "c4 41 05 e1 1b"},
|
||||||
|
{"VPSRAW (R11), Y15, Y2", "c4 c1 05 e1 13"},
|
||||||
|
{"VPSRAW X11, X9, X11", "c4 41 31 e1 db"},
|
||||||
|
{"VPSRAW X11, X9, X2", "c4 c1 31 e1 d3"},
|
||||||
|
{"VPSRAW X11, Y15, Y11", "c4 41 05 e1 db"},
|
||||||
|
{"VPSRAW X11, Y15, Y2", "c4 c1 05 e1 d3"},
|
||||||
|
{"VPSRAW X2, X9, X11", "c5 31 e1 da"},
|
||||||
|
{"VPSRAW X2, X9, X2", "c5 b1 e1 d2"},
|
||||||
|
{"VPSRAW X2, Y15, Y11", "c5 05 e1 da"},
|
||||||
|
{"VPSRAW X2, Y15, Y2", "c5 85 e1 d2"},
|
||||||
|
{"VPSRLW $7, X11, X9", "c4 c1 31 71 d3 07"},
|
||||||
|
{"VPSRLW $7, X2, X9", "c5 b1 71 d2 07"},
|
||||||
|
{"VPSRLW $7, Y11, Y15", "c4 c1 05 71 d3 07"},
|
||||||
|
{"VPSRLW $7, Y2, Y15", "c5 85 71 d2 07"},
|
||||||
|
{"VPSRLW (BX), X9, X11", "c5 31 d1 1b"},
|
||||||
|
{"VPSRLW (BX), X9, X2", "c5 b1 d1 13"},
|
||||||
|
{"VPSRLW (BX), Y15, Y11", "c5 05 d1 1b"},
|
||||||
|
{"VPSRLW (BX), Y15, Y2", "c5 85 d1 13"},
|
||||||
|
{"VPSRLW (R11), X9, X11", "c4 41 31 d1 1b"},
|
||||||
|
{"VPSRLW (R11), X9, X2", "c4 c1 31 d1 13"},
|
||||||
|
{"VPSRLW (R11), Y15, Y11", "c4 41 05 d1 1b"},
|
||||||
|
{"VPSRLW (R11), Y15, Y2", "c4 c1 05 d1 13"},
|
||||||
|
{"VPSRLW X11, X9, X11", "c4 41 31 d1 db"},
|
||||||
|
{"VPSRLW X11, X9, X2", "c4 c1 31 d1 d3"},
|
||||||
|
{"VPSRLW X11, Y15, Y11", "c4 41 05 d1 db"},
|
||||||
|
{"VPSRLW X11, Y15, Y2", "c4 c1 05 d1 d3"},
|
||||||
|
{"VPSRLW X2, X9, X11", "c5 31 d1 da"},
|
||||||
|
{"VPSRLW X2, X9, X2", "c5 b1 d1 d2"},
|
||||||
|
{"VPSRLW X2, Y15, Y11", "c5 05 d1 da"},
|
||||||
|
{"VPSRLW X2, Y15, Y2", "c5 85 d1 d2"},
|
||||||
|
{"VRCPPS (BX), X11", "c5 78 53 1b"},
|
||||||
|
{"VRCPPS (BX), X2", "c5 f8 53 13"},
|
||||||
|
{"VRCPPS (BX), Y11", "c5 7c 53 1b"},
|
||||||
|
{"VRCPPS (BX), Y2", "c5 fc 53 13"},
|
||||||
|
{"VRCPPS (R11), X11", "c4 41 78 53 1b"},
|
||||||
|
{"VRCPPS (R11), X2", "c4 c1 78 53 13"},
|
||||||
|
{"VRCPPS (R11), Y11", "c4 41 7c 53 1b"},
|
||||||
|
{"VRCPPS (R11), Y2", "c4 c1 7c 53 13"},
|
||||||
|
{"VRCPPS X11, X11", "c4 41 78 53 db"},
|
||||||
|
{"VRCPPS X11, X2", "c4 c1 78 53 d3"},
|
||||||
|
{"VRCPPS X2, X11", "c5 78 53 da"},
|
||||||
|
{"VRCPPS X2, X2", "c5 f8 53 d2"},
|
||||||
|
{"VRCPPS Y11, Y11", "c4 41 7c 53 db"},
|
||||||
|
{"VRCPPS Y11, Y2", "c4 c1 7c 53 d3"},
|
||||||
|
{"VRCPPS Y2, Y11", "c5 7c 53 da"},
|
||||||
|
{"VRCPPS Y2, Y2", "c5 fc 53 d2"},
|
||||||
|
{"VRCPSS (BX), X9, X11", "c5 32 53 1b"},
|
||||||
|
{"VRCPSS (BX), X9, X2", "c5 b2 53 13"},
|
||||||
|
{"VRCPSS (R11), X9, X11", "c4 41 32 53 1b"},
|
||||||
|
{"VRCPSS (R11), X9, X2", "c4 c1 32 53 13"},
|
||||||
|
{"VRCPSS X11, X9, X11", "c4 41 32 53 db"},
|
||||||
|
{"VRCPSS X11, X9, X2", "c4 c1 32 53 d3"},
|
||||||
|
{"VRCPSS X2, X9, X11", "c5 32 53 da"},
|
||||||
|
{"VRCPSS X2, X9, X2", "c5 b2 53 d2"},
|
||||||
|
{"VROUNDSD $7, (BX), X9, X11", "c4 63 31 0b 1b 07"},
|
||||||
|
{"VROUNDSD $7, (BX), X9, X2", "c4 e3 31 0b 13 07"},
|
||||||
|
{"VROUNDSD $7, (R11), X9, X11", "c4 43 31 0b 1b 07"},
|
||||||
|
{"VROUNDSD $7, (R11), X9, X2", "c4 c3 31 0b 13 07"},
|
||||||
|
{"VROUNDSD $7, X11, X9, X11", "c4 43 31 0b db 07"},
|
||||||
|
{"VROUNDSD $7, X11, X9, X2", "c4 c3 31 0b d3 07"},
|
||||||
|
{"VROUNDSD $7, X2, X9, X11", "c4 63 31 0b da 07"},
|
||||||
|
{"VROUNDSD $7, X2, X9, X2", "c4 e3 31 0b d2 07"},
|
||||||
|
{"VROUNDSS $7, (BX), X9, X11", "c4 63 31 0a 1b 07"},
|
||||||
|
{"VROUNDSS $7, (BX), X9, X2", "c4 e3 31 0a 13 07"},
|
||||||
|
{"VROUNDSS $7, (R11), X9, X11", "c4 43 31 0a 1b 07"},
|
||||||
|
{"VROUNDSS $7, (R11), X9, X2", "c4 c3 31 0a 13 07"},
|
||||||
|
{"VROUNDSS $7, X11, X9, X11", "c4 43 31 0a db 07"},
|
||||||
|
{"VROUNDSS $7, X11, X9, X2", "c4 c3 31 0a d3 07"},
|
||||||
|
{"VROUNDSS $7, X2, X9, X11", "c4 63 31 0a da 07"},
|
||||||
|
{"VROUNDSS $7, X2, X9, X2", "c4 e3 31 0a d2 07"},
|
||||||
|
{"VRSQRTPS (BX), X11", "c5 78 52 1b"},
|
||||||
|
{"VRSQRTPS (BX), X2", "c5 f8 52 13"},
|
||||||
|
{"VRSQRTPS (BX), Y11", "c5 7c 52 1b"},
|
||||||
|
{"VRSQRTPS (BX), Y2", "c5 fc 52 13"},
|
||||||
|
{"VRSQRTPS (R11), X11", "c4 41 78 52 1b"},
|
||||||
|
{"VRSQRTPS (R11), X2", "c4 c1 78 52 13"},
|
||||||
|
{"VRSQRTPS (R11), Y11", "c4 41 7c 52 1b"},
|
||||||
|
{"VRSQRTPS (R11), Y2", "c4 c1 7c 52 13"},
|
||||||
|
{"VRSQRTPS X11, X11", "c4 41 78 52 db"},
|
||||||
|
{"VRSQRTPS X11, X2", "c4 c1 78 52 d3"},
|
||||||
|
{"VRSQRTPS X2, X11", "c5 78 52 da"},
|
||||||
|
{"VRSQRTPS X2, X2", "c5 f8 52 d2"},
|
||||||
|
{"VRSQRTPS Y11, Y11", "c4 41 7c 52 db"},
|
||||||
|
{"VRSQRTPS Y11, Y2", "c4 c1 7c 52 d3"},
|
||||||
|
{"VRSQRTPS Y2, Y11", "c5 7c 52 da"},
|
||||||
|
{"VRSQRTPS Y2, Y2", "c5 fc 52 d2"},
|
||||||
|
{"VRSQRTSS (BX), X9, X11", "c5 32 52 1b"},
|
||||||
|
{"VRSQRTSS (BX), X9, X2", "c5 b2 52 13"},
|
||||||
|
{"VRSQRTSS (R11), X9, X11", "c4 41 32 52 1b"},
|
||||||
|
{"VRSQRTSS (R11), X9, X2", "c4 c1 32 52 13"},
|
||||||
|
{"VRSQRTSS X11, X9, X11", "c4 41 32 52 db"},
|
||||||
|
{"VRSQRTSS X11, X9, X2", "c4 c1 32 52 d3"},
|
||||||
|
{"VRSQRTSS X2, X9, X11", "c5 32 52 da"},
|
||||||
|
{"VRSQRTSS X2, X9, X2", "c5 b2 52 d2"},
|
||||||
|
{"VSTMXCSR (BX)", "c5 f8 ae 1b"},
|
||||||
|
{"VSTMXCSR (R11)", "c4 c1 78 ae 1b"},
|
||||||
|
{"VTESTPD (BX), X11", "c4 62 79 0f 1b"},
|
||||||
|
{"VTESTPD (BX), X2", "c4 e2 79 0f 13"},
|
||||||
|
{"VTESTPD (BX), Y11", "c4 62 7d 0f 1b"},
|
||||||
|
{"VTESTPD (BX), Y2", "c4 e2 7d 0f 13"},
|
||||||
|
{"VTESTPD (R11), X11", "c4 42 79 0f 1b"},
|
||||||
|
{"VTESTPD (R11), X2", "c4 c2 79 0f 13"},
|
||||||
|
{"VTESTPD (R11), Y11", "c4 42 7d 0f 1b"},
|
||||||
|
{"VTESTPD (R11), Y2", "c4 c2 7d 0f 13"},
|
||||||
|
{"VTESTPD X11, X11", "c4 42 79 0f db"},
|
||||||
|
{"VTESTPD X11, X2", "c4 c2 79 0f d3"},
|
||||||
|
{"VTESTPD X2, X11", "c4 62 79 0f da"},
|
||||||
|
{"VTESTPD X2, X2", "c4 e2 79 0f d2"},
|
||||||
|
{"VTESTPD Y11, Y11", "c4 42 7d 0f db"},
|
||||||
|
{"VTESTPD Y11, Y2", "c4 c2 7d 0f d3"},
|
||||||
|
{"VTESTPD Y2, Y11", "c4 62 7d 0f da"},
|
||||||
|
{"VTESTPD Y2, Y2", "c4 e2 7d 0f d2"},
|
||||||
|
{"VTESTPS (BX), X11", "c4 62 79 0e 1b"},
|
||||||
|
{"VTESTPS (BX), X2", "c4 e2 79 0e 13"},
|
||||||
|
{"VTESTPS (BX), Y11", "c4 62 7d 0e 1b"},
|
||||||
|
{"VTESTPS (BX), Y2", "c4 e2 7d 0e 13"},
|
||||||
|
{"VTESTPS (R11), X11", "c4 42 79 0e 1b"},
|
||||||
|
{"VTESTPS (R11), X2", "c4 c2 79 0e 13"},
|
||||||
|
{"VTESTPS (R11), Y11", "c4 42 7d 0e 1b"},
|
||||||
|
{"VTESTPS (R11), Y2", "c4 c2 7d 0e 13"},
|
||||||
|
{"VTESTPS X11, X11", "c4 42 79 0e db"},
|
||||||
|
{"VTESTPS X11, X2", "c4 c2 79 0e d3"},
|
||||||
|
{"VTESTPS X2, X11", "c4 62 79 0e da"},
|
||||||
|
{"VTESTPS X2, X2", "c4 e2 79 0e d2"},
|
||||||
|
{"VTESTPS Y11, Y11", "c4 42 7d 0e db"},
|
||||||
|
{"VTESTPS Y11, Y2", "c4 c2 7d 0e d3"},
|
||||||
|
{"VTESTPS Y2, Y11", "c4 62 7d 0e da"},
|
||||||
|
{"VTESTPS Y2, Y2", "c4 e2 7d 0e d2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64VexCorpus assembles every corpus line and requires the same bytes
|
||||||
|
// go tool asm emits for it.
|
||||||
|
func TestAmd64VexCorpus(t *testing.T) {
|
||||||
|
for _, tc := range amd64VexCorpus {
|
||||||
|
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tc.line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexBytes(code); got != tc.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", tc.line, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,835 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// amd64VexParityCorpus holds every line the Go toolchain's own
|
||||||
|
// amd64enc.s carries for the mnemonics the full-file byte sweep
|
||||||
|
// flagged: the VEX forms the toolchain prefers for plain vector
|
||||||
|
// registers, the compare-with-predicate family and the byte-level
|
||||||
|
// corrections (PEXTRW's GPR fields, PUSHW and POPW widths, the double
|
||||||
|
// shift's high register field, VCOMISS's prefix, RORX's destination
|
||||||
|
// bit, the variable shifts' consolidated rows). Each entry lists the
|
||||||
|
// byte strings go tool asm emits or accepts for the line; a line
|
||||||
|
// passes when the assembled bytes match one of them.
|
||||||
|
var amd64VexParityCorpus = []struct {
|
||||||
|
line string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"PEXTRW $7, X11, (BX)", "66 44 0f 3a 15 1b 07"},
|
||||||
|
{"PEXTRW $7, X11, (R11)", "66 45 0f 3a 15 1b 07"},
|
||||||
|
{"PEXTRW $7, X11, DX", "66 41 0f c5 d3 07|66 44 0f 3a 15 da 07"},
|
||||||
|
{"PEXTRW $7, X11, R11", "66 45 0f c5 db 07|66 45 0f 3a 15 db 07"},
|
||||||
|
{"PEXTRW $7, X2, (BX)", "66 0f 3a 15 13 07"},
|
||||||
|
{"PEXTRW $7, X2, (R11)", "66 41 0f 3a 15 13 07"},
|
||||||
|
{"PEXTRW $7, X2, DX", "66 0f c5 d2 07|66 0f 3a 15 d2 07"},
|
||||||
|
{"PEXTRW $7, X2, R11", "66 44 0f c5 da 07|66 41 0f 3a 15 d3 07"},
|
||||||
|
{"POPW (BX)", "66 8f 03"},
|
||||||
|
{"POPW (R11)", "66 41 8f 03"},
|
||||||
|
{"POPW DX", "66 8f c2|66 5a"},
|
||||||
|
{"POPW R11", "66 41 8f c3|66 41 5b"},
|
||||||
|
{"PUSHW $61731", "66 68 23 f1"},
|
||||||
|
{"PUSHW (BX)", "66 ff 33"},
|
||||||
|
{"PUSHW (R11)", "66 41 ff 33"},
|
||||||
|
{"PUSHW DX", "66 ff f2|66 52"},
|
||||||
|
{"PUSHW R11", "66 41 ff f3|66 41 53"},
|
||||||
|
{"RORXL $7, (BX), DX", "c4 e3 7b f0 13 07"},
|
||||||
|
{"RORXL $7, (BX), R11", "c4 63 7b f0 1b 07"},
|
||||||
|
{"RORXL $7, (R11), DX", "c4 c3 7b f0 13 07"},
|
||||||
|
{"RORXL $7, (R11), R11", "c4 43 7b f0 1b 07"},
|
||||||
|
{"RORXL $7, DX, DX", "c4 e3 7b f0 d2 07"},
|
||||||
|
{"RORXL $7, DX, R11", "c4 63 7b f0 da 07"},
|
||||||
|
{"RORXL $7, R11, DX", "c4 c3 7b f0 d3 07"},
|
||||||
|
{"RORXL $7, R11, R11", "c4 43 7b f0 db 07"},
|
||||||
|
{"RORXQ $7, (BX), DX", "c4 e3 fb f0 13 07"},
|
||||||
|
{"RORXQ $7, (BX), R11", "c4 63 fb f0 1b 07"},
|
||||||
|
{"RORXQ $7, (R11), DX", "c4 c3 fb f0 13 07"},
|
||||||
|
{"RORXQ $7, (R11), R11", "c4 43 fb f0 1b 07"},
|
||||||
|
{"RORXQ $7, DX, DX", "c4 e3 fb f0 d2 07"},
|
||||||
|
{"RORXQ $7, DX, R11", "c4 63 fb f0 da 07"},
|
||||||
|
{"RORXQ $7, R11, DX", "c4 c3 fb f0 d3 07"},
|
||||||
|
{"RORXQ $7, R11, R11", "c4 43 fb f0 db 07"},
|
||||||
|
{"SHLL $1, (BX)", "d1 23"},
|
||||||
|
{"SHLL $1, (R11)", "41 d1 23"},
|
||||||
|
{"SHLL $1, DX", "d1 e2"},
|
||||||
|
{"SHLL $1, R11", "41 d1 e3"},
|
||||||
|
{"SHLL $7, (BX)", "c1 23 07"},
|
||||||
|
{"SHLL $7, (R11)", "41 c1 23 07"},
|
||||||
|
{"SHLL $7, DX", "c1 e2 07"},
|
||||||
|
{"SHLL $7, DX, (BX)", "0f a4 13 07"},
|
||||||
|
{"SHLL $7, DX, (R11)", "41 0f a4 13 07"},
|
||||||
|
{"SHLL $7, DX, DX", "0f a4 d2 07"},
|
||||||
|
{"SHLL $7, DX, R11", "41 0f a4 d3 07"},
|
||||||
|
{"SHLL $7, R11", "41 c1 e3 07"},
|
||||||
|
{"SHLL $7, R11, (BX)", "44 0f a4 1b 07"},
|
||||||
|
{"SHLL $7, R11, (R11)", "45 0f a4 1b 07"},
|
||||||
|
{"SHLL $7, R11, DX", "44 0f a4 da 07"},
|
||||||
|
{"SHLL $7, R11, R11", "45 0f a4 db 07"},
|
||||||
|
{"SHLL CL, (BX)", "d3 23"},
|
||||||
|
{"SHLL CL, (R11)", "41 d3 23"},
|
||||||
|
{"SHLL CL, DX", "d3 e2"},
|
||||||
|
{"SHLL CL, DX, (BX)", "0f a5 13"},
|
||||||
|
{"SHLL CL, DX, (R11)", "41 0f a5 13"},
|
||||||
|
{"SHLL CL, DX, DX", "0f a5 d2"},
|
||||||
|
{"SHLL CL, DX, R11", "41 0f a5 d3"},
|
||||||
|
{"SHLL CL, R11", "41 d3 e3"},
|
||||||
|
{"SHLL CL, R11, (BX)", "44 0f a5 1b"},
|
||||||
|
{"SHLL CL, R11, (R11)", "45 0f a5 1b"},
|
||||||
|
{"SHLL CL, R11, DX", "44 0f a5 da"},
|
||||||
|
{"SHLL CL, R11, R11", "45 0f a5 db"},
|
||||||
|
{"SHLQ $1, (BX)", "48 d1 23"},
|
||||||
|
{"SHLQ $1, (R11)", "49 d1 23"},
|
||||||
|
{"SHLQ $1, DX", "48 d1 e2"},
|
||||||
|
{"SHLQ $1, R11", "49 d1 e3"},
|
||||||
|
{"SHLQ $7, (BX)", "48 c1 23 07"},
|
||||||
|
{"SHLQ $7, (R11)", "49 c1 23 07"},
|
||||||
|
{"SHLQ $7, DX", "48 c1 e2 07"},
|
||||||
|
{"SHLQ $7, DX, (BX)", "48 0f a4 13 07"},
|
||||||
|
{"SHLQ $7, DX, (R11)", "49 0f a4 13 07"},
|
||||||
|
{"SHLQ $7, DX, DX", "48 0f a4 d2 07"},
|
||||||
|
{"SHLQ $7, DX, R11", "49 0f a4 d3 07"},
|
||||||
|
{"SHLQ $7, R11", "49 c1 e3 07"},
|
||||||
|
{"SHLQ $7, R11, (BX)", "4c 0f a4 1b 07"},
|
||||||
|
{"SHLQ $7, R11, (R11)", "4d 0f a4 1b 07"},
|
||||||
|
{"SHLQ $7, R11, DX", "4c 0f a4 da 07"},
|
||||||
|
{"SHLQ $7, R11, R11", "4d 0f a4 db 07"},
|
||||||
|
{"SHLQ CL, (BX)", "48 d3 23"},
|
||||||
|
{"SHLQ CL, (R11)", "49 d3 23"},
|
||||||
|
{"SHLQ CL, DX", "48 d3 e2"},
|
||||||
|
{"SHLQ CL, DX, (BX)", "48 0f a5 13"},
|
||||||
|
{"SHLQ CL, DX, (R11)", "49 0f a5 13"},
|
||||||
|
{"SHLQ CL, DX, DX", "48 0f a5 d2"},
|
||||||
|
{"SHLQ CL, DX, R11", "49 0f a5 d3"},
|
||||||
|
{"SHLQ CL, R11", "49 d3 e3"},
|
||||||
|
{"SHLQ CL, R11, (BX)", "4c 0f a5 1b"},
|
||||||
|
{"SHLQ CL, R11, (R11)", "4d 0f a5 1b"},
|
||||||
|
{"SHLQ CL, R11, DX", "4c 0f a5 da"},
|
||||||
|
{"SHLQ CL, R11, R11", "4d 0f a5 db"},
|
||||||
|
{"SHLW $1, (BX)", "66 d1 23"},
|
||||||
|
{"SHLW $1, (R11)", "66 41 d1 23"},
|
||||||
|
{"SHLW $1, DX", "66 d1 e2"},
|
||||||
|
{"SHLW $1, R11", "66 41 d1 e3"},
|
||||||
|
{"SHLW $7, (BX)", "66 c1 23 07"},
|
||||||
|
{"SHLW $7, (R11)", "66 41 c1 23 07"},
|
||||||
|
{"SHLW $7, DX", "66 c1 e2 07"},
|
||||||
|
{"SHLW $7, DX, (BX)", "66 0f a4 13 07"},
|
||||||
|
{"SHLW $7, DX, (R11)", "66 41 0f a4 13 07"},
|
||||||
|
{"SHLW $7, DX, DX", "66 0f a4 d2 07"},
|
||||||
|
{"SHLW $7, DX, R11", "66 41 0f a4 d3 07"},
|
||||||
|
{"SHLW $7, R11", "66 41 c1 e3 07"},
|
||||||
|
{"SHLW $7, R11, (BX)", "66 44 0f a4 1b 07"},
|
||||||
|
{"SHLW $7, R11, (R11)", "66 45 0f a4 1b 07"},
|
||||||
|
{"SHLW $7, R11, DX", "66 44 0f a4 da 07"},
|
||||||
|
{"SHLW $7, R11, R11", "66 45 0f a4 db 07"},
|
||||||
|
{"SHLW CL, (BX)", "66 d3 23"},
|
||||||
|
{"SHLW CL, (R11)", "66 41 d3 23"},
|
||||||
|
{"SHLW CL, DX", "66 d3 e2"},
|
||||||
|
{"SHLW CL, DX, (BX)", "66 0f a5 13"},
|
||||||
|
{"SHLW CL, DX, (R11)", "66 41 0f a5 13"},
|
||||||
|
{"SHLW CL, DX, DX", "66 0f a5 d2"},
|
||||||
|
{"SHLW CL, DX, R11", "66 41 0f a5 d3"},
|
||||||
|
{"SHLW CL, R11", "66 41 d3 e3"},
|
||||||
|
{"SHLW CL, R11, (BX)", "66 44 0f a5 1b"},
|
||||||
|
{"SHLW CL, R11, (R11)", "66 45 0f a5 1b"},
|
||||||
|
{"SHLW CL, R11, DX", "66 44 0f a5 da"},
|
||||||
|
{"SHLW CL, R11, R11", "66 45 0f a5 db"},
|
||||||
|
{"VCMPPD $7, (BX), X9, X11", "c4 61 31 c2 1b 07|c5 31 c2 1b 07"},
|
||||||
|
{"VCMPPD $7, (BX), X9, X2", "c4 e1 31 c2 13 07|c5 b1 c2 13 07"},
|
||||||
|
{"VCMPPD $7, (BX), Y15, Y11", "c4 61 05 c2 1b 07|c5 05 c2 1b 07"},
|
||||||
|
{"VCMPPD $7, (BX), Y15, Y2", "c4 e1 05 c2 13 07|c5 85 c2 13 07"},
|
||||||
|
{"VCMPPD $7, (R11), X9, X11", "c4 41 31 c2 1b 07"},
|
||||||
|
{"VCMPPD $7, (R11), X9, X2", "c4 c1 31 c2 13 07"},
|
||||||
|
{"VCMPPD $7, (R11), Y15, Y11", "c4 41 05 c2 1b 07"},
|
||||||
|
{"VCMPPD $7, (R11), Y15, Y2", "c4 c1 05 c2 13 07"},
|
||||||
|
{"VCMPPD $7, X11, X9, X11", "c4 41 31 c2 db 07"},
|
||||||
|
{"VCMPPD $7, X11, X9, X2", "c4 c1 31 c2 d3 07"},
|
||||||
|
{"VCMPPD $7, X2, X9, X11", "c4 61 31 c2 da 07|c5 31 c2 da 07"},
|
||||||
|
{"VCMPPD $7, X2, X9, X2", "c4 e1 31 c2 d2 07|c5 b1 c2 d2 07"},
|
||||||
|
{"VCMPPD $7, Y11, Y15, Y11", "c4 41 05 c2 db 07"},
|
||||||
|
{"VCMPPD $7, Y11, Y15, Y2", "c4 c1 05 c2 d3 07"},
|
||||||
|
{"VCMPPD $7, Y2, Y15, Y11", "c4 61 05 c2 da 07|c5 05 c2 da 07"},
|
||||||
|
{"VCMPPD $7, Y2, Y15, Y2", "c4 e1 05 c2 d2 07|c5 85 c2 d2 07"},
|
||||||
|
{"VCMPPS $7, (BX), X9, X11", "c4 61 30 c2 1b 07|c5 30 c2 1b 07"},
|
||||||
|
{"VCMPPS $7, (BX), X9, X2", "c4 e1 30 c2 13 07|c5 b0 c2 13 07"},
|
||||||
|
{"VCMPPS $7, (BX), Y15, Y11", "c4 61 04 c2 1b 07|c5 04 c2 1b 07"},
|
||||||
|
{"VCMPPS $7, (BX), Y15, Y2", "c4 e1 04 c2 13 07|c5 84 c2 13 07"},
|
||||||
|
{"VCMPPS $7, (R11), X9, X11", "c4 41 30 c2 1b 07"},
|
||||||
|
{"VCMPPS $7, (R11), X9, X2", "c4 c1 30 c2 13 07"},
|
||||||
|
{"VCMPPS $7, (R11), Y15, Y11", "c4 41 04 c2 1b 07"},
|
||||||
|
{"VCMPPS $7, (R11), Y15, Y2", "c4 c1 04 c2 13 07"},
|
||||||
|
{"VCMPPS $7, X11, X9, X11", "c4 41 30 c2 db 07"},
|
||||||
|
{"VCMPPS $7, X11, X9, X2", "c4 c1 30 c2 d3 07"},
|
||||||
|
{"VCMPPS $7, X2, X9, X11", "c4 61 30 c2 da 07|c5 30 c2 da 07"},
|
||||||
|
{"VCMPPS $7, X2, X9, X2", "c4 e1 30 c2 d2 07|c5 b0 c2 d2 07"},
|
||||||
|
{"VCMPPS $7, Y11, Y15, Y11", "c4 41 04 c2 db 07"},
|
||||||
|
{"VCMPPS $7, Y11, Y15, Y2", "c4 c1 04 c2 d3 07"},
|
||||||
|
{"VCMPPS $7, Y2, Y15, Y11", "c4 61 04 c2 da 07|c5 04 c2 da 07"},
|
||||||
|
{"VCMPPS $7, Y2, Y15, Y2", "c4 e1 04 c2 d2 07|c5 84 c2 d2 07"},
|
||||||
|
{"VCMPSD $7, (BX), X9, X11", "c4 61 33 c2 1b 07|c5 33 c2 1b 07"},
|
||||||
|
{"VCMPSD $7, (BX), X9, X2", "c4 e1 33 c2 13 07|c5 b3 c2 13 07"},
|
||||||
|
{"VCMPSD $7, (R11), X9, X11", "c4 41 33 c2 1b 07"},
|
||||||
|
{"VCMPSD $7, (R11), X9, X2", "c4 c1 33 c2 13 07"},
|
||||||
|
{"VCMPSD $7, X11, X9, X11", "c4 41 33 c2 db 07"},
|
||||||
|
{"VCMPSD $7, X11, X9, X2", "c4 c1 33 c2 d3 07"},
|
||||||
|
{"VCMPSD $7, X2, X9, X11", "c4 61 33 c2 da 07|c5 33 c2 da 07"},
|
||||||
|
{"VCMPSD $7, X2, X9, X2", "c4 e1 33 c2 d2 07|c5 b3 c2 d2 07"},
|
||||||
|
{"VCMPSS $7, (BX), X9, X11", "c4 61 32 c2 1b 07|c5 32 c2 1b 07"},
|
||||||
|
{"VCMPSS $7, (BX), X9, X2", "c4 e1 32 c2 13 07|c5 b2 c2 13 07"},
|
||||||
|
{"VCMPSS $7, (R11), X9, X11", "c4 41 32 c2 1b 07"},
|
||||||
|
{"VCMPSS $7, (R11), X9, X2", "c4 c1 32 c2 13 07"},
|
||||||
|
{"VCMPSS $7, X11, X9, X11", "c4 41 32 c2 db 07"},
|
||||||
|
{"VCMPSS $7, X11, X9, X2", "c4 c1 32 c2 d3 07"},
|
||||||
|
{"VCMPSS $7, X2, X9, X11", "c4 61 32 c2 da 07|c5 32 c2 da 07"},
|
||||||
|
{"VCMPSS $7, X2, X9, X2", "c4 e1 32 c2 d2 07|c5 b2 c2 d2 07"},
|
||||||
|
{"VCOMISS (BX), X11", "c4 61 78 2f 1b|c5 78 2f 1b"},
|
||||||
|
{"VCOMISS (BX), X2", "c4 e1 78 2f 13|c5 f8 2f 13"},
|
||||||
|
{"VCOMISS (R11), X11", "c4 41 78 2f 1b"},
|
||||||
|
{"VCOMISS (R11), X2", "c4 c1 78 2f 13"},
|
||||||
|
{"VCOMISS X11, X11", "c4 41 78 2f db"},
|
||||||
|
{"VCOMISS X11, X2", "c4 c1 78 2f d3"},
|
||||||
|
{"VCOMISS X2, X11", "c4 61 78 2f da|c5 78 2f da"},
|
||||||
|
{"VCOMISS X2, X2", "c4 e1 78 2f d2|c5 f8 2f d2"},
|
||||||
|
{"VCVTPS2DQ (BX), X11", "c4 61 79 5b 1b|c5 79 5b 1b"},
|
||||||
|
{"VCVTPS2DQ (BX), X2", "c4 e1 79 5b 13|c5 f9 5b 13"},
|
||||||
|
{"VCVTPS2DQ (BX), Y11", "c4 61 7d 5b 1b|c5 7d 5b 1b"},
|
||||||
|
{"VCVTPS2DQ (BX), Y2", "c4 e1 7d 5b 13|c5 fd 5b 13"},
|
||||||
|
{"VCVTPS2DQ (R11), X11", "c4 41 79 5b 1b"},
|
||||||
|
{"VCVTPS2DQ (R11), X2", "c4 c1 79 5b 13"},
|
||||||
|
{"VCVTPS2DQ (R11), Y11", "c4 41 7d 5b 1b"},
|
||||||
|
{"VCVTPS2DQ (R11), Y2", "c4 c1 7d 5b 13"},
|
||||||
|
{"VCVTPS2DQ X11, X11", "c4 41 79 5b db"},
|
||||||
|
{"VCVTPS2DQ X11, X2", "c4 c1 79 5b d3"},
|
||||||
|
{"VCVTPS2DQ X2, X11", "c4 61 79 5b da|c5 79 5b da"},
|
||||||
|
{"VCVTPS2DQ X2, X2", "c4 e1 79 5b d2|c5 f9 5b d2"},
|
||||||
|
{"VCVTPS2DQ Y11, Y11", "c4 41 7d 5b db"},
|
||||||
|
{"VCVTPS2DQ Y11, Y2", "c4 c1 7d 5b d3"},
|
||||||
|
{"VCVTPS2DQ Y2, Y11", "c4 61 7d 5b da|c5 7d 5b da"},
|
||||||
|
{"VCVTPS2DQ Y2, Y2", "c4 e1 7d 5b d2|c5 fd 5b d2"},
|
||||||
|
{"VCVTTPS2DQ (BX), X11", "c4 61 7a 5b 1b|c5 7a 5b 1b"},
|
||||||
|
{"VCVTTPS2DQ (BX), X2", "c4 e1 7a 5b 13|c5 fa 5b 13"},
|
||||||
|
{"VCVTTPS2DQ (BX), Y11", "c4 61 7e 5b 1b|c5 7e 5b 1b"},
|
||||||
|
{"VCVTTPS2DQ (BX), Y2", "c4 e1 7e 5b 13|c5 fe 5b 13"},
|
||||||
|
{"VCVTTPS2DQ (R11), X11", "c4 41 7a 5b 1b"},
|
||||||
|
{"VCVTTPS2DQ (R11), X2", "c4 c1 7a 5b 13"},
|
||||||
|
{"VCVTTPS2DQ (R11), Y11", "c4 41 7e 5b 1b"},
|
||||||
|
{"VCVTTPS2DQ (R11), Y2", "c4 c1 7e 5b 13"},
|
||||||
|
{"VCVTTPS2DQ X11, X11", "c4 41 7a 5b db"},
|
||||||
|
{"VCVTTPS2DQ X11, X2", "c4 c1 7a 5b d3"},
|
||||||
|
{"VCVTTPS2DQ X2, X11", "c4 61 7a 5b da|c5 7a 5b da"},
|
||||||
|
{"VCVTTPS2DQ X2, X2", "c4 e1 7a 5b d2|c5 fa 5b d2"},
|
||||||
|
{"VCVTTPS2DQ Y11, Y11", "c4 41 7e 5b db"},
|
||||||
|
{"VCVTTPS2DQ Y11, Y2", "c4 c1 7e 5b d3"},
|
||||||
|
{"VCVTTPS2DQ Y2, Y11", "c4 61 7e 5b da|c5 7e 5b da"},
|
||||||
|
{"VCVTTPS2DQ Y2, Y2", "c4 e1 7e 5b d2|c5 fe 5b d2"},
|
||||||
|
{"VMOVLHPS X11, X9, X11", "c4 41 30 16 db"},
|
||||||
|
{"VMOVLHPS X11, X9, X2", "c4 c1 30 16 d3"},
|
||||||
|
{"VMOVLHPS X2, X9, X11", "c4 61 30 16 da|c5 30 16 da"},
|
||||||
|
{"VMOVLHPS X2, X9, X2", "c4 e1 30 16 d2|c5 b0 16 d2"},
|
||||||
|
{"VMOVUPS (BX), X11", "c4 61 78 10 1b|c5 78 10 1b"},
|
||||||
|
{"VMOVUPS (BX), X2", "c4 e1 78 10 13|c5 f8 10 13"},
|
||||||
|
{"VMOVUPS (BX), Y11", "c4 61 7c 10 1b|c5 7c 10 1b"},
|
||||||
|
{"VMOVUPS (BX), Y2", "c4 e1 7c 10 13|c5 fc 10 13"},
|
||||||
|
{"VMOVUPS (R11), X11", "c4 41 78 10 1b"},
|
||||||
|
{"VMOVUPS (R11), X2", "c4 c1 78 10 13"},
|
||||||
|
{"VMOVUPS (R11), Y11", "c4 41 7c 10 1b"},
|
||||||
|
{"VMOVUPS (R11), Y2", "c4 c1 7c 10 13"},
|
||||||
|
{"VMOVUPS X11, (BX)", "c4 61 78 11 1b|c5 78 11 1b"},
|
||||||
|
{"VMOVUPS X11, (R11)", "c4 41 78 11 1b"},
|
||||||
|
{"VMOVUPS X11, X11", "c4 41 78 10 db|c4 41 78 11 db"},
|
||||||
|
{"VMOVUPS X11, X2", "c4 c1 78 10 d3|c4 61 78 11 da|c5 78 11 da"},
|
||||||
|
{"VMOVUPS X2, (BX)", "c4 e1 78 11 13|c5 f8 11 13"},
|
||||||
|
{"VMOVUPS X2, (R11)", "c4 c1 78 11 13"},
|
||||||
|
{"VMOVUPS X2, X11", "c4 61 78 10 da|c5 78 10 da|c4 c1 78 11 d3"},
|
||||||
|
{"VMOVUPS X2, X2", "c4 e1 78 10 d2|c5 f8 10 d2|c4 e1 78 11 d2|c5 f8 11 d2"},
|
||||||
|
{"VMOVUPS Y11, (BX)", "c4 61 7c 11 1b|c5 7c 11 1b"},
|
||||||
|
{"VMOVUPS Y11, (R11)", "c4 41 7c 11 1b"},
|
||||||
|
{"VMOVUPS Y11, Y11", "c4 41 7c 10 db|c4 41 7c 11 db"},
|
||||||
|
{"VMOVUPS Y11, Y2", "c4 c1 7c 10 d3|c4 61 7c 11 da|c5 7c 11 da"},
|
||||||
|
{"VMOVUPS Y2, (BX)", "c4 e1 7c 11 13|c5 fc 11 13"},
|
||||||
|
{"VMOVUPS Y2, (R11)", "c4 c1 7c 11 13"},
|
||||||
|
{"VMOVUPS Y2, Y11", "c4 61 7c 10 da|c5 7c 10 da|c4 c1 7c 11 d3"},
|
||||||
|
{"VMOVUPS Y2, Y2", "c4 e1 7c 10 d2|c5 fc 10 d2|c4 e1 7c 11 d2|c5 fc 11 d2"},
|
||||||
|
{"VPABSB (BX), X11", "c4 62 79 1c 1b"},
|
||||||
|
{"VPABSB (BX), X2", "c4 e2 79 1c 13"},
|
||||||
|
{"VPABSB (BX), Y11", "c4 62 7d 1c 1b"},
|
||||||
|
{"VPABSB (BX), Y2", "c4 e2 7d 1c 13"},
|
||||||
|
{"VPABSB (R11), X11", "c4 42 79 1c 1b"},
|
||||||
|
{"VPABSB (R11), X2", "c4 c2 79 1c 13"},
|
||||||
|
{"VPABSB (R11), Y11", "c4 42 7d 1c 1b"},
|
||||||
|
{"VPABSB (R11), Y2", "c4 c2 7d 1c 13"},
|
||||||
|
{"VPABSB X11, X11", "c4 42 79 1c db"},
|
||||||
|
{"VPABSB X11, X2", "c4 c2 79 1c d3"},
|
||||||
|
{"VPABSB X2, X11", "c4 62 79 1c da"},
|
||||||
|
{"VPABSB X2, X2", "c4 e2 79 1c d2"},
|
||||||
|
{"VPABSB Y11, Y11", "c4 42 7d 1c db"},
|
||||||
|
{"VPABSB Y11, Y2", "c4 c2 7d 1c d3"},
|
||||||
|
{"VPABSB Y2, Y11", "c4 62 7d 1c da"},
|
||||||
|
{"VPABSB Y2, Y2", "c4 e2 7d 1c d2"},
|
||||||
|
{"VPABSD (BX), X11", "c4 62 79 1e 1b"},
|
||||||
|
{"VPABSD (BX), X2", "c4 e2 79 1e 13"},
|
||||||
|
{"VPABSD (BX), Y11", "c4 62 7d 1e 1b"},
|
||||||
|
{"VPABSD (BX), Y2", "c4 e2 7d 1e 13"},
|
||||||
|
{"VPABSD (R11), X11", "c4 42 79 1e 1b"},
|
||||||
|
{"VPABSD (R11), X2", "c4 c2 79 1e 13"},
|
||||||
|
{"VPABSD (R11), Y11", "c4 42 7d 1e 1b"},
|
||||||
|
{"VPABSD (R11), Y2", "c4 c2 7d 1e 13"},
|
||||||
|
{"VPABSD X11, X11", "c4 42 79 1e db"},
|
||||||
|
{"VPABSD X11, X2", "c4 c2 79 1e d3"},
|
||||||
|
{"VPABSD X2, X11", "c4 62 79 1e da"},
|
||||||
|
{"VPABSD X2, X2", "c4 e2 79 1e d2"},
|
||||||
|
{"VPABSD Y11, Y11", "c4 42 7d 1e db"},
|
||||||
|
{"VPABSD Y11, Y2", "c4 c2 7d 1e d3"},
|
||||||
|
{"VPABSD Y2, Y11", "c4 62 7d 1e da"},
|
||||||
|
{"VPABSD Y2, Y2", "c4 e2 7d 1e d2"},
|
||||||
|
{"VPABSW (BX), X11", "c4 62 79 1d 1b"},
|
||||||
|
{"VPABSW (BX), X2", "c4 e2 79 1d 13"},
|
||||||
|
{"VPABSW (BX), Y11", "c4 62 7d 1d 1b"},
|
||||||
|
{"VPABSW (BX), Y2", "c4 e2 7d 1d 13"},
|
||||||
|
{"VPABSW (R11), X11", "c4 42 79 1d 1b"},
|
||||||
|
{"VPABSW (R11), X2", "c4 c2 79 1d 13"},
|
||||||
|
{"VPABSW (R11), Y11", "c4 42 7d 1d 1b"},
|
||||||
|
{"VPABSW (R11), Y2", "c4 c2 7d 1d 13"},
|
||||||
|
{"VPABSW X11, X11", "c4 42 79 1d db"},
|
||||||
|
{"VPABSW X11, X2", "c4 c2 79 1d d3"},
|
||||||
|
{"VPABSW X2, X11", "c4 62 79 1d da"},
|
||||||
|
{"VPABSW X2, X2", "c4 e2 79 1d d2"},
|
||||||
|
{"VPABSW Y11, Y11", "c4 42 7d 1d db"},
|
||||||
|
{"VPABSW Y11, Y2", "c4 c2 7d 1d d3"},
|
||||||
|
{"VPABSW Y2, Y11", "c4 62 7d 1d da"},
|
||||||
|
{"VPABSW Y2, Y2", "c4 e2 7d 1d d2"},
|
||||||
|
{"VPACKSSWB (BX), X9, X11", "c4 61 31 63 1b|c5 31 63 1b"},
|
||||||
|
{"VPACKSSWB (BX), X9, X2", "c4 e1 31 63 13|c5 b1 63 13"},
|
||||||
|
{"VPACKSSWB (BX), Y15, Y11", "c4 61 05 63 1b|c5 05 63 1b"},
|
||||||
|
{"VPACKSSWB (BX), Y15, Y2", "c4 e1 05 63 13|c5 85 63 13"},
|
||||||
|
{"VPACKSSWB (R11), X9, X11", "c4 41 31 63 1b"},
|
||||||
|
{"VPACKSSWB (R11), X9, X2", "c4 c1 31 63 13"},
|
||||||
|
{"VPACKSSWB (R11), Y15, Y11", "c4 41 05 63 1b"},
|
||||||
|
{"VPACKSSWB (R11), Y15, Y2", "c4 c1 05 63 13"},
|
||||||
|
{"VPACKSSWB X11, X9, X11", "c4 41 31 63 db"},
|
||||||
|
{"VPACKSSWB X11, X9, X2", "c4 c1 31 63 d3"},
|
||||||
|
{"VPACKSSWB X2, X9, X11", "c4 61 31 63 da|c5 31 63 da"},
|
||||||
|
{"VPACKSSWB X2, X9, X2", "c4 e1 31 63 d2|c5 b1 63 d2"},
|
||||||
|
{"VPACKSSWB Y11, Y15, Y11", "c4 41 05 63 db"},
|
||||||
|
{"VPACKSSWB Y11, Y15, Y2", "c4 c1 05 63 d3"},
|
||||||
|
{"VPACKSSWB Y2, Y15, Y11", "c4 61 05 63 da|c5 05 63 da"},
|
||||||
|
{"VPACKSSWB Y2, Y15, Y2", "c4 e1 05 63 d2|c5 85 63 d2"},
|
||||||
|
{"VPACKUSDW (BX), X9, X11", "c4 62 31 2b 1b"},
|
||||||
|
{"VPACKUSDW (BX), X9, X2", "c4 e2 31 2b 13"},
|
||||||
|
{"VPACKUSDW (BX), Y15, Y11", "c4 62 05 2b 1b"},
|
||||||
|
{"VPACKUSDW (BX), Y15, Y2", "c4 e2 05 2b 13"},
|
||||||
|
{"VPACKUSDW (R11), X9, X11", "c4 42 31 2b 1b"},
|
||||||
|
{"VPACKUSDW (R11), X9, X2", "c4 c2 31 2b 13"},
|
||||||
|
{"VPACKUSDW (R11), Y15, Y11", "c4 42 05 2b 1b"},
|
||||||
|
{"VPACKUSDW (R11), Y15, Y2", "c4 c2 05 2b 13"},
|
||||||
|
{"VPACKUSDW X11, X9, X11", "c4 42 31 2b db"},
|
||||||
|
{"VPACKUSDW X11, X9, X2", "c4 c2 31 2b d3"},
|
||||||
|
{"VPACKUSDW X2, X9, X11", "c4 62 31 2b da"},
|
||||||
|
{"VPACKUSDW X2, X9, X2", "c4 e2 31 2b d2"},
|
||||||
|
{"VPACKUSDW Y11, Y15, Y11", "c4 42 05 2b db"},
|
||||||
|
{"VPACKUSDW Y11, Y15, Y2", "c4 c2 05 2b d3"},
|
||||||
|
{"VPACKUSDW Y2, Y15, Y11", "c4 62 05 2b da"},
|
||||||
|
{"VPACKUSDW Y2, Y15, Y2", "c4 e2 05 2b d2"},
|
||||||
|
{"VPACKUSWB (BX), X9, X11", "c4 61 31 67 1b|c5 31 67 1b"},
|
||||||
|
{"VPACKUSWB (BX), X9, X2", "c4 e1 31 67 13|c5 b1 67 13"},
|
||||||
|
{"VPACKUSWB (BX), Y15, Y11", "c4 61 05 67 1b|c5 05 67 1b"},
|
||||||
|
{"VPACKUSWB (BX), Y15, Y2", "c4 e1 05 67 13|c5 85 67 13"},
|
||||||
|
{"VPACKUSWB (R11), X9, X11", "c4 41 31 67 1b"},
|
||||||
|
{"VPACKUSWB (R11), X9, X2", "c4 c1 31 67 13"},
|
||||||
|
{"VPACKUSWB (R11), Y15, Y11", "c4 41 05 67 1b"},
|
||||||
|
{"VPACKUSWB (R11), Y15, Y2", "c4 c1 05 67 13"},
|
||||||
|
{"VPACKUSWB X11, X9, X11", "c4 41 31 67 db"},
|
||||||
|
{"VPACKUSWB X11, X9, X2", "c4 c1 31 67 d3"},
|
||||||
|
{"VPACKUSWB X2, X9, X11", "c4 61 31 67 da|c5 31 67 da"},
|
||||||
|
{"VPACKUSWB X2, X9, X2", "c4 e1 31 67 d2|c5 b1 67 d2"},
|
||||||
|
{"VPACKUSWB Y11, Y15, Y11", "c4 41 05 67 db"},
|
||||||
|
{"VPACKUSWB Y11, Y15, Y2", "c4 c1 05 67 d3"},
|
||||||
|
{"VPACKUSWB Y2, Y15, Y11", "c4 61 05 67 da|c5 05 67 da"},
|
||||||
|
{"VPACKUSWB Y2, Y15, Y2", "c4 e1 05 67 d2|c5 85 67 d2"},
|
||||||
|
{"VPADDB (BX), X9, X11", "c4 61 31 fc 1b|c5 31 fc 1b"},
|
||||||
|
{"VPADDB (BX), X9, X2", "c4 e1 31 fc 13|c5 b1 fc 13"},
|
||||||
|
{"VPADDB (BX), Y15, Y11", "c4 61 05 fc 1b|c5 05 fc 1b"},
|
||||||
|
{"VPADDB (BX), Y15, Y2", "c4 e1 05 fc 13|c5 85 fc 13"},
|
||||||
|
{"VPADDB (R11), X9, X11", "c4 41 31 fc 1b"},
|
||||||
|
{"VPADDB (R11), X9, X2", "c4 c1 31 fc 13"},
|
||||||
|
{"VPADDB (R11), Y15, Y11", "c4 41 05 fc 1b"},
|
||||||
|
{"VPADDB (R11), Y15, Y2", "c4 c1 05 fc 13"},
|
||||||
|
{"VPADDB X11, X9, X11", "c4 41 31 fc db"},
|
||||||
|
{"VPADDB X11, X9, X2", "c4 c1 31 fc d3"},
|
||||||
|
{"VPADDB X2, X9, X11", "c4 61 31 fc da|c5 31 fc da"},
|
||||||
|
{"VPADDB X2, X9, X2", "c4 e1 31 fc d2|c5 b1 fc d2"},
|
||||||
|
{"VPADDB Y11, Y15, Y11", "c4 41 05 fc db"},
|
||||||
|
{"VPADDB Y11, Y15, Y2", "c4 c1 05 fc d3"},
|
||||||
|
{"VPADDB Y2, Y15, Y11", "c4 61 05 fc da|c5 05 fc da"},
|
||||||
|
{"VPADDB Y2, Y15, Y2", "c4 e1 05 fc d2|c5 85 fc d2"},
|
||||||
|
{"VPADDW (BX), X9, X11", "c4 61 31 fd 1b|c5 31 fd 1b"},
|
||||||
|
{"VPADDW (BX), X9, X2", "c4 e1 31 fd 13|c5 b1 fd 13"},
|
||||||
|
{"VPADDW (BX), Y15, Y11", "c4 61 05 fd 1b|c5 05 fd 1b"},
|
||||||
|
{"VPADDW (BX), Y15, Y2", "c4 e1 05 fd 13|c5 85 fd 13"},
|
||||||
|
{"VPADDW (R11), X9, X11", "c4 41 31 fd 1b"},
|
||||||
|
{"VPADDW (R11), X9, X2", "c4 c1 31 fd 13"},
|
||||||
|
{"VPADDW (R11), Y15, Y11", "c4 41 05 fd 1b"},
|
||||||
|
{"VPADDW (R11), Y15, Y2", "c4 c1 05 fd 13"},
|
||||||
|
{"VPADDW X11, X9, X11", "c4 41 31 fd db"},
|
||||||
|
{"VPADDW X11, X9, X2", "c4 c1 31 fd d3"},
|
||||||
|
{"VPADDW X2, X9, X11", "c4 61 31 fd da|c5 31 fd da"},
|
||||||
|
{"VPADDW X2, X9, X2", "c4 e1 31 fd d2|c5 b1 fd d2"},
|
||||||
|
{"VPADDW Y11, Y15, Y11", "c4 41 05 fd db"},
|
||||||
|
{"VPADDW Y11, Y15, Y2", "c4 c1 05 fd d3"},
|
||||||
|
{"VPADDW Y2, Y15, Y11", "c4 61 05 fd da|c5 05 fd da"},
|
||||||
|
{"VPADDW Y2, Y15, Y2", "c4 e1 05 fd d2|c5 85 fd d2"},
|
||||||
|
{"VPAVGB (BX), X9, X11", "c4 61 31 e0 1b|c5 31 e0 1b"},
|
||||||
|
{"VPAVGB (BX), X9, X2", "c4 e1 31 e0 13|c5 b1 e0 13"},
|
||||||
|
{"VPAVGB (BX), Y15, Y11", "c4 61 05 e0 1b|c5 05 e0 1b"},
|
||||||
|
{"VPAVGB (BX), Y15, Y2", "c4 e1 05 e0 13|c5 85 e0 13"},
|
||||||
|
{"VPAVGB (R11), X9, X11", "c4 41 31 e0 1b"},
|
||||||
|
{"VPAVGB (R11), X9, X2", "c4 c1 31 e0 13"},
|
||||||
|
{"VPAVGB (R11), Y15, Y11", "c4 41 05 e0 1b"},
|
||||||
|
{"VPAVGB (R11), Y15, Y2", "c4 c1 05 e0 13"},
|
||||||
|
{"VPAVGB X11, X9, X11", "c4 41 31 e0 db"},
|
||||||
|
{"VPAVGB X11, X9, X2", "c4 c1 31 e0 d3"},
|
||||||
|
{"VPAVGB X2, X9, X11", "c4 61 31 e0 da|c5 31 e0 da"},
|
||||||
|
{"VPAVGB X2, X9, X2", "c4 e1 31 e0 d2|c5 b1 e0 d2"},
|
||||||
|
{"VPAVGB Y11, Y15, Y11", "c4 41 05 e0 db"},
|
||||||
|
{"VPAVGB Y11, Y15, Y2", "c4 c1 05 e0 d3"},
|
||||||
|
{"VPAVGB Y2, Y15, Y11", "c4 61 05 e0 da|c5 05 e0 da"},
|
||||||
|
{"VPAVGB Y2, Y15, Y2", "c4 e1 05 e0 d2|c5 85 e0 d2"},
|
||||||
|
{"VPAVGW (BX), X9, X11", "c4 61 31 e3 1b|c5 31 e3 1b"},
|
||||||
|
{"VPAVGW (BX), X9, X2", "c4 e1 31 e3 13|c5 b1 e3 13"},
|
||||||
|
{"VPAVGW (BX), Y15, Y11", "c4 61 05 e3 1b|c5 05 e3 1b"},
|
||||||
|
{"VPAVGW (BX), Y15, Y2", "c4 e1 05 e3 13|c5 85 e3 13"},
|
||||||
|
{"VPAVGW (R11), X9, X11", "c4 41 31 e3 1b"},
|
||||||
|
{"VPAVGW (R11), X9, X2", "c4 c1 31 e3 13"},
|
||||||
|
{"VPAVGW (R11), Y15, Y11", "c4 41 05 e3 1b"},
|
||||||
|
{"VPAVGW (R11), Y15, Y2", "c4 c1 05 e3 13"},
|
||||||
|
{"VPAVGW X11, X9, X11", "c4 41 31 e3 db"},
|
||||||
|
{"VPAVGW X11, X9, X2", "c4 c1 31 e3 d3"},
|
||||||
|
{"VPAVGW X2, X9, X11", "c4 61 31 e3 da|c5 31 e3 da"},
|
||||||
|
{"VPAVGW X2, X9, X2", "c4 e1 31 e3 d2|c5 b1 e3 d2"},
|
||||||
|
{"VPAVGW Y11, Y15, Y11", "c4 41 05 e3 db"},
|
||||||
|
{"VPAVGW Y11, Y15, Y2", "c4 c1 05 e3 d3"},
|
||||||
|
{"VPAVGW Y2, Y15, Y11", "c4 61 05 e3 da|c5 05 e3 da"},
|
||||||
|
{"VPAVGW Y2, Y15, Y2", "c4 e1 05 e3 d2|c5 85 e3 d2"},
|
||||||
|
{"VPMADDUBSW (BX), X9, X11", "c4 62 31 04 1b"},
|
||||||
|
{"VPMADDUBSW (BX), X9, X2", "c4 e2 31 04 13"},
|
||||||
|
{"VPMADDUBSW (BX), Y15, Y11", "c4 62 05 04 1b"},
|
||||||
|
{"VPMADDUBSW (BX), Y15, Y2", "c4 e2 05 04 13"},
|
||||||
|
{"VPMADDUBSW (R11), X9, X11", "c4 42 31 04 1b"},
|
||||||
|
{"VPMADDUBSW (R11), X9, X2", "c4 c2 31 04 13"},
|
||||||
|
{"VPMADDUBSW (R11), Y15, Y11", "c4 42 05 04 1b"},
|
||||||
|
{"VPMADDUBSW (R11), Y15, Y2", "c4 c2 05 04 13"},
|
||||||
|
{"VPMADDUBSW X11, X9, X11", "c4 42 31 04 db"},
|
||||||
|
{"VPMADDUBSW X11, X9, X2", "c4 c2 31 04 d3"},
|
||||||
|
{"VPMADDUBSW X2, X9, X11", "c4 62 31 04 da"},
|
||||||
|
{"VPMADDUBSW X2, X9, X2", "c4 e2 31 04 d2"},
|
||||||
|
{"VPMADDUBSW Y11, Y15, Y11", "c4 42 05 04 db"},
|
||||||
|
{"VPMADDUBSW Y11, Y15, Y2", "c4 c2 05 04 d3"},
|
||||||
|
{"VPMADDUBSW Y2, Y15, Y11", "c4 62 05 04 da"},
|
||||||
|
{"VPMADDUBSW Y2, Y15, Y2", "c4 e2 05 04 d2"},
|
||||||
|
{"VPMADDWD (BX), X9, X11", "c4 61 31 f5 1b|c5 31 f5 1b"},
|
||||||
|
{"VPMADDWD (BX), X9, X2", "c4 e1 31 f5 13|c5 b1 f5 13"},
|
||||||
|
{"VPMADDWD (BX), Y15, Y11", "c4 61 05 f5 1b|c5 05 f5 1b"},
|
||||||
|
{"VPMADDWD (BX), Y15, Y2", "c4 e1 05 f5 13|c5 85 f5 13"},
|
||||||
|
{"VPMADDWD (R11), X9, X11", "c4 41 31 f5 1b"},
|
||||||
|
{"VPMADDWD (R11), X9, X2", "c4 c1 31 f5 13"},
|
||||||
|
{"VPMADDWD (R11), Y15, Y11", "c4 41 05 f5 1b"},
|
||||||
|
{"VPMADDWD (R11), Y15, Y2", "c4 c1 05 f5 13"},
|
||||||
|
{"VPMADDWD X11, X9, X11", "c4 41 31 f5 db"},
|
||||||
|
{"VPMADDWD X11, X9, X2", "c4 c1 31 f5 d3"},
|
||||||
|
{"VPMADDWD X2, X9, X11", "c4 61 31 f5 da|c5 31 f5 da"},
|
||||||
|
{"VPMADDWD X2, X9, X2", "c4 e1 31 f5 d2|c5 b1 f5 d2"},
|
||||||
|
{"VPMADDWD Y11, Y15, Y11", "c4 41 05 f5 db"},
|
||||||
|
{"VPMADDWD Y11, Y15, Y2", "c4 c1 05 f5 d3"},
|
||||||
|
{"VPMADDWD Y2, Y15, Y11", "c4 61 05 f5 da|c5 05 f5 da"},
|
||||||
|
{"VPMADDWD Y2, Y15, Y2", "c4 e1 05 f5 d2|c5 85 f5 d2"},
|
||||||
|
{"VPMAXSB (BX), X9, X11", "c4 62 31 3c 1b"},
|
||||||
|
{"VPMAXSB (BX), X9, X2", "c4 e2 31 3c 13"},
|
||||||
|
{"VPMAXSB (BX), Y15, Y11", "c4 62 05 3c 1b"},
|
||||||
|
{"VPMAXSB (BX), Y15, Y2", "c4 e2 05 3c 13"},
|
||||||
|
{"VPMAXSB (R11), X9, X11", "c4 42 31 3c 1b"},
|
||||||
|
{"VPMAXSB (R11), X9, X2", "c4 c2 31 3c 13"},
|
||||||
|
{"VPMAXSB (R11), Y15, Y11", "c4 42 05 3c 1b"},
|
||||||
|
{"VPMAXSB (R11), Y15, Y2", "c4 c2 05 3c 13"},
|
||||||
|
{"VPMAXSB X11, X9, X11", "c4 42 31 3c db"},
|
||||||
|
{"VPMAXSB X11, X9, X2", "c4 c2 31 3c d3"},
|
||||||
|
{"VPMAXSB X2, X9, X11", "c4 62 31 3c da"},
|
||||||
|
{"VPMAXSB X2, X9, X2", "c4 e2 31 3c d2"},
|
||||||
|
{"VPMAXSB Y11, Y15, Y11", "c4 42 05 3c db"},
|
||||||
|
{"VPMAXSB Y11, Y15, Y2", "c4 c2 05 3c d3"},
|
||||||
|
{"VPMAXSB Y2, Y15, Y11", "c4 62 05 3c da"},
|
||||||
|
{"VPMAXSB Y2, Y15, Y2", "c4 e2 05 3c d2"},
|
||||||
|
{"VPMAXSD (BX), X9, X11", "c4 62 31 3d 1b"},
|
||||||
|
{"VPMAXSD (BX), X9, X2", "c4 e2 31 3d 13"},
|
||||||
|
{"VPMAXSD (BX), Y15, Y11", "c4 62 05 3d 1b"},
|
||||||
|
{"VPMAXSD (BX), Y15, Y2", "c4 e2 05 3d 13"},
|
||||||
|
{"VPMAXSD (R11), X9, X11", "c4 42 31 3d 1b"},
|
||||||
|
{"VPMAXSD (R11), X9, X2", "c4 c2 31 3d 13"},
|
||||||
|
{"VPMAXSD (R11), Y15, Y11", "c4 42 05 3d 1b"},
|
||||||
|
{"VPMAXSD (R11), Y15, Y2", "c4 c2 05 3d 13"},
|
||||||
|
{"VPMAXSD X11, X9, X11", "c4 42 31 3d db"},
|
||||||
|
{"VPMAXSD X11, X9, X2", "c4 c2 31 3d d3"},
|
||||||
|
{"VPMAXSD X2, X9, X11", "c4 62 31 3d da"},
|
||||||
|
{"VPMAXSD X2, X9, X2", "c4 e2 31 3d d2"},
|
||||||
|
{"VPMAXSD Y11, Y15, Y11", "c4 42 05 3d db"},
|
||||||
|
{"VPMAXSD Y11, Y15, Y2", "c4 c2 05 3d d3"},
|
||||||
|
{"VPMAXSD Y2, Y15, Y11", "c4 62 05 3d da"},
|
||||||
|
{"VPMAXSD Y2, Y15, Y2", "c4 e2 05 3d d2"},
|
||||||
|
{"VPMAXSW (BX), X9, X11", "c4 61 31 ee 1b|c5 31 ee 1b"},
|
||||||
|
{"VPMAXSW (BX), X9, X2", "c4 e1 31 ee 13|c5 b1 ee 13"},
|
||||||
|
{"VPMAXSW (BX), Y15, Y11", "c4 61 05 ee 1b|c5 05 ee 1b"},
|
||||||
|
{"VPMAXSW (BX), Y15, Y2", "c4 e1 05 ee 13|c5 85 ee 13"},
|
||||||
|
{"VPMAXSW (R11), X9, X11", "c4 41 31 ee 1b"},
|
||||||
|
{"VPMAXSW (R11), X9, X2", "c4 c1 31 ee 13"},
|
||||||
|
{"VPMAXSW (R11), Y15, Y11", "c4 41 05 ee 1b"},
|
||||||
|
{"VPMAXSW (R11), Y15, Y2", "c4 c1 05 ee 13"},
|
||||||
|
{"VPMAXSW X11, X9, X11", "c4 41 31 ee db"},
|
||||||
|
{"VPMAXSW X11, X9, X2", "c4 c1 31 ee d3"},
|
||||||
|
{"VPMAXSW X2, X9, X11", "c4 61 31 ee da|c5 31 ee da"},
|
||||||
|
{"VPMAXSW X2, X9, X2", "c4 e1 31 ee d2|c5 b1 ee d2"},
|
||||||
|
{"VPMAXSW Y11, Y15, Y11", "c4 41 05 ee db"},
|
||||||
|
{"VPMAXSW Y11, Y15, Y2", "c4 c1 05 ee d3"},
|
||||||
|
{"VPMAXSW Y2, Y15, Y11", "c4 61 05 ee da|c5 05 ee da"},
|
||||||
|
{"VPMAXSW Y2, Y15, Y2", "c4 e1 05 ee d2|c5 85 ee d2"},
|
||||||
|
{"VPMAXUB (BX), X9, X11", "c4 61 31 de 1b|c5 31 de 1b"},
|
||||||
|
{"VPMAXUB (BX), X9, X2", "c4 e1 31 de 13|c5 b1 de 13"},
|
||||||
|
{"VPMAXUB (BX), Y15, Y11", "c4 61 05 de 1b|c5 05 de 1b"},
|
||||||
|
{"VPMAXUB (BX), Y15, Y2", "c4 e1 05 de 13|c5 85 de 13"},
|
||||||
|
{"VPMAXUB (R11), X9, X11", "c4 41 31 de 1b"},
|
||||||
|
{"VPMAXUB (R11), X9, X2", "c4 c1 31 de 13"},
|
||||||
|
{"VPMAXUB (R11), Y15, Y11", "c4 41 05 de 1b"},
|
||||||
|
{"VPMAXUB (R11), Y15, Y2", "c4 c1 05 de 13"},
|
||||||
|
{"VPMAXUB X11, X9, X11", "c4 41 31 de db"},
|
||||||
|
{"VPMAXUB X11, X9, X2", "c4 c1 31 de d3"},
|
||||||
|
{"VPMAXUB X2, X9, X11", "c4 61 31 de da|c5 31 de da"},
|
||||||
|
{"VPMAXUB X2, X9, X2", "c4 e1 31 de d2|c5 b1 de d2"},
|
||||||
|
{"VPMAXUB Y11, Y15, Y11", "c4 41 05 de db"},
|
||||||
|
{"VPMAXUB Y11, Y15, Y2", "c4 c1 05 de d3"},
|
||||||
|
{"VPMAXUB Y2, Y15, Y11", "c4 61 05 de da|c5 05 de da"},
|
||||||
|
{"VPMAXUB Y2, Y15, Y2", "c4 e1 05 de d2|c5 85 de d2"},
|
||||||
|
{"VPMAXUD (BX), X9, X11", "c4 62 31 3f 1b"},
|
||||||
|
{"VPMAXUD (BX), X9, X2", "c4 e2 31 3f 13"},
|
||||||
|
{"VPMAXUD (BX), Y15, Y11", "c4 62 05 3f 1b"},
|
||||||
|
{"VPMAXUD (BX), Y15, Y2", "c4 e2 05 3f 13"},
|
||||||
|
{"VPMAXUD (R11), X9, X11", "c4 42 31 3f 1b"},
|
||||||
|
{"VPMAXUD (R11), X9, X2", "c4 c2 31 3f 13"},
|
||||||
|
{"VPMAXUD (R11), Y15, Y11", "c4 42 05 3f 1b"},
|
||||||
|
{"VPMAXUD (R11), Y15, Y2", "c4 c2 05 3f 13"},
|
||||||
|
{"VPMAXUD X11, X9, X11", "c4 42 31 3f db"},
|
||||||
|
{"VPMAXUD X11, X9, X2", "c4 c2 31 3f d3"},
|
||||||
|
{"VPMAXUD X2, X9, X11", "c4 62 31 3f da"},
|
||||||
|
{"VPMAXUD X2, X9, X2", "c4 e2 31 3f d2"},
|
||||||
|
{"VPMAXUD Y11, Y15, Y11", "c4 42 05 3f db"},
|
||||||
|
{"VPMAXUD Y11, Y15, Y2", "c4 c2 05 3f d3"},
|
||||||
|
{"VPMAXUD Y2, Y15, Y11", "c4 62 05 3f da"},
|
||||||
|
{"VPMAXUD Y2, Y15, Y2", "c4 e2 05 3f d2"},
|
||||||
|
{"VPMAXUW (BX), X9, X11", "c4 62 31 3e 1b"},
|
||||||
|
{"VPMAXUW (BX), X9, X2", "c4 e2 31 3e 13"},
|
||||||
|
{"VPMAXUW (BX), Y15, Y11", "c4 62 05 3e 1b"},
|
||||||
|
{"VPMAXUW (BX), Y15, Y2", "c4 e2 05 3e 13"},
|
||||||
|
{"VPMAXUW (R11), X9, X11", "c4 42 31 3e 1b"},
|
||||||
|
{"VPMAXUW (R11), X9, X2", "c4 c2 31 3e 13"},
|
||||||
|
{"VPMAXUW (R11), Y15, Y11", "c4 42 05 3e 1b"},
|
||||||
|
{"VPMAXUW (R11), Y15, Y2", "c4 c2 05 3e 13"},
|
||||||
|
{"VPMAXUW X11, X9, X11", "c4 42 31 3e db"},
|
||||||
|
{"VPMAXUW X11, X9, X2", "c4 c2 31 3e d3"},
|
||||||
|
{"VPMAXUW X2, X9, X11", "c4 62 31 3e da"},
|
||||||
|
{"VPMAXUW X2, X9, X2", "c4 e2 31 3e d2"},
|
||||||
|
{"VPMAXUW Y11, Y15, Y11", "c4 42 05 3e db"},
|
||||||
|
{"VPMAXUW Y11, Y15, Y2", "c4 c2 05 3e d3"},
|
||||||
|
{"VPMAXUW Y2, Y15, Y11", "c4 62 05 3e da"},
|
||||||
|
{"VPMAXUW Y2, Y15, Y2", "c4 e2 05 3e d2"},
|
||||||
|
{"VPMINSB (BX), X9, X11", "c4 62 31 38 1b"},
|
||||||
|
{"VPMINSB (BX), X9, X2", "c4 e2 31 38 13"},
|
||||||
|
{"VPMINSB (BX), Y15, Y11", "c4 62 05 38 1b"},
|
||||||
|
{"VPMINSB (BX), Y15, Y2", "c4 e2 05 38 13"},
|
||||||
|
{"VPMINSB (R11), X9, X11", "c4 42 31 38 1b"},
|
||||||
|
{"VPMINSB (R11), X9, X2", "c4 c2 31 38 13"},
|
||||||
|
{"VPMINSB (R11), Y15, Y11", "c4 42 05 38 1b"},
|
||||||
|
{"VPMINSB (R11), Y15, Y2", "c4 c2 05 38 13"},
|
||||||
|
{"VPMINSB X11, X9, X11", "c4 42 31 38 db"},
|
||||||
|
{"VPMINSB X11, X9, X2", "c4 c2 31 38 d3"},
|
||||||
|
{"VPMINSB X2, X9, X11", "c4 62 31 38 da"},
|
||||||
|
{"VPMINSB X2, X9, X2", "c4 e2 31 38 d2"},
|
||||||
|
{"VPMINSB Y11, Y15, Y11", "c4 42 05 38 db"},
|
||||||
|
{"VPMINSB Y11, Y15, Y2", "c4 c2 05 38 d3"},
|
||||||
|
{"VPMINSB Y2, Y15, Y11", "c4 62 05 38 da"},
|
||||||
|
{"VPMINSB Y2, Y15, Y2", "c4 e2 05 38 d2"},
|
||||||
|
{"VPMINSD (BX), X9, X11", "c4 62 31 39 1b"},
|
||||||
|
{"VPMINSD (BX), X9, X2", "c4 e2 31 39 13"},
|
||||||
|
{"VPMINSD (BX), Y15, Y11", "c4 62 05 39 1b"},
|
||||||
|
{"VPMINSD (BX), Y15, Y2", "c4 e2 05 39 13"},
|
||||||
|
{"VPMINSD (R11), X9, X11", "c4 42 31 39 1b"},
|
||||||
|
{"VPMINSD (R11), X9, X2", "c4 c2 31 39 13"},
|
||||||
|
{"VPMINSD (R11), Y15, Y11", "c4 42 05 39 1b"},
|
||||||
|
{"VPMINSD (R11), Y15, Y2", "c4 c2 05 39 13"},
|
||||||
|
{"VPMINSD X11, X9, X11", "c4 42 31 39 db"},
|
||||||
|
{"VPMINSD X11, X9, X2", "c4 c2 31 39 d3"},
|
||||||
|
{"VPMINSD X2, X9, X11", "c4 62 31 39 da"},
|
||||||
|
{"VPMINSD X2, X9, X2", "c4 e2 31 39 d2"},
|
||||||
|
{"VPMINSD Y11, Y15, Y11", "c4 42 05 39 db"},
|
||||||
|
{"VPMINSD Y11, Y15, Y2", "c4 c2 05 39 d3"},
|
||||||
|
{"VPMINSD Y2, Y15, Y11", "c4 62 05 39 da"},
|
||||||
|
{"VPMINSD Y2, Y15, Y2", "c4 e2 05 39 d2"},
|
||||||
|
{"VPMINSW (BX), X9, X11", "c4 61 31 ea 1b|c5 31 ea 1b"},
|
||||||
|
{"VPMINSW (BX), X9, X2", "c4 e1 31 ea 13|c5 b1 ea 13"},
|
||||||
|
{"VPMINSW (BX), Y15, Y11", "c4 61 05 ea 1b|c5 05 ea 1b"},
|
||||||
|
{"VPMINSW (BX), Y15, Y2", "c4 e1 05 ea 13|c5 85 ea 13"},
|
||||||
|
{"VPMINSW (R11), X9, X11", "c4 41 31 ea 1b"},
|
||||||
|
{"VPMINSW (R11), X9, X2", "c4 c1 31 ea 13"},
|
||||||
|
{"VPMINSW (R11), Y15, Y11", "c4 41 05 ea 1b"},
|
||||||
|
{"VPMINSW (R11), Y15, Y2", "c4 c1 05 ea 13"},
|
||||||
|
{"VPMINSW X11, X9, X11", "c4 41 31 ea db"},
|
||||||
|
{"VPMINSW X11, X9, X2", "c4 c1 31 ea d3"},
|
||||||
|
{"VPMINSW X2, X9, X11", "c4 61 31 ea da|c5 31 ea da"},
|
||||||
|
{"VPMINSW X2, X9, X2", "c4 e1 31 ea d2|c5 b1 ea d2"},
|
||||||
|
{"VPMINSW Y11, Y15, Y11", "c4 41 05 ea db"},
|
||||||
|
{"VPMINSW Y11, Y15, Y2", "c4 c1 05 ea d3"},
|
||||||
|
{"VPMINSW Y2, Y15, Y11", "c4 61 05 ea da|c5 05 ea da"},
|
||||||
|
{"VPMINSW Y2, Y15, Y2", "c4 e1 05 ea d2|c5 85 ea d2"},
|
||||||
|
{"VPMINUB (BX), X9, X11", "c4 61 31 da 1b|c5 31 da 1b"},
|
||||||
|
{"VPMINUB (BX), X9, X2", "c4 e1 31 da 13|c5 b1 da 13"},
|
||||||
|
{"VPMINUB (BX), Y15, Y11", "c4 61 05 da 1b|c5 05 da 1b"},
|
||||||
|
{"VPMINUB (BX), Y15, Y2", "c4 e1 05 da 13|c5 85 da 13"},
|
||||||
|
{"VPMINUB (R11), X9, X11", "c4 41 31 da 1b"},
|
||||||
|
{"VPMINUB (R11), X9, X2", "c4 c1 31 da 13"},
|
||||||
|
{"VPMINUB (R11), Y15, Y11", "c4 41 05 da 1b"},
|
||||||
|
{"VPMINUB (R11), Y15, Y2", "c4 c1 05 da 13"},
|
||||||
|
{"VPMINUB X11, X9, X11", "c4 41 31 da db"},
|
||||||
|
{"VPMINUB X11, X9, X2", "c4 c1 31 da d3"},
|
||||||
|
{"VPMINUB X2, X9, X11", "c4 61 31 da da|c5 31 da da"},
|
||||||
|
{"VPMINUB X2, X9, X2", "c4 e1 31 da d2|c5 b1 da d2"},
|
||||||
|
{"VPMINUB Y11, Y15, Y11", "c4 41 05 da db"},
|
||||||
|
{"VPMINUB Y11, Y15, Y2", "c4 c1 05 da d3"},
|
||||||
|
{"VPMINUB Y2, Y15, Y11", "c4 61 05 da da|c5 05 da da"},
|
||||||
|
{"VPMINUB Y2, Y15, Y2", "c4 e1 05 da d2|c5 85 da d2"},
|
||||||
|
{"VPMINUD (BX), X9, X11", "c4 62 31 3b 1b"},
|
||||||
|
{"VPMINUD (BX), X9, X2", "c4 e2 31 3b 13"},
|
||||||
|
{"VPMINUD (BX), Y15, Y11", "c4 62 05 3b 1b"},
|
||||||
|
{"VPMINUD (BX), Y15, Y2", "c4 e2 05 3b 13"},
|
||||||
|
{"VPMINUD (R11), X9, X11", "c4 42 31 3b 1b"},
|
||||||
|
{"VPMINUD (R11), X9, X2", "c4 c2 31 3b 13"},
|
||||||
|
{"VPMINUD (R11), Y15, Y11", "c4 42 05 3b 1b"},
|
||||||
|
{"VPMINUD (R11), Y15, Y2", "c4 c2 05 3b 13"},
|
||||||
|
{"VPMINUD X11, X9, X11", "c4 42 31 3b db"},
|
||||||
|
{"VPMINUD X11, X9, X2", "c4 c2 31 3b d3"},
|
||||||
|
{"VPMINUD X2, X9, X11", "c4 62 31 3b da"},
|
||||||
|
{"VPMINUD X2, X9, X2", "c4 e2 31 3b d2"},
|
||||||
|
{"VPMINUD Y11, Y15, Y11", "c4 42 05 3b db"},
|
||||||
|
{"VPMINUD Y11, Y15, Y2", "c4 c2 05 3b d3"},
|
||||||
|
{"VPMINUD Y2, Y15, Y11", "c4 62 05 3b da"},
|
||||||
|
{"VPMINUD Y2, Y15, Y2", "c4 e2 05 3b d2"},
|
||||||
|
{"VPMINUW (BX), X9, X11", "c4 62 31 3a 1b"},
|
||||||
|
{"VPMINUW (BX), X9, X2", "c4 e2 31 3a 13"},
|
||||||
|
{"VPMINUW (BX), Y15, Y11", "c4 62 05 3a 1b"},
|
||||||
|
{"VPMINUW (BX), Y15, Y2", "c4 e2 05 3a 13"},
|
||||||
|
{"VPMINUW (R11), X9, X11", "c4 42 31 3a 1b"},
|
||||||
|
{"VPMINUW (R11), X9, X2", "c4 c2 31 3a 13"},
|
||||||
|
{"VPMINUW (R11), Y15, Y11", "c4 42 05 3a 1b"},
|
||||||
|
{"VPMINUW (R11), Y15, Y2", "c4 c2 05 3a 13"},
|
||||||
|
{"VPMINUW X11, X9, X11", "c4 42 31 3a db"},
|
||||||
|
{"VPMINUW X11, X9, X2", "c4 c2 31 3a d3"},
|
||||||
|
{"VPMINUW X2, X9, X11", "c4 62 31 3a da"},
|
||||||
|
{"VPMINUW X2, X9, X2", "c4 e2 31 3a d2"},
|
||||||
|
{"VPMINUW Y11, Y15, Y11", "c4 42 05 3a db"},
|
||||||
|
{"VPMINUW Y11, Y15, Y2", "c4 c2 05 3a d3"},
|
||||||
|
{"VPMINUW Y2, Y15, Y11", "c4 62 05 3a da"},
|
||||||
|
{"VPMINUW Y2, Y15, Y2", "c4 e2 05 3a d2"},
|
||||||
|
{"VPMOVSXBW (BX), X11", "c4 62 79 20 1b"},
|
||||||
|
{"VPMOVSXBW (BX), X2", "c4 e2 79 20 13"},
|
||||||
|
{"VPMOVSXBW (BX), Y11", "c4 62 7d 20 1b"},
|
||||||
|
{"VPMOVSXBW (BX), Y2", "c4 e2 7d 20 13"},
|
||||||
|
{"VPMOVSXBW (R11), X11", "c4 42 79 20 1b"},
|
||||||
|
{"VPMOVSXBW (R11), X2", "c4 c2 79 20 13"},
|
||||||
|
{"VPMOVSXBW (R11), Y11", "c4 42 7d 20 1b"},
|
||||||
|
{"VPMOVSXBW (R11), Y2", "c4 c2 7d 20 13"},
|
||||||
|
{"VPMOVSXBW X11, X11", "c4 42 79 20 db"},
|
||||||
|
{"VPMOVSXBW X11, X2", "c4 c2 79 20 d3"},
|
||||||
|
{"VPMOVSXBW X11, Y11", "c4 42 7d 20 db"},
|
||||||
|
{"VPMOVSXBW X11, Y2", "c4 c2 7d 20 d3"},
|
||||||
|
{"VPMOVSXBW X2, X11", "c4 62 79 20 da"},
|
||||||
|
{"VPMOVSXBW X2, X2", "c4 e2 79 20 d2"},
|
||||||
|
{"VPMOVSXBW X2, Y11", "c4 62 7d 20 da"},
|
||||||
|
{"VPMOVSXBW X2, Y2", "c4 e2 7d 20 d2"},
|
||||||
|
{"VPMULHUW (BX), X9, X11", "c4 61 31 e4 1b|c5 31 e4 1b"},
|
||||||
|
{"VPMULHUW (BX), X9, X2", "c4 e1 31 e4 13|c5 b1 e4 13"},
|
||||||
|
{"VPMULHUW (BX), Y15, Y11", "c4 61 05 e4 1b|c5 05 e4 1b"},
|
||||||
|
{"VPMULHUW (BX), Y15, Y2", "c4 e1 05 e4 13|c5 85 e4 13"},
|
||||||
|
{"VPMULHUW (R11), X9, X11", "c4 41 31 e4 1b"},
|
||||||
|
{"VPMULHUW (R11), X9, X2", "c4 c1 31 e4 13"},
|
||||||
|
{"VPMULHUW (R11), Y15, Y11", "c4 41 05 e4 1b"},
|
||||||
|
{"VPMULHUW (R11), Y15, Y2", "c4 c1 05 e4 13"},
|
||||||
|
{"VPMULHUW X11, X9, X11", "c4 41 31 e4 db"},
|
||||||
|
{"VPMULHUW X11, X9, X2", "c4 c1 31 e4 d3"},
|
||||||
|
{"VPMULHUW X2, X9, X11", "c4 61 31 e4 da|c5 31 e4 da"},
|
||||||
|
{"VPMULHUW X2, X9, X2", "c4 e1 31 e4 d2|c5 b1 e4 d2"},
|
||||||
|
{"VPMULHUW Y11, Y15, Y11", "c4 41 05 e4 db"},
|
||||||
|
{"VPMULHUW Y11, Y15, Y2", "c4 c1 05 e4 d3"},
|
||||||
|
{"VPMULHUW Y2, Y15, Y11", "c4 61 05 e4 da|c5 05 e4 da"},
|
||||||
|
{"VPMULHUW Y2, Y15, Y2", "c4 e1 05 e4 d2|c5 85 e4 d2"},
|
||||||
|
{"VPMULLW (BX), X9, X11", "c4 61 31 d5 1b|c5 31 d5 1b"},
|
||||||
|
{"VPMULLW (BX), X9, X2", "c4 e1 31 d5 13|c5 b1 d5 13"},
|
||||||
|
{"VPMULLW (BX), Y15, Y11", "c4 61 05 d5 1b|c5 05 d5 1b"},
|
||||||
|
{"VPMULLW (BX), Y15, Y2", "c4 e1 05 d5 13|c5 85 d5 13"},
|
||||||
|
{"VPMULLW (R11), X9, X11", "c4 41 31 d5 1b"},
|
||||||
|
{"VPMULLW (R11), X9, X2", "c4 c1 31 d5 13"},
|
||||||
|
{"VPMULLW (R11), Y15, Y11", "c4 41 05 d5 1b"},
|
||||||
|
{"VPMULLW (R11), Y15, Y2", "c4 c1 05 d5 13"},
|
||||||
|
{"VPMULLW X11, X9, X11", "c4 41 31 d5 db"},
|
||||||
|
{"VPMULLW X11, X9, X2", "c4 c1 31 d5 d3"},
|
||||||
|
{"VPMULLW X2, X9, X11", "c4 61 31 d5 da|c5 31 d5 da"},
|
||||||
|
{"VPMULLW X2, X9, X2", "c4 e1 31 d5 d2|c5 b1 d5 d2"},
|
||||||
|
{"VPMULLW Y11, Y15, Y11", "c4 41 05 d5 db"},
|
||||||
|
{"VPMULLW Y11, Y15, Y2", "c4 c1 05 d5 d3"},
|
||||||
|
{"VPMULLW Y2, Y15, Y11", "c4 61 05 d5 da|c5 05 d5 da"},
|
||||||
|
{"VPMULLW Y2, Y15, Y2", "c4 e1 05 d5 d2|c5 85 d5 d2"},
|
||||||
|
{"VPSLLVD (BX), X9, X11", "c4 62 31 47 1b"},
|
||||||
|
{"VPSLLVD (BX), X9, X2", "c4 e2 31 47 13"},
|
||||||
|
{"VPSLLVD (BX), Y15, Y11", "c4 62 05 47 1b"},
|
||||||
|
{"VPSLLVD (BX), Y15, Y2", "c4 e2 05 47 13"},
|
||||||
|
{"VPSLLVD (R11), X9, X11", "c4 42 31 47 1b"},
|
||||||
|
{"VPSLLVD (R11), X9, X2", "c4 c2 31 47 13"},
|
||||||
|
{"VPSLLVD (R11), Y15, Y11", "c4 42 05 47 1b"},
|
||||||
|
{"VPSLLVD (R11), Y15, Y2", "c4 c2 05 47 13"},
|
||||||
|
{"VPSLLVD X11, X9, X11", "c4 42 31 47 db"},
|
||||||
|
{"VPSLLVD X11, X9, X2", "c4 c2 31 47 d3"},
|
||||||
|
{"VPSLLVD X2, X9, X11", "c4 62 31 47 da"},
|
||||||
|
{"VPSLLVD X2, X9, X2", "c4 e2 31 47 d2"},
|
||||||
|
{"VPSLLVD Y11, Y15, Y11", "c4 42 05 47 db"},
|
||||||
|
{"VPSLLVD Y11, Y15, Y2", "c4 c2 05 47 d3"},
|
||||||
|
{"VPSLLVD Y2, Y15, Y11", "c4 62 05 47 da"},
|
||||||
|
{"VPSLLVD Y2, Y15, Y2", "c4 e2 05 47 d2"},
|
||||||
|
{"VPSLLVQ (BX), X9, X11", "c4 62 b1 47 1b"},
|
||||||
|
{"VPSLLVQ (BX), X9, X2", "c4 e2 b1 47 13"},
|
||||||
|
{"VPSLLVQ (BX), Y15, Y11", "c4 62 85 47 1b"},
|
||||||
|
{"VPSLLVQ (BX), Y15, Y2", "c4 e2 85 47 13"},
|
||||||
|
{"VPSLLVQ (R11), X9, X11", "c4 42 b1 47 1b"},
|
||||||
|
{"VPSLLVQ (R11), X9, X2", "c4 c2 b1 47 13"},
|
||||||
|
{"VPSLLVQ (R11), Y15, Y11", "c4 42 85 47 1b"},
|
||||||
|
{"VPSLLVQ (R11), Y15, Y2", "c4 c2 85 47 13"},
|
||||||
|
{"VPSLLVQ X11, X9, X11", "c4 42 b1 47 db"},
|
||||||
|
{"VPSLLVQ X11, X9, X2", "c4 c2 b1 47 d3"},
|
||||||
|
{"VPSLLVQ X2, X9, X11", "c4 62 b1 47 da"},
|
||||||
|
{"VPSLLVQ X2, X9, X2", "c4 e2 b1 47 d2"},
|
||||||
|
{"VPSLLVQ Y11, Y15, Y11", "c4 42 85 47 db"},
|
||||||
|
{"VPSLLVQ Y11, Y15, Y2", "c4 c2 85 47 d3"},
|
||||||
|
{"VPSLLVQ Y2, Y15, Y11", "c4 62 85 47 da"},
|
||||||
|
{"VPSLLVQ Y2, Y15, Y2", "c4 e2 85 47 d2"},
|
||||||
|
{"VPSRAVD (BX), X9, X11", "c4 62 31 46 1b"},
|
||||||
|
{"VPSRAVD (BX), X9, X2", "c4 e2 31 46 13"},
|
||||||
|
{"VPSRAVD (BX), Y15, Y11", "c4 62 05 46 1b"},
|
||||||
|
{"VPSRAVD (BX), Y15, Y2", "c4 e2 05 46 13"},
|
||||||
|
{"VPSRAVD (R11), X9, X11", "c4 42 31 46 1b"},
|
||||||
|
{"VPSRAVD (R11), X9, X2", "c4 c2 31 46 13"},
|
||||||
|
{"VPSRAVD (R11), Y15, Y11", "c4 42 05 46 1b"},
|
||||||
|
{"VPSRAVD (R11), Y15, Y2", "c4 c2 05 46 13"},
|
||||||
|
{"VPSRAVD X11, X9, X11", "c4 42 31 46 db"},
|
||||||
|
{"VPSRAVD X11, X9, X2", "c4 c2 31 46 d3"},
|
||||||
|
{"VPSRAVD X2, X9, X11", "c4 62 31 46 da"},
|
||||||
|
{"VPSRAVD X2, X9, X2", "c4 e2 31 46 d2"},
|
||||||
|
{"VPSRAVD Y11, Y15, Y11", "c4 42 05 46 db"},
|
||||||
|
{"VPSRAVD Y11, Y15, Y2", "c4 c2 05 46 d3"},
|
||||||
|
{"VPSRAVD Y2, Y15, Y11", "c4 62 05 46 da"},
|
||||||
|
{"VPSRAVD Y2, Y15, Y2", "c4 e2 05 46 d2"},
|
||||||
|
{"VPSRLVD (BX), X9, X11", "c4 62 31 45 1b"},
|
||||||
|
{"VPSRLVD (BX), X9, X2", "c4 e2 31 45 13"},
|
||||||
|
{"VPSRLVD (BX), Y15, Y11", "c4 62 05 45 1b"},
|
||||||
|
{"VPSRLVD (BX), Y15, Y2", "c4 e2 05 45 13"},
|
||||||
|
{"VPSRLVD (R11), X9, X11", "c4 42 31 45 1b"},
|
||||||
|
{"VPSRLVD (R11), X9, X2", "c4 c2 31 45 13"},
|
||||||
|
{"VPSRLVD (R11), Y15, Y11", "c4 42 05 45 1b"},
|
||||||
|
{"VPSRLVD (R11), Y15, Y2", "c4 c2 05 45 13"},
|
||||||
|
{"VPSRLVD X11, X9, X11", "c4 42 31 45 db"},
|
||||||
|
{"VPSRLVD X11, X9, X2", "c4 c2 31 45 d3"},
|
||||||
|
{"VPSRLVD X2, X9, X11", "c4 62 31 45 da"},
|
||||||
|
{"VPSRLVD X2, X9, X2", "c4 e2 31 45 d2"},
|
||||||
|
{"VPSRLVD Y11, Y15, Y11", "c4 42 05 45 db"},
|
||||||
|
{"VPSRLVD Y11, Y15, Y2", "c4 c2 05 45 d3"},
|
||||||
|
{"VPSRLVD Y2, Y15, Y11", "c4 62 05 45 da"},
|
||||||
|
{"VPSRLVD Y2, Y15, Y2", "c4 e2 05 45 d2"},
|
||||||
|
{"VPSRLVQ (BX), X9, X11", "c4 62 b1 45 1b"},
|
||||||
|
{"VPSRLVQ (BX), X9, X2", "c4 e2 b1 45 13"},
|
||||||
|
{"VPSRLVQ (BX), Y15, Y11", "c4 62 85 45 1b"},
|
||||||
|
{"VPSRLVQ (BX), Y15, Y2", "c4 e2 85 45 13"},
|
||||||
|
{"VPSRLVQ (R11), X9, X11", "c4 42 b1 45 1b"},
|
||||||
|
{"VPSRLVQ (R11), X9, X2", "c4 c2 b1 45 13"},
|
||||||
|
{"VPSRLVQ (R11), Y15, Y11", "c4 42 85 45 1b"},
|
||||||
|
{"VPSRLVQ (R11), Y15, Y2", "c4 c2 85 45 13"},
|
||||||
|
{"VPSRLVQ X11, X9, X11", "c4 42 b1 45 db"},
|
||||||
|
{"VPSRLVQ X11, X9, X2", "c4 c2 b1 45 d3"},
|
||||||
|
{"VPSRLVQ X2, X9, X11", "c4 62 b1 45 da"},
|
||||||
|
{"VPSRLVQ X2, X9, X2", "c4 e2 b1 45 d2"},
|
||||||
|
{"VPSRLVQ Y11, Y15, Y11", "c4 42 85 45 db"},
|
||||||
|
{"VPSRLVQ Y11, Y15, Y2", "c4 c2 85 45 d3"},
|
||||||
|
{"VPSRLVQ Y2, Y15, Y11", "c4 62 85 45 da"},
|
||||||
|
{"VPSRLVQ Y2, Y15, Y2", "c4 e2 85 45 d2"},
|
||||||
|
{"VPSUBB (BX), X9, X11", "c4 61 31 f8 1b|c5 31 f8 1b"},
|
||||||
|
{"VPSUBB (BX), X9, X2", "c4 e1 31 f8 13|c5 b1 f8 13"},
|
||||||
|
{"VPSUBB (BX), Y15, Y11", "c4 61 05 f8 1b|c5 05 f8 1b"},
|
||||||
|
{"VPSUBB (BX), Y15, Y2", "c4 e1 05 f8 13|c5 85 f8 13"},
|
||||||
|
{"VPSUBB (R11), X9, X11", "c4 41 31 f8 1b"},
|
||||||
|
{"VPSUBB (R11), X9, X2", "c4 c1 31 f8 13"},
|
||||||
|
{"VPSUBB (R11), Y15, Y11", "c4 41 05 f8 1b"},
|
||||||
|
{"VPSUBB (R11), Y15, Y2", "c4 c1 05 f8 13"},
|
||||||
|
{"VPSUBB X11, X9, X11", "c4 41 31 f8 db"},
|
||||||
|
{"VPSUBB X11, X9, X2", "c4 c1 31 f8 d3"},
|
||||||
|
{"VPSUBB X2, X9, X11", "c4 61 31 f8 da|c5 31 f8 da"},
|
||||||
|
{"VPSUBB X2, X9, X2", "c4 e1 31 f8 d2|c5 b1 f8 d2"},
|
||||||
|
{"VPSUBB Y11, Y15, Y11", "c4 41 05 f8 db"},
|
||||||
|
{"VPSUBB Y11, Y15, Y2", "c4 c1 05 f8 d3"},
|
||||||
|
{"VPSUBB Y2, Y15, Y11", "c4 61 05 f8 da|c5 05 f8 da"},
|
||||||
|
{"VPSUBB Y2, Y15, Y2", "c4 e1 05 f8 d2|c5 85 f8 d2"},
|
||||||
|
{"VPSUBW (BX), X9, X11", "c4 61 31 f9 1b|c5 31 f9 1b"},
|
||||||
|
{"VPSUBW (BX), X9, X2", "c4 e1 31 f9 13|c5 b1 f9 13"},
|
||||||
|
{"VPSUBW (BX), Y15, Y11", "c4 61 05 f9 1b|c5 05 f9 1b"},
|
||||||
|
{"VPSUBW (BX), Y15, Y2", "c4 e1 05 f9 13|c5 85 f9 13"},
|
||||||
|
{"VPSUBW (R11), X9, X11", "c4 41 31 f9 1b"},
|
||||||
|
{"VPSUBW (R11), X9, X2", "c4 c1 31 f9 13"},
|
||||||
|
{"VPSUBW (R11), Y15, Y11", "c4 41 05 f9 1b"},
|
||||||
|
{"VPSUBW (R11), Y15, Y2", "c4 c1 05 f9 13"},
|
||||||
|
{"VPSUBW X11, X9, X11", "c4 41 31 f9 db"},
|
||||||
|
{"VPSUBW X11, X9, X2", "c4 c1 31 f9 d3"},
|
||||||
|
{"VPSUBW X2, X9, X11", "c4 61 31 f9 da|c5 31 f9 da"},
|
||||||
|
{"VPSUBW X2, X9, X2", "c4 e1 31 f9 d2|c5 b1 f9 d2"},
|
||||||
|
{"VPSUBW Y11, Y15, Y11", "c4 41 05 f9 db"},
|
||||||
|
{"VPSUBW Y11, Y15, Y2", "c4 c1 05 f9 d3"},
|
||||||
|
{"VPSUBW Y2, Y15, Y11", "c4 61 05 f9 da|c5 05 f9 da"},
|
||||||
|
{"VPSUBW Y2, Y15, Y2", "c4 e1 05 f9 d2|c5 85 f9 d2"},
|
||||||
|
{"VSHUFPS $7, (BX), X9, X11", "c4 61 30 c6 1b 07|c5 30 c6 1b 07"},
|
||||||
|
{"VSHUFPS $7, (BX), X9, X2", "c4 e1 30 c6 13 07|c5 b0 c6 13 07"},
|
||||||
|
{"VSHUFPS $7, (BX), Y15, Y11", "c4 61 04 c6 1b 07|c5 04 c6 1b 07"},
|
||||||
|
{"VSHUFPS $7, (BX), Y15, Y2", "c4 e1 04 c6 13 07|c5 84 c6 13 07"},
|
||||||
|
{"VSHUFPS $7, (R11), X9, X11", "c4 41 30 c6 1b 07"},
|
||||||
|
{"VSHUFPS $7, (R11), X9, X2", "c4 c1 30 c6 13 07"},
|
||||||
|
{"VSHUFPS $7, (R11), Y15, Y11", "c4 41 04 c6 1b 07"},
|
||||||
|
{"VSHUFPS $7, (R11), Y15, Y2", "c4 c1 04 c6 13 07"},
|
||||||
|
{"VSHUFPS $7, X11, X9, X11", "c4 41 30 c6 db 07"},
|
||||||
|
{"VSHUFPS $7, X11, X9, X2", "c4 c1 30 c6 d3 07"},
|
||||||
|
{"VSHUFPS $7, X2, X9, X11", "c4 61 30 c6 da 07|c5 30 c6 da 07"},
|
||||||
|
{"VSHUFPS $7, X2, X9, X2", "c4 e1 30 c6 d2 07|c5 b0 c6 d2 07"},
|
||||||
|
{"VSHUFPS $7, Y11, Y15, Y11", "c4 41 04 c6 db 07"},
|
||||||
|
{"VSHUFPS $7, Y11, Y15, Y2", "c4 c1 04 c6 d3 07"},
|
||||||
|
{"VSHUFPS $7, Y2, Y15, Y11", "c4 61 04 c6 da 07|c5 04 c6 da 07"},
|
||||||
|
{"VSHUFPS $7, Y2, Y15, Y2", "c4 e1 04 c6 d2 07|c5 84 c6 d2 07"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64VexParityCorpus assembles every corpus line and requires the
|
||||||
|
// bytes to match one of the alternatives go tool asm accepts.
|
||||||
|
func TestAmd64VexParityCorpus(t *testing.T) {
|
||||||
|
for _, tc := range amd64VexParityCorpus {
|
||||||
|
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tc.line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got := hexBytes(code)
|
||||||
|
if !slices.Contains(strings.Split(tc.want, "|"), got) {
|
||||||
|
t.Errorf("%s: got %s, want one of %s", tc.line, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The byte forms of the suffixed scalar families answer to the register
|
||||||
|
// operands as much as to the mnemonic: the toolchain's own disassembly
|
||||||
|
// prints them with the L suffix or none at all (the rendered suffix rides
|
||||||
|
// the operand-size attribute), the register names carrying the width. The
|
||||||
|
// tests here pin that reconciliation: the renderer's spellings encode the
|
||||||
|
// byte form byte for byte, the W and Q spellings never ride a byte
|
||||||
|
// register, and the classic names stay size-agnostic.
|
||||||
|
|
||||||
|
// TestOperandWidthByteForms pins the renderer's spellings: an L suffix or
|
||||||
|
// no suffix with a byte-spelled register encodes the 8-bit form, every
|
||||||
|
// register joining at its low byte. Every row's bytes were cross-checked
|
||||||
|
// against `go tool asm -S` over the B-suffixed spelling of the same
|
||||||
|
// operands (the toolchain rejects the L spelling itself), and against the
|
||||||
|
// toolchain's objdump text for the byte encodings.
|
||||||
|
func TestOperandWidthByteForms(t *testing.T) {
|
||||||
|
r11 := Reg{idx: 11, size: 8}
|
||||||
|
r8 := Reg{idx: 8, size: 1}
|
||||||
|
r9 := Reg{idx: 9, size: 1}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// The atomics pair: XADD and CMPXCHG drop to the 0F C0/0F B0 byte
|
||||||
|
// opcodes the L spelling would otherwise widen past.
|
||||||
|
{"XADDL DL, DL", []Operand{DL, DL}, "0fc0d2"},
|
||||||
|
{"XADDB DL, DL", []Operand{DL, DL}, "0fc0d2"},
|
||||||
|
{"XADDL R8B, R9B", []Operand{r8, r9}, "450fc0c1"},
|
||||||
|
{"CMPXCHGL DL, DL", []Operand{DL, DL}, "0fb0d2"},
|
||||||
|
{"XCHGL DL, DL", []Operand{DL, DL}, "86d2"},
|
||||||
|
{"XCHGL DL, 0(BX)", []Operand{DL, Ptr(BX, 0, 1)}, "8613"},
|
||||||
|
// The ALU immediates: the accumulator short form for the AL
|
||||||
|
// spelling, the generic 0x80 /digit elsewhere.
|
||||||
|
{"CMPL AL, $7", []Operand{AL, Imm(7)}, "3c07"},
|
||||||
|
{"ADDL $7, AL", []Operand{Imm(7), AL}, "0407"},
|
||||||
|
{"SUBL $7, AL", []Operand{Imm(7), AL}, "2c07"},
|
||||||
|
{"ANDL $7, AL", []Operand{Imm(7), AL}, "2407"},
|
||||||
|
{"SBBL $7, AL", []Operand{Imm(7), AL}, "1c07"},
|
||||||
|
{"SBBL $7, DL", []Operand{Imm(7), DL}, "80da07"},
|
||||||
|
{"ADDB $3, AX", []Operand{Imm(3), AX}, "80c003"},
|
||||||
|
{"ORB $7, AX", []Operand{Imm(7), AX}, "80c807"},
|
||||||
|
{"SBBB $7, AX", []Operand{Imm(7), AX}, "80d807"},
|
||||||
|
// The register forms, the extended register riding REX.B and the
|
||||||
|
// byte source in the reg field.
|
||||||
|
{"SBBL DL, R11", []Operand{DL, r11}, "4118d3"},
|
||||||
|
{"TESTL R11, DL", []Operand{r11, DL}, "4484da"},
|
||||||
|
{"TESTB $7, AX", []Operand{Imm(7), AX}, "f6c007"},
|
||||||
|
{"TESTB R11, DL", []Operand{r11, DL}, "4484da"},
|
||||||
|
// CRC32 keeps the F0 byte opcode under the unsuffixed spelling.
|
||||||
|
{"CRC32 DL, R11", []Operand{DL, r11}, "f2440f38f0da"},
|
||||||
|
{"CRC32B DL, R11", []Operand{DL, r11}, "f2440f38f0da"},
|
||||||
|
{"CRC32B AX, CX", []Operand{AX, CX}, "f20f38f0c8"},
|
||||||
|
// The move and unary families follow the same rule.
|
||||||
|
{"MOVL $7, DL", []Operand{Imm(7), DL}, "b207"},
|
||||||
|
{"MOVB $7, DL", []Operand{Imm(7), DL}, "b207"},
|
||||||
|
{"MOVB AX, AL", []Operand{AX, AL}, "88c0"},
|
||||||
|
{"INCL DL", []Operand{DL}, "fec2"},
|
||||||
|
{"NEGL DL", []Operand{DL}, "f6da"},
|
||||||
|
{"IMULL DL", []Operand{DL}, "f6ea"},
|
||||||
|
{"SHLL $2, DL", []Operand{Imm(2), DL}, "c0e202"},
|
||||||
|
{"ROLL CL, DL", []Operand{CL, DL}, "d2c2"},
|
||||||
|
// The shift count never narrows the shifted value.
|
||||||
|
{"RCLW CL, 0(R11)", []Operand{CL, Ptr(r11, 0, 2)}, "6641d313"},
|
||||||
|
{"RORQ CL, AX", []Operand{CL, AX}, "48d3c8"},
|
||||||
|
// The size-agnostic spellings stay width-free: the L and Q forms
|
||||||
|
// of the same families are untouched by the reconciliation.
|
||||||
|
{"XADDL AX, CX", []Operand{AX, CX}, "0fc1c1"},
|
||||||
|
{"ADDL $7, AX", []Operand{Imm(7), AX}, "83c007"},
|
||||||
|
{"ADDL $256, AX", []Operand{Imm(256), AX}, "0500010000"},
|
||||||
|
{"CRC32L AX, CX", []Operand{AX, CX}, "f20f38f1c8"},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Encode(mnemonicOf(tt.name), tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %v", tt.name, err)
|
||||||
|
}
|
||||||
|
if want := unhex(tt.want); !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("%s: % x, want % x", tt.name, got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOperandWidthConflicts pins the refusals: the W and Q spellings never
|
||||||
|
// ride a byte register (go tool asm rejects MOVQ AL, AX and its siblings
|
||||||
|
// outright), and neither does the MOVD alias of the quad move.
|
||||||
|
func TestOperandWidthConflicts(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
ops []Operand
|
||||||
|
}{
|
||||||
|
{"MOVQ AL, AX", []Operand{AL, AX}},
|
||||||
|
{"MOVQ AX, AL", []Operand{AX, AL}},
|
||||||
|
{"MOVQ DL, DL", []Operand{DL, DL}},
|
||||||
|
{"MOVW $7, DL", []Operand{Imm(7), DL}},
|
||||||
|
{"MOVD AL, AX", []Operand{AL, AX}},
|
||||||
|
{"XADDQ DL, DL", []Operand{DL, DL}},
|
||||||
|
{"CMPXCHGQ DL, DL", []Operand{DL, DL}},
|
||||||
|
{"XCHGQ DL, DL", []Operand{DL, DL}},
|
||||||
|
{"SHLQ $2, DL", []Operand{Imm(2), DL}},
|
||||||
|
{"INCQ DL", []Operand{DL}},
|
||||||
|
{"IMULQ DL", []Operand{DL}},
|
||||||
|
{"TESTQ R11, DL", []Operand{Reg{idx: 11, size: 8}, DL}},
|
||||||
|
{"CRC32Q DL, R11", []Operand{DL, Reg{idx: 11, size: 8}}},
|
||||||
|
{"CRC32W DL, R11", []Operand{DL, Reg{idx: 11, size: 8}}},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if _, err := Encode(mnemonicOf(tt.name), tt.ops...); err == nil {
|
||||||
|
t.Errorf("%s: encoded, want the byte-register conflict refused", tt.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOperandWidthDifferential is the byte-parity oracle for the same
|
||||||
|
// reconciliation: the B-suffixed spellings, which go tool asm accepts, must
|
||||||
|
// encode identically through both assemblers, the agnostic names included
|
||||||
|
// (their low byte joins the byte form) and the accumulator division (AL
|
||||||
|
// short, AX generic) with them.
|
||||||
|
func TestOperandWidthDifferential(t *testing.T) {
|
||||||
|
kernel := "#include \"textflag.h\"\n" +
|
||||||
|
"TEXT \u00b7bytewidth(SB), NOSPLIT, $0\n" +
|
||||||
|
"\tXADDB DL, DL\n" +
|
||||||
|
"\tXADDB R8B, R9B\n" +
|
||||||
|
"\tXADDL AX, CX\n" +
|
||||||
|
"\tCMPXCHGB DL, DL\n" +
|
||||||
|
"\tXCHGB DL, DL\n" +
|
||||||
|
"\tXCHGB DL, 0(BX)\n" +
|
||||||
|
"\tCMPB AL, $7\n" +
|
||||||
|
"\tADDB $7, AL\n" +
|
||||||
|
"\tADDB $3, AX\n" +
|
||||||
|
"\tSUBB $7, AL\n" +
|
||||||
|
"\tANDB $7, AL\n" +
|
||||||
|
"\tSBBB $7, AL\n" +
|
||||||
|
"\tSBBB $7, DL\n" +
|
||||||
|
"\tSBBB DL, R11\n" +
|
||||||
|
"\tORB $7, AX\n" +
|
||||||
|
"\tTESTB R11, DL\n" +
|
||||||
|
"\tTESTB $7, AX\n" +
|
||||||
|
"\tCRC32B DL, R11\n" +
|
||||||
|
"\tCRC32B AX, CX\n" +
|
||||||
|
"\tCRC32B R8B, CX\n" +
|
||||||
|
"\tCRC32L AX, CX\n" +
|
||||||
|
"\tMOVB $7, DL\n" +
|
||||||
|
"\tMOVB $3, AX\n" +
|
||||||
|
"\tMOVB AX, AL\n" +
|
||||||
|
"\tINCB DL\n" +
|
||||||
|
"\tNEGB DL\n" +
|
||||||
|
"\tIMULB DL\n" +
|
||||||
|
"\tMULB CL\n" +
|
||||||
|
"\tSHLB $2, DL\n" +
|
||||||
|
"\tROLB CL, DL\n" +
|
||||||
|
"\tRET\n"
|
||||||
|
path := filepath.Join(t.TempDir(), "bytewidth_amd64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(kernel), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gt := oracleFuncCode(t, toolAsmObject(t, path, ""))
|
||||||
|
goCode, ok := gt["bytewidth.bytewidth"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("oracle: bytewidth function missing (%d functions)", len(gt))
|
||||||
|
}
|
||||||
|
gasmCode := code(path, kernel)
|
||||||
|
if gasmCode == nil {
|
||||||
|
t.Fatal("gasm: assemble failed")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(gasmCode, goCode) {
|
||||||
|
t.Errorf("byte width kernel:\ngasm %x\ngo %x", gasmCode, goCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mnemonicOf returns the first whitespace-free token of a rendered row.
|
||||||
|
func mnemonicOf(text string) string {
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
if text[i] == ' ' || text[i] == ',' {
|
||||||
|
return text[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// unhex decodes a hex string, failing the test on malformed input.
|
||||||
|
func unhex(s string) []byte {
|
||||||
|
out, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic("unhex: " + err.Error())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// This file implements the x87 floating-point family the Go assembler
|
||||||
|
// carries: the no-operand stack controls, the two-register arithmetic pair,
|
||||||
|
// the register compares, the memory loads and stores and the FXSAVE pair.
|
||||||
|
// Every encoding here is pinned byte for byte against go tool asm through
|
||||||
|
// the corpus lines in amd64_x87_test.go.
|
||||||
|
|
||||||
|
// x87NoOperand maps the no-operand x87 instruction to its postfix byte
|
||||||
|
// inside the D9 escape: D9 <postfix>, no ModR/M, no operand.
|
||||||
|
var x87NoOperand = map[string]byte{
|
||||||
|
"F2XM1": 0xF0,
|
||||||
|
"FABS": 0xE1,
|
||||||
|
"FCHS": 0xE0,
|
||||||
|
"FCOS": 0xFF,
|
||||||
|
"FDECSTP": 0xF6,
|
||||||
|
"FINCSTP": 0xF7,
|
||||||
|
"FLD1": 0xE8,
|
||||||
|
"FLDL2E": 0xEA,
|
||||||
|
"FLDL2T": 0xE9,
|
||||||
|
"FLDLG2": 0xEC,
|
||||||
|
"FLDPI": 0xEB,
|
||||||
|
"FNOP": 0xD0,
|
||||||
|
"FPATAN": 0xF3,
|
||||||
|
"FPREM": 0xF8,
|
||||||
|
"FPREM1": 0xF5,
|
||||||
|
"FPTAN": 0xF2,
|
||||||
|
"FRNDINT": 0xFC,
|
||||||
|
"FSCALE": 0xFD,
|
||||||
|
"FSIN": 0xFE,
|
||||||
|
"FSINCOS": 0xFB,
|
||||||
|
"FSQRT": 0xFA,
|
||||||
|
"FTST": 0xE4,
|
||||||
|
"FXAM": 0xE5,
|
||||||
|
"FXTRACT": 0xF4,
|
||||||
|
"FYL2X": 0xF1,
|
||||||
|
"FYL2XP1": 0xF9,
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87ArithSpec describes one member of the D8/DC two-register arithmetic
|
||||||
|
// pair. The D8 form reads ST(0) as its second operand (FADDD F2, F0), the
|
||||||
|
// DC form ST(0) as its first (FADDD F0, F2) or a memory source (FADDD (BX),
|
||||||
|
// F0). FDIV is the odd member: with ST(0) as the first operand the toolchain
|
||||||
|
// assembles the reversed register form (DC F8+i, the FDIVR digit), so the DC
|
||||||
|
// digit differs from the memory digit there.
|
||||||
|
type x87ArithSpec struct {
|
||||||
|
d8Digit int // the /digit of the D8 form (dst = ST(0))
|
||||||
|
dcDigit int // the /digit of the DC register form (src = ST(0))
|
||||||
|
memDigit int // the /digit of the DC memory form (dst = ST(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87Arith maps the arithmetic mnemonics to their digits.
|
||||||
|
var x87Arith = map[string]x87ArithSpec{
|
||||||
|
"FADDD": {0, 0, 0},
|
||||||
|
"FCOMD": {2, 2, 2},
|
||||||
|
"FDIVD": {6, 7, 6},
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87FCmov maps the conditional x87 moves to their escape byte and postfix
|
||||||
|
// base (the C0/C8/D0/D8 group the condition selects); the compared register
|
||||||
|
// rides the postfix's low three bits.
|
||||||
|
var x87FCmov = map[string][2]byte{
|
||||||
|
"FCMOVB": {0xDA, 0xC0},
|
||||||
|
"FCMOVBE": {0xDA, 0xD0},
|
||||||
|
"FCMOVE": {0xDA, 0xC8},
|
||||||
|
"FCMOVNB": {0xDB, 0xC0},
|
||||||
|
"FCMOVNBE": {0xDB, 0xD0},
|
||||||
|
"FCMOVNE": {0xDB, 0xC8},
|
||||||
|
"FCMOVNU": {0xDB, 0xD8},
|
||||||
|
"FCMOVU": {0xDA, 0xD8},
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87Compare maps the register compare pair to their escape byte; the
|
||||||
|
// register form is escape F0+i (mod 11, reg 110, rm = the compared stack
|
||||||
|
// register), ST(0) fixed as the second operand.
|
||||||
|
var x87Compare = map[string]byte{
|
||||||
|
"FCOMI": 0xDB,
|
||||||
|
"FCOMIP": 0xDF,
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87MemUnary maps the one-memory-operand x87 controls to their escape byte
|
||||||
|
// and /digit.
|
||||||
|
var x87MemUnary = map[string]struct {
|
||||||
|
escape byte
|
||||||
|
digit int
|
||||||
|
}{
|
||||||
|
"FBLD": {0xDF, 4},
|
||||||
|
"FBSTP": {0xDF, 6},
|
||||||
|
"FLDCW": {0xD9, 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87Fxsav maps the FXSAVE pair to their /digit in the 0F AE group; the 64
|
||||||
|
// spellings carry REX.W.
|
||||||
|
var x87Fxsav = map[string]struct {
|
||||||
|
digit int
|
||||||
|
rexW bool
|
||||||
|
}{
|
||||||
|
"FXSAVE": {0, false},
|
||||||
|
"FXSAVE64": {0, true},
|
||||||
|
"FXRSTOR": {1, false},
|
||||||
|
"FXRSTOR64": {1, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeX87 encodes the x87 family. It reports whether the mnemonic belongs
|
||||||
|
// to the family; a false result hands the mnemonic back to the caller, an
|
||||||
|
// error result a failed attempt to encode it.
|
||||||
|
func (e *enc) encodeX87(upper string, ops []Operand) (bool, error) {
|
||||||
|
if post, ok := x87NoOperand[upper]; ok {
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return true, fmt.Errorf("%s takes no operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
return true, e.emit(&instr{opcode: []byte{0xD9, post}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
if spec, ok := x87Arith[upper]; ok {
|
||||||
|
return true, e.encodeX87Arith(upper, spec, ops)
|
||||||
|
}
|
||||||
|
if esc, ok := x87FCmov[upper]; ok {
|
||||||
|
src, _, err := x87PairOperands(upper, ops)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
// The condition applies between the named register and ST(0), so the
|
||||||
|
// second operand is always F0; the first rides the postfix's low bits.
|
||||||
|
return true, e.emit(&instr{opcode: []byte{esc[0], esc[1] | byte(src.idx&7)}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
if escape, ok := x87Compare[upper]; ok {
|
||||||
|
if _, _, err := x87PairOperands(upper, ops); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
src, _ := ops[0].(Reg)
|
||||||
|
return true, e.emit(&instr{opcode: []byte{escape, 0xF0 | byte(src.idx&7)}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
if upper == "FADDDP" {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return true, fmt.Errorf("FADDDP expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
first, ok1 := ops[0].(Reg)
|
||||||
|
second, ok2 := ops[1].(Reg)
|
||||||
|
if !ok1 || !ok2 || !first.fp || !second.fp {
|
||||||
|
return true, fmt.Errorf("FADDDP takes two x87 stack registers")
|
||||||
|
}
|
||||||
|
if first.idx != 0 {
|
||||||
|
return true, fmt.Errorf("FADDDP: the first operand must be F0")
|
||||||
|
}
|
||||||
|
return true, e.emit(&instr{opcode: []byte{0xDE, 0xC0 | byte(second.idx&7)}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
if m, ok := x87MemUnary[upper]; ok {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{m.escape}, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
if m, ok := x87Fxsav[upper]; ok {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := newInstr(0, []byte{0x0F, 0xAE})
|
||||||
|
i.rexW = m.rexW
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeX87Arith encodes one member of the D8/DC arithmetic pair. The tool-
|
||||||
|
// chain's shape set: (Fn, F0) rides D8, (F0, Fn) rides DC, ((m), F0) rides
|
||||||
|
// the DC memory digit; every other pairing is an error.
|
||||||
|
func (e *enc) encodeX87Arith(mnem string, spec x87ArithSpec, ops []Operand) error {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
src, dst := ops[0], ops[1]
|
||||||
|
dstReg, dstIsF := dst.(Reg)
|
||||||
|
if !dstIsF || !dstReg.fp {
|
||||||
|
return fmt.Errorf("%s: the destination must be an x87 stack register", mnem)
|
||||||
|
}
|
||||||
|
switch s := src.(type) {
|
||||||
|
case Reg:
|
||||||
|
if !s.fp {
|
||||||
|
return fmt.Errorf("%s: the source must be an x87 stack register", mnem)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case dstReg.idx == 0:
|
||||||
|
return e.emit(&instr{opcode: []byte{0xD8, 0xC0 | byte(spec.d8Digit)<<3 | byte(s.idx&7)}, modrm: -1, sib: -1})
|
||||||
|
case s.idx == 0:
|
||||||
|
return e.emit(&instr{opcode: []byte{0xDC, 0xC0 | byte(spec.dcDigit)<<3 | byte(dstReg.idx&7)}, modrm: -1, sib: -1})
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s: one operand must be F0", mnem)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !isX86Mem(src) {
|
||||||
|
return fmt.Errorf("%s: the source must be an x87 stack register or memory", mnem)
|
||||||
|
}
|
||||||
|
if dstReg.idx != 0 {
|
||||||
|
return fmt.Errorf("%s: the destination must be F0 with a memory source", mnem)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0xDC}, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, spec.memDigit, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// x87PairOperands validates the (register, F0) shape the conditional moves
|
||||||
|
// and compares take and returns the two registers.
|
||||||
|
func x87PairOperands(mnem string, ops []Operand) (Reg, Reg, error) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return Reg{}, Reg{}, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
src, ok1 := ops[0].(Reg)
|
||||||
|
dst, ok2 := ops[1].(Reg)
|
||||||
|
if !ok1 || !ok2 || !src.fp || !dst.fp {
|
||||||
|
return Reg{}, Reg{}, fmt.Errorf("%s takes two x87 stack registers", mnem)
|
||||||
|
}
|
||||||
|
if dst.idx != 0 {
|
||||||
|
return Reg{}, Reg{}, fmt.Errorf("%s: the second operand must be F0", mnem)
|
||||||
|
}
|
||||||
|
return src, dst, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// amd64X87Corpus holds every line the Go toolchain's own
|
||||||
|
// amd64enc.s carries for the x87 family (stack controls, the arithmetic pair, the compares, the memory loads and the FXSAVE pair), with the bytes go tool asm
|
||||||
|
// emits for each: the differential ground truth the family is proven
|
||||||
|
// against, line for line.
|
||||||
|
var amd64X87Corpus = []struct {
|
||||||
|
line string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"F2XM1", "d9 f0"},
|
||||||
|
{"FABS", "d9 e1"},
|
||||||
|
{"FADDD F2, F0", "d8 c2"},
|
||||||
|
{"FADDD F3, F0", "d8 c3"},
|
||||||
|
{"FADDD F0, F2", "dc c2"},
|
||||||
|
{"FADDD F0, F3", "dc c3"},
|
||||||
|
{"FADDD (BX), F0", "dc 03"},
|
||||||
|
{"FADDD (R11), F0", "41 dc 03"},
|
||||||
|
{"FADDDP F0, F2", "de c2"},
|
||||||
|
{"FADDDP F0, F3", "de c3"},
|
||||||
|
{"FBLD (BX)", "df 23"},
|
||||||
|
{"FBLD (R11)", "41 df 23"},
|
||||||
|
{"FBSTP (BX)", "df 33"},
|
||||||
|
{"FBSTP (R11)", "41 df 33"},
|
||||||
|
{"FCHS", "d9 e0"},
|
||||||
|
{"FCMOVB F2, F0", "da c2"},
|
||||||
|
{"FCMOVB F3, F0", "da c3"},
|
||||||
|
{"FCMOVBE F2, F0", "da d2"},
|
||||||
|
{"FCMOVBE F3, F0", "da d3"},
|
||||||
|
{"FCMOVE F2, F0", "da ca"},
|
||||||
|
{"FCMOVE F3, F0", "da cb"},
|
||||||
|
{"FCMOVNB F2, F0", "db c2"},
|
||||||
|
{"FCMOVNB F3, F0", "db c3"},
|
||||||
|
{"FCMOVNBE F2, F0", "db d2"},
|
||||||
|
{"FCMOVNBE F3, F0", "db d3"},
|
||||||
|
{"FCMOVNE F2, F0", "db ca"},
|
||||||
|
{"FCMOVNE F3, F0", "db cb"},
|
||||||
|
{"FCMOVNU F2, F0", "db da"},
|
||||||
|
{"FCMOVNU F3, F0", "db db"},
|
||||||
|
{"FCMOVU F2, F0", "da da"},
|
||||||
|
{"FCMOVU F3, F0", "da db"},
|
||||||
|
{"FCOMD F2, F0", "d8 d2"},
|
||||||
|
{"FCOMD F3, F0", "d8 d3"},
|
||||||
|
{"FCOMD (BX), F0", "dc 13"},
|
||||||
|
{"FCOMD (R11), F0", "41 dc 13"},
|
||||||
|
{"FCOMI F2, F0", "db f2"},
|
||||||
|
{"FCOMI F3, F0", "db f3"},
|
||||||
|
{"FCOMIP F2, F0", "df f2"},
|
||||||
|
{"FCOMIP F3, F0", "df f3"},
|
||||||
|
{"FCOS", "d9 ff"},
|
||||||
|
{"FDECSTP", "d9 f6"},
|
||||||
|
{"FDIVD F2, F0", "d8 f2"},
|
||||||
|
{"FDIVD F3, F0", "d8 f3"},
|
||||||
|
{"FDIVD F0, F2", "dc fa"},
|
||||||
|
{"FDIVD F0, F3", "dc fb"},
|
||||||
|
{"FDIVD (BX), F0", "dc 33"},
|
||||||
|
{"FDIVD (R11), F0", "41 dc 33"},
|
||||||
|
{"FINCSTP", "d9 f7"},
|
||||||
|
{"FLD1", "d9 e8"},
|
||||||
|
{"FLDCW (BX)", "d9 2b"},
|
||||||
|
{"FLDCW (R11)", "41 d9 2b"},
|
||||||
|
{"FLDL2E", "d9 ea"},
|
||||||
|
{"FLDL2T", "d9 e9"},
|
||||||
|
{"FLDLG2", "d9 ec"},
|
||||||
|
{"FLDPI", "d9 eb"},
|
||||||
|
{"FNOP", "d9 d0"},
|
||||||
|
{"FPATAN", "d9 f3"},
|
||||||
|
{"FPREM", "d9 f8"},
|
||||||
|
{"FPREM1", "d9 f5"},
|
||||||
|
{"FPTAN", "d9 f2"},
|
||||||
|
{"FRNDINT", "d9 fc"},
|
||||||
|
{"FSCALE", "d9 fd"},
|
||||||
|
{"FSIN", "d9 fe"},
|
||||||
|
{"FSINCOS", "d9 fb"},
|
||||||
|
{"FSQRT", "d9 fa"},
|
||||||
|
{"FTST", "d9 e4"},
|
||||||
|
{"FXAM", "d9 e5"},
|
||||||
|
{"FXRSTOR (BX)", "0f ae 0b"},
|
||||||
|
{"FXRSTOR (R11)", "41 0f ae 0b"},
|
||||||
|
{"FXRSTOR64 (BX)", "48 0f ae 0b"},
|
||||||
|
{"FXRSTOR64 (R11)", "49 0f ae 0b"},
|
||||||
|
{"FXSAVE (BX)", "0f ae 03"},
|
||||||
|
{"FXSAVE (R11)", "41 0f ae 03"},
|
||||||
|
{"FXSAVE64 (BX)", "48 0f ae 03"},
|
||||||
|
{"FXSAVE64 (R11)", "49 0f ae 03"},
|
||||||
|
{"FXTRACT", "d9 f4"},
|
||||||
|
{"FYL2X", "d9 f1"},
|
||||||
|
{"FYL2XP1", "d9 f9"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64X87Corpus assembles every corpus line and requires the same bytes
|
||||||
|
// go tool asm emits for it.
|
||||||
|
func TestAmd64X87Corpus(t *testing.T) {
|
||||||
|
for _, tc := range amd64X87Corpus {
|
||||||
|
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tc.line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexBytes(code); got != tc.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", tc.line, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file implements the XSAVE family: the extended-state save and restore
|
||||||
|
// pair in its four generations (XSAVE/XRSTOR, XSAVEOPT, XSAVEC, XSAVES) and
|
||||||
|
// their 64 spellings. Each takes one memory operand alone; the 64 spellings
|
||||||
|
// carry REX.W. Every encoding here is pinned byte for byte against
|
||||||
|
// go tool asm through the corpus lines in amd64_xsave_test.go.
|
||||||
|
|
||||||
|
// xsaveSpec is one XSAVE family member: the opcode group and the /digit.
|
||||||
|
// XSAVEOPT carries no mandatory prefix in the toolchain's encoding despite
|
||||||
|
// the manual's 0x66, so the family has none anywhere.
|
||||||
|
type xsaveSpec struct {
|
||||||
|
op []byte
|
||||||
|
digit int
|
||||||
|
}
|
||||||
|
|
||||||
|
// xsaveTable maps the save/restore mnemonics to their encodings. The 64
|
||||||
|
// spellings share the base's digit with REX.W.
|
||||||
|
var xsaveTable = map[string]xsaveSpec{
|
||||||
|
"XSAVE": {[]byte{0x0F, 0xAE}, 4},
|
||||||
|
"XRSTOR": {[]byte{0x0F, 0xAE}, 5},
|
||||||
|
"XSAVEOPT": {[]byte{0x0F, 0xAE}, 6},
|
||||||
|
"XSAVEC": {[]byte{0x0F, 0xC7}, 4},
|
||||||
|
"XSAVES": {[]byte{0x0F, 0xC7}, 5},
|
||||||
|
"XRSTORS": {[]byte{0x0F, 0xC7}, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeXsave encodes the XSAVE family. It reports whether the mnemonic
|
||||||
|
// belongs to the family.
|
||||||
|
func (e *enc) encodeXsave(upper string, ops []Operand) (bool, error) {
|
||||||
|
name, rexW := upper, false
|
||||||
|
if base, ok := strings.CutSuffix(upper, "64"); ok {
|
||||||
|
name, rexW = base, true
|
||||||
|
}
|
||||||
|
spec, ok := xsaveTable[name]
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[0]) {
|
||||||
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: append([]byte(nil), spec.op...), modrm: -1, sib: -1, rexW: rexW}
|
||||||
|
if err := setRMDigit(i, spec.digit, ops[0], 8); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, e.emit(i)
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// amd64XsaveCorpus holds every line the Go toolchain's own
|
||||||
|
// amd64enc.s carries for the XSAVE family (XSAVE, XSAVEOPT, XSAVEC, XSAVES and the restores, plain and 64), with the bytes go tool asm
|
||||||
|
// emits for each: the differential ground truth the family is proven
|
||||||
|
// against, line for line.
|
||||||
|
var amd64XsaveCorpus = []struct {
|
||||||
|
line string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"XRSTOR (BX)", "0f ae 2b"},
|
||||||
|
{"XRSTOR (R11)", "41 0f ae 2b"},
|
||||||
|
{"XRSTOR64 (BX)", "48 0f ae 2b"},
|
||||||
|
{"XRSTOR64 (R11)", "49 0f ae 2b"},
|
||||||
|
{"XRSTORS (BX)", "0f c7 1b"},
|
||||||
|
{"XRSTORS (R11)", "41 0f c7 1b"},
|
||||||
|
{"XRSTORS64 (BX)", "48 0f c7 1b"},
|
||||||
|
{"XRSTORS64 (R11)", "49 0f c7 1b"},
|
||||||
|
{"XSAVE (BX)", "0f ae 23"},
|
||||||
|
{"XSAVE (R11)", "41 0f ae 23"},
|
||||||
|
{"XSAVE64 (BX)", "48 0f ae 23"},
|
||||||
|
{"XSAVE64 (R11)", "49 0f ae 23"},
|
||||||
|
{"XSAVEC (BX)", "0f c7 23"},
|
||||||
|
{"XSAVEC (R11)", "41 0f c7 23"},
|
||||||
|
{"XSAVEC64 (BX)", "48 0f c7 23"},
|
||||||
|
{"XSAVEC64 (R11)", "49 0f c7 23"},
|
||||||
|
{"XSAVEOPT (BX)", "0f ae 33"},
|
||||||
|
{"XSAVEOPT (R11)", "41 0f ae 33"},
|
||||||
|
{"XSAVEOPT64 (BX)", "48 0f ae 33"},
|
||||||
|
{"XSAVEOPT64 (R11)", "49 0f ae 33"},
|
||||||
|
{"XSAVES (BX)", "0f c7 2b"},
|
||||||
|
{"XSAVES (R11)", "41 0f c7 2b"},
|
||||||
|
{"XSAVES64 (BX)", "48 0f c7 2b"},
|
||||||
|
{"XSAVES64 (R11)", "49 0f c7 2b"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64XsaveCorpus assembles every corpus line and requires the same bytes
|
||||||
|
// go tool asm emits for it.
|
||||||
|
func TestAmd64XsaveCorpus(t *testing.T) {
|
||||||
|
for _, tc := range amd64XsaveCorpus {
|
||||||
|
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tc.line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexBytes(code); got != tc.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", tc.line, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2907
-326
File diff suppressed because it is too large.
Load diff
+443
-36
@@ -33,7 +33,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// arm64RegNum returns the 5-bit register number for an AArch64 register name:
|
// arm64RegNum returns the 5-bit register number for an AArch64 register name:
|
||||||
@@ -79,6 +79,11 @@ func arm64RegNum(name string) int {
|
|||||||
return 17
|
return 17
|
||||||
case "R18":
|
case "R18":
|
||||||
return 18
|
return 18
|
||||||
|
case "R18_PLATFORM":
|
||||||
|
// The toolchain's Windows spelling: R18 is renamed R18_PLATFORM in
|
||||||
|
// cmd/asm/internal/arch so assembly cannot use it by accident, and
|
||||||
|
// sys_windows_arm64.s references it only through this name.
|
||||||
|
return 18
|
||||||
case "R19":
|
case "R19":
|
||||||
return 19
|
return 19
|
||||||
case "R20":
|
case "R20":
|
||||||
@@ -308,6 +313,8 @@ const (
|
|||||||
a64CondLT = 0xb
|
a64CondLT = 0xb
|
||||||
a64CondGT = 0xc
|
a64CondGT = 0xc
|
||||||
a64CondLE = 0xd
|
a64CondLE = 0xd
|
||||||
|
a64CondAL = 0xe
|
||||||
|
a64CondNV = 0xf
|
||||||
)
|
)
|
||||||
|
|
||||||
// arm64CondMap maps Go assembler condition mnemonics to AArch64 condition codes.
|
// arm64CondMap maps Go assembler condition mnemonics to AArch64 condition codes.
|
||||||
@@ -328,6 +335,8 @@ var arm64CondMap = map[string]uint32{
|
|||||||
"LT": a64CondLT,
|
"LT": a64CondLT,
|
||||||
"GT": a64CondGT,
|
"GT": a64CondGT,
|
||||||
"LE": a64CondLE,
|
"LE": a64CondLE,
|
||||||
|
"AL": a64CondAL,
|
||||||
|
"NV": a64CondNV,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- instruction format tags ----
|
// ---- instruction format tags ----
|
||||||
@@ -343,6 +352,7 @@ const (
|
|||||||
a64FADR // ADR/ADRP
|
a64FADR // ADR/ADRP
|
||||||
a64FEXTR // EXTR
|
a64FEXTR // EXTR
|
||||||
a64FBitfield // bitfield: BFI/BFXIL/SBFM/UBFM/BFM
|
a64FBitfield // bitfield: BFI/BFXIL/SBFM/UBFM/BFM
|
||||||
|
a64FBitfieldAlias // bitfield alias: BFI/BFXIL/SBFIZ/UBFIZ, ($lsb, Rn, $width, Rd)
|
||||||
a64FShift // shifts: LSL/LSR/ASR alias SBFM/UBFM, ROR aliases EXTR; register forms are two-source
|
a64FShift // shifts: LSL/LSR/ASR alias SBFM/UBFM, ROR aliases EXTR; register forms are two-source
|
||||||
a64FDPR4 // data-processing 4-register: MADD/MSUB, Ra in bits 14:10
|
a64FDPR4 // data-processing 4-register: MADD/MSUB, Ra in bits 14:10
|
||||||
a64FFP3 // FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, etc.
|
a64FFP3 // FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, etc.
|
||||||
@@ -364,6 +374,7 @@ const (
|
|||||||
a64FPair // load/store pair: LDP, STP, LDPW, STPW, FLDPD, FSTPD
|
a64FPair // load/store pair: LDP, STP, LDPW, STPW, FLDPD, FSTPD
|
||||||
a64FAcqRel // acquire/release: LDAR family, STLR family
|
a64FAcqRel // acquire/release: LDAR family, STLR family
|
||||||
a64FSys // system: BRK, SVC, DMB, DSB, ISB, DC, MRS, MSR, PRFM
|
a64FSys // system: BRK, SVC, DMB, DSB, ISB, DC, MRS, MSR, PRFM
|
||||||
|
a64FCASP // compare and swap pair: CASP
|
||||||
a64FCrypto2 // crypto 2-register: AESD, AESE, AESIMC, AESMC, SHA1H, ...
|
a64FCrypto2 // crypto 2-register: AESD, AESE, AESIMC, AESMC, SHA1H, ...
|
||||||
a64FCrypto3 // crypto 3-register: SHA1C, SHA256H, SHA512SU1, ...
|
a64FCrypto3 // crypto 3-register: SHA1C, SHA256H, SHA512SU1, ...
|
||||||
a64FSIMDV // SIMD 3-register with arrangement: VADD, VAND, VCMEQ, VZIP1, ...
|
a64FSIMDV // SIMD 3-register with arrangement: VADD, VAND, VCMEQ, VZIP1, ...
|
||||||
@@ -374,6 +385,7 @@ const (
|
|||||||
a64FDUP // SIMD element moves: VDUP, VMOV with element indices
|
a64FDUP // SIMD element moves: VDUP, VMOV with element indices
|
||||||
a64FVLDST // SIMD structure loads/stores: VLD1, VST1, VLD1R, VLD4R
|
a64FVLDST // SIMD structure loads/stores: VLD1, VST1, VLD1R, VLD4R
|
||||||
a64FShiftImm // SIMD shift by immediate: VSHL, VUSHR, VSRI
|
a64FShiftImm // SIMD shift by immediate: VSHL, VUSHR, VSRI
|
||||||
|
a64FVMoviImm // SIMD move immediate: VMOVI $imm8, Vd.B8/B16
|
||||||
a64FMoviLit // VMOVS/VMOVD/VMOVQ with a large constant (literal pool)
|
a64FMoviLit // VMOVS/VMOVD/VMOVQ with a large constant (literal pool)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -487,6 +499,16 @@ func init() {
|
|||||||
a64InstrTable["MADDW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24}
|
a64InstrTable["MADDW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24}
|
||||||
a64InstrTable["MSUB"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<15}
|
a64InstrTable["MSUB"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<15}
|
||||||
a64InstrTable["MSUBW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24 | 1<<15}
|
a64InstrTable["MSUBW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24 | 1<<15}
|
||||||
|
// The widening multiplies: a 64-bit result riding the same layout, the
|
||||||
|
// three-operand forms reading the accumulate register as ZR.
|
||||||
|
a64InstrTable["SMADDL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21}
|
||||||
|
a64InstrTable["UMADDL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<23}
|
||||||
|
a64InstrTable["SMSUBL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<15}
|
||||||
|
a64InstrTable["UMSUBL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<23 | 1<<15}
|
||||||
|
a64InstrTable["SMULL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 31<<10}
|
||||||
|
a64InstrTable["UMULL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<23 | 31<<10}
|
||||||
|
a64InstrTable["SMNEGL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<15 | 31<<10}
|
||||||
|
a64InstrTable["UMNEGL"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<21 | 1<<23 | 1<<15 | 31<<10}
|
||||||
|
|
||||||
// ---- move wide ----
|
// ---- move wide ----
|
||||||
// MOVZ/MOVN/MOVK
|
// MOVZ/MOVN/MOVK
|
||||||
@@ -536,14 +558,19 @@ func init() {
|
|||||||
// ---- bitfield ----
|
// ---- bitfield ----
|
||||||
a64InstrTable["BFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["BFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["BFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["BFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
||||||
|
// The four-operand bitfield aliases: ($lsb, Rn, $width, Rd).
|
||||||
|
a64InstrTable["BFI"] = a64Enc{format: a64FBitfieldAlias, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
||||||
|
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfieldAlias, op: 0<<31 | 1<<29 | 0x26<<23}
|
||||||
|
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfieldAlias, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
||||||
|
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfieldAlias, op: 0<<31 | 1<<29 | 0x26<<23}
|
||||||
|
a64InstrTable["SBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x93400000}
|
||||||
|
a64InstrTable["SBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x13000000}
|
||||||
|
a64InstrTable["UBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0xd3400000}
|
||||||
|
a64InstrTable["UBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x53000000}
|
||||||
a64InstrTable["SBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["SBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["SBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["SBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}
|
||||||
a64InstrTable["UBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["UBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["UBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["UBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
||||||
a64InstrTable["BFI"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
|
||||||
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
|
||||||
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
|
||||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
|
||||||
|
|
||||||
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
||||||
fp3 := map[string]uint32{
|
fp3 := map[string]uint32{
|
||||||
@@ -568,6 +595,10 @@ func init() {
|
|||||||
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
||||||
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
||||||
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
||||||
|
// The half-precision conversions: FPOP1S rows with the half source
|
||||||
|
// or destination (type 3), single and double beside them.
|
||||||
|
"FCVTHS": 0x1ee24000, "FCVTHD": 0x1ee2c000,
|
||||||
|
"FCVTSH": 0x1e23c000, "FCVTDH": 0x1e63c000,
|
||||||
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
||||||
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
||||||
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
||||||
@@ -716,6 +747,13 @@ func init() {
|
|||||||
"RBIT": 0xdac00000, "REV16": 0xdac00400, "REV32": 0xdac00800,
|
"RBIT": 0xdac00000, "REV16": 0xdac00400, "REV32": 0xdac00800,
|
||||||
"REV": 0xdac00c00, "CLZ": 0xdac01000, "CLS": 0xdac01400,
|
"REV": 0xdac00c00, "CLZ": 0xdac01000, "CLS": 0xdac01400,
|
||||||
"RBITW": 0x5ac00000, "REVW": 0x5ac00800, "CLZW": 0x5ac01000, "CLSW": 0x5ac01400,
|
"RBITW": 0x5ac00000, "REVW": 0x5ac00800, "CLZW": 0x5ac01000, "CLSW": 0x5ac01400,
|
||||||
|
// Extend and byte-reverse: the UBFM/SBFM aliases with imms fixing
|
||||||
|
// the source width.
|
||||||
|
"SXTB": 0x93401c00, "SXTBW": 0x13001c00, "SXTH": 0x93403c00,
|
||||||
|
"SXTHW": 0x13003c00, "SXTW": 0x93407c00,
|
||||||
|
"UXTB": 0x53001c00, "UXTBW": 0x53001c00, "UXTH": 0x53403c00,
|
||||||
|
"UXTHW": 0x53003c00, "UXTW": 0x53407c00,
|
||||||
|
"REV16W": 0x5ac00400,
|
||||||
}
|
}
|
||||||
for m, op := range dp1 {
|
for m, op := range dp1 {
|
||||||
a64InstrTable[m] = a64Enc{format: a64FDP1, op: op}
|
a64InstrTable[m] = a64Enc{format: a64FDP1, op: op}
|
||||||
@@ -734,7 +772,7 @@ func init() {
|
|||||||
a64InstrTable["CCMNW"] = a64Enc{format: a64FCondCmp, op: 0x3a400000}
|
a64InstrTable["CCMNW"] = a64Enc{format: a64FCondCmp, op: 0x3a400000}
|
||||||
|
|
||||||
// ---- system operations ----
|
// ---- system operations ----
|
||||||
for _, m := range []string{"BRK", "SVC", "DMB", "DSB", "ISB", "DC", "MRS", "MSR", "PRFM"} {
|
for _, m := range []string{"BRK", "SVC", "DMB", "DSB", "ISB", "CLREX", "HINT", "BTI", "HLT", "SMC", "HVC", "DCPS1", "DCPS2", "DCPS3", "DRPS", "ERET", "AUTIASP", "AUTIBSP", "AUTIA1716", "AUTIB1716", "SEVL", "SEV", "WFE", "WFI", "YIELD", "DC", "MRS", "MSR", "PRFM", "RPRFM", "SYS", "SYSL", "TLBI", "SB", "PACIASP", "PACIBSP"} {
|
||||||
a64InstrTable[m] = a64Enc{format: a64FSys}
|
a64InstrTable[m] = a64Enc{format: a64FSys}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,12 +785,20 @@ func init() {
|
|||||||
a64InstrTable["TBNZ"] = a64Enc{format: a64FTestBranch, op: 0x37000000}
|
a64InstrTable["TBNZ"] = a64Enc{format: a64FTestBranch, op: 0x37000000}
|
||||||
|
|
||||||
// ---- load/store pair (signed offset) ----
|
// ---- load/store pair (signed offset) ----
|
||||||
|
// The scale column of a64LoadTable does not reach the pair forms, so each
|
||||||
|
// entry states its own access width through the imm7 divisor the pair
|
||||||
|
// encoder derives from the opc field (8 for D, 4 for W and SW, 16 for Q).
|
||||||
a64InstrTable["LDP"] = a64Enc{format: a64FPair, op: 0xa9400000}
|
a64InstrTable["LDP"] = a64Enc{format: a64FPair, op: 0xa9400000}
|
||||||
a64InstrTable["LDPW"] = a64Enc{format: a64FPair, op: 0x29400000}
|
a64InstrTable["LDPW"] = a64Enc{format: a64FPair, op: 0x29400000}
|
||||||
|
a64InstrTable["LDPSW"] = a64Enc{format: a64FPair, op: 0x69400000}
|
||||||
a64InstrTable["STP"] = a64Enc{format: a64FPair, op: 0xa9000000}
|
a64InstrTable["STP"] = a64Enc{format: a64FPair, op: 0xa9000000}
|
||||||
a64InstrTable["STPW"] = a64Enc{format: a64FPair, op: 0x29000000}
|
a64InstrTable["STPW"] = a64Enc{format: a64FPair, op: 0x29000000}
|
||||||
a64InstrTable["FLDPD"] = a64Enc{format: a64FPair, op: 0x6d400000}
|
a64InstrTable["FLDPD"] = a64Enc{format: a64FPair, op: 0x6d400000}
|
||||||
a64InstrTable["FSTPD"] = a64Enc{format: a64FPair, op: 0x6d000000}
|
a64InstrTable["FSTPD"] = a64Enc{format: a64FPair, op: 0x6d000000}
|
||||||
|
a64InstrTable["FLDPS"] = a64Enc{format: a64FPair, op: 0x2d400000}
|
||||||
|
a64InstrTable["FSTPS"] = a64Enc{format: a64FPair, op: 0x2d000000}
|
||||||
|
a64InstrTable["FLDPQ"] = a64Enc{format: a64FPair, op: 0xad400000}
|
||||||
|
a64InstrTable["FSTPQ"] = a64Enc{format: a64FPair, op: 0xad000000}
|
||||||
|
|
||||||
// ---- acquire/release loads and stores ----
|
// ---- acquire/release loads and stores ----
|
||||||
a64InstrTable["LDAR"] = a64Enc{format: a64FAcqRel, op: 0xc8dffc00}
|
a64InstrTable["LDAR"] = a64Enc{format: a64FAcqRel, op: 0xc8dffc00}
|
||||||
@@ -770,11 +816,23 @@ func init() {
|
|||||||
lse := map[string]uint32{
|
lse := map[string]uint32{
|
||||||
"CASALD": 0xc8e0fc00,
|
"CASALD": 0xc8e0fc00,
|
||||||
"CASALW": 0x88e0fc00,
|
"CASALW": 0x88e0fc00,
|
||||||
|
"CASB": 0x08a07c00,
|
||||||
|
"CASAB": 0x08e07c00,
|
||||||
|
"CASH": 0x48a07c00,
|
||||||
|
"CASLD": 0xc8a0fc00,
|
||||||
|
"CASLH": 0x48a0fc00,
|
||||||
|
"CASAW": 0x88e07c00,
|
||||||
|
"CASAD": 0xc8e07c00,
|
||||||
|
"CASALH": 0x48e0fc00,
|
||||||
"LDADDALD": 0xf8e00000,
|
"LDADDALD": 0xf8e00000,
|
||||||
"LDADDALW": 0xb8e00000,
|
"LDADDALW": 0xb8e00000,
|
||||||
|
"LDADDAD": 0xf8a00000,
|
||||||
|
"LDADDAW": 0xb8a00000,
|
||||||
"LDCLRALB": 0x38e01000,
|
"LDCLRALB": 0x38e01000,
|
||||||
"LDCLRALW": 0xb8e01000,
|
"LDCLRALW": 0xb8e01000,
|
||||||
"LDCLRALD": 0xf8e01000,
|
"LDCLRALD": 0xf8e01000,
|
||||||
|
"LDCLRAD": 0xf8a01000,
|
||||||
|
"LDCLRAW": 0xb8a01000,
|
||||||
"LDORALB": 0x38e03000,
|
"LDORALB": 0x38e03000,
|
||||||
"LDORALW": 0xb8e03000,
|
"LDORALW": 0xb8e03000,
|
||||||
"LDORALD": 0xf8e03000,
|
"LDORALD": 0xf8e03000,
|
||||||
@@ -785,6 +843,81 @@ func init() {
|
|||||||
for m, op := range lse {
|
for m, op := range lse {
|
||||||
a64InstrTable[m] = a64Enc{format: a64FLSE, op: op}
|
a64InstrTable[m] = a64Enc{format: a64FLSE, op: op}
|
||||||
}
|
}
|
||||||
|
// The remaining width and ordering spellings of the same shapes, and the
|
||||||
|
// CAS compare-and-swap family, word-verified against go tool asm.
|
||||||
|
lseMore := map[string]uint32{
|
||||||
|
"LDADDAB": 0x38a00000,
|
||||||
|
"LDADDAH": 0x78a00000,
|
||||||
|
"LDADDALB": 0x38e00000,
|
||||||
|
"LDADDALH": 0x78e00000,
|
||||||
|
"LDADDLB": 0x38600000,
|
||||||
|
"LDADDLD": 0xf8600000,
|
||||||
|
"LDADDLH": 0x78600000,
|
||||||
|
"LDADDLW": 0xb8600000,
|
||||||
|
"LDCLRAB": 0x38a01000,
|
||||||
|
"LDCLRAH": 0x78a01000,
|
||||||
|
"LDCLRALH": 0x78e01000,
|
||||||
|
"LDCLRB": 0x38201000,
|
||||||
|
"LDCLRD": 0xf8201000,
|
||||||
|
"LDCLRH": 0x78201000,
|
||||||
|
"LDCLRLB": 0x38601000,
|
||||||
|
"LDCLRLD": 0xf8601000,
|
||||||
|
"LDCLRLH": 0x78601000,
|
||||||
|
"LDCLRLW": 0xb8601000,
|
||||||
|
"LDCLRW": 0xb8201000,
|
||||||
|
"LDEORAB": 0x38a02000,
|
||||||
|
"LDEORAD": 0xf8a02000,
|
||||||
|
"LDEORAH": 0x78a02000,
|
||||||
|
"LDEORALB": 0x38e02000,
|
||||||
|
"LDEORALD": 0xf8e02000,
|
||||||
|
"LDEORALH": 0x78e02000,
|
||||||
|
"LDEORALW": 0xb8e02000,
|
||||||
|
"LDEORAW": 0xb8a02000,
|
||||||
|
"LDEORB": 0x38202000,
|
||||||
|
"LDEORD": 0xf8202000,
|
||||||
|
"LDEORH": 0x78202000,
|
||||||
|
"LDEORLB": 0x38602000,
|
||||||
|
"LDEORLD": 0xf8602000,
|
||||||
|
"LDEORLH": 0x78602000,
|
||||||
|
"LDEORLW": 0xb8602000,
|
||||||
|
"LDEORW": 0xb8202000,
|
||||||
|
"LDORAB": 0x38a03000,
|
||||||
|
"LDORAD": 0xf8a03000,
|
||||||
|
"LDORAH": 0x78a03000,
|
||||||
|
"LDORALH": 0x78e03000,
|
||||||
|
"LDORAW": 0xb8a03000,
|
||||||
|
"LDORB": 0x38203000,
|
||||||
|
"LDORD": 0xf8203000,
|
||||||
|
"LDORH": 0x78203000,
|
||||||
|
"LDORLB": 0x38603000,
|
||||||
|
"LDORLD": 0xf8603000,
|
||||||
|
"LDORLH": 0x78603000,
|
||||||
|
"LDORLW": 0xb8603000,
|
||||||
|
"LDORW": 0xb8203000,
|
||||||
|
"SWPAB": 0x38a08000,
|
||||||
|
"SWPAD": 0xf8a08000,
|
||||||
|
"SWPAH": 0x78a08000,
|
||||||
|
"SWPALH": 0x78e08000,
|
||||||
|
"SWPAW": 0xb8a08000,
|
||||||
|
"SWPB": 0x38208000,
|
||||||
|
"SWPH": 0x78208000,
|
||||||
|
"SWPLB": 0x38608000,
|
||||||
|
"SWPLD": 0xf8608000,
|
||||||
|
"SWPLH": 0x78608000,
|
||||||
|
"SWPLW": 0xb8608000,
|
||||||
|
"CASAD": 0xc8e07c00,
|
||||||
|
"CASALB": 0x08e0fc00,
|
||||||
|
"CASLW": 0x88a0fc00,
|
||||||
|
}
|
||||||
|
for m, op := range lseMore {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FLSE, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare and swap pair: the second register of each pair is implicit
|
||||||
|
// (Rs+1 and Rt+1), so the encoding carries Rs and Rt alone over a preset
|
||||||
|
// fixed field (asm7.go atomicCASP).
|
||||||
|
a64InstrTable["CASPD"] = a64Enc{format: a64FCASP, op: 1<<30 | 0x41<<21 | 0x1f<<10}
|
||||||
|
a64InstrTable["CASPW"] = a64Enc{format: a64FCASP, op: 0x41<<21 | 0x1f<<10}
|
||||||
|
|
||||||
// ---- carry-setting/carry-using arithmetic and widening multiply ----
|
// ---- carry-setting/carry-using arithmetic and widening multiply ----
|
||||||
// MUL and SMULH/UMULH are the MADD/MSUB layout with the accumulate
|
// MUL and SMULH/UMULH are the MADD/MSUB layout with the accumulate
|
||||||
@@ -794,6 +927,11 @@ func init() {
|
|||||||
"ADCS": 0xba000000, "ADCSW": 0x3a000000,
|
"ADCS": 0xba000000, "ADCSW": 0x3a000000,
|
||||||
"SBC": 0xda000000, "SBCW": 0x5a000000,
|
"SBC": 0xda000000, "SBCW": 0x5a000000,
|
||||||
"SBCS": 0xfa000000, "SBCSW": 0x7a000000,
|
"SBCS": 0xfa000000, "SBCSW": 0x7a000000,
|
||||||
|
// MNEG/MSUB and NGC/SBC with the complementing register preset to ZR.
|
||||||
|
"MNEG": 0x9b00fc00, "MNEGW": 0x1b00fc00,
|
||||||
|
"NGC": 0xda000000, "NGCW": 0x5a000000,
|
||||||
|
"NGCS": 0xfa000000, "NGCSW": 0x7a000000,
|
||||||
|
"NEGSW": 0x6b000000,
|
||||||
"MUL": 0x9b007c00, "MULW": 0x1b007c00,
|
"MUL": 0x9b007c00, "MULW": 0x1b007c00,
|
||||||
"SMULH": 0x9b407c00, "UMULH": 0x9bc07c00,
|
"SMULH": 0x9b407c00, "UMULH": 0x9bc07c00,
|
||||||
}
|
}
|
||||||
@@ -832,9 +970,17 @@ func init() {
|
|||||||
a64InstrTable["VMOVS"] = a64Enc{format: a64FMoviLit, op: 0xbd400000}
|
a64InstrTable["VMOVS"] = a64Enc{format: a64FMoviLit, op: 0xbd400000}
|
||||||
a64InstrTable["VMOVD"] = a64Enc{format: a64FMoviLit, op: 0xfd400000}
|
a64InstrTable["VMOVD"] = a64Enc{format: a64FMoviLit, op: 0xfd400000}
|
||||||
a64InstrTable["VMOVQ"] = a64Enc{format: a64FMoviLit, op: 0x3dc00000}
|
a64InstrTable["VMOVQ"] = a64Enc{format: a64FMoviLit, op: 0x3dc00000}
|
||||||
|
a64InstrTable["VMOVI"] = a64Enc{format: a64FVMoviImm}
|
||||||
a64InstrTable["VSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 21<<10}
|
a64InstrTable["VSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 21<<10}
|
||||||
a64InstrTable["VUSHR"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 1<<10}
|
a64InstrTable["VUSHR"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 1<<10}
|
||||||
a64InstrTable["VSRI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 17<<10}
|
a64InstrTable["VSRI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 17<<10}
|
||||||
|
a64InstrTable["VSSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 1<<10}
|
||||||
|
a64InstrTable["VSRA"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 7<<10}
|
||||||
|
a64InstrTable["VUSRA"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 5<<10}
|
||||||
|
a64InstrTable["VSRSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 9<<10}
|
||||||
|
a64InstrTable["VSLI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 21<<10}
|
||||||
|
a64InstrTable["VSQSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 29<<10}
|
||||||
|
a64InstrTable["VUQSHL"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 29<<10}
|
||||||
a64InstrTable["VLD1"] = a64Enc{format: a64FVLDST}
|
a64InstrTable["VLD1"] = a64Enc{format: a64FVLDST}
|
||||||
a64InstrTable["VLD1.P"] = a64Enc{format: a64FVLDST, op: 1}
|
a64InstrTable["VLD1.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
a64InstrTable["VST1"] = a64Enc{format: a64FVLDST}
|
a64InstrTable["VST1"] = a64Enc{format: a64FVLDST}
|
||||||
@@ -843,16 +989,37 @@ func init() {
|
|||||||
a64InstrTable["VLD1R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
a64InstrTable["VLD1R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
a64InstrTable["VLD4R"] = a64Enc{format: a64FVLDST}
|
a64InstrTable["VLD4R"] = a64Enc{format: a64FVLDST}
|
||||||
a64InstrTable["VLD4R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
a64InstrTable["VLD4R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
// Multi-register structure accesses beyond VLD1/VST1: VLD2/VLD3/VLD4 and
|
||||||
|
// the replicate loads VLD2R/VLD3R, each with the post-index spelling.
|
||||||
|
a64InstrTable["VLD2"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD2.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD3"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD3.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD4"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD4.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD2R"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD2R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD3R"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD3R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST2"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST2.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST3"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST3.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST4"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST4.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64SimdVSpec is one arrangement-aware SIMD instruction: the 8B base word,
|
// a64SimdVSpec is one arrangement-aware SIMD instruction: the 8B base word,
|
||||||
// the set of arrangements it accepts as a bitmask over the a64Arr index and,
|
// the set of arrangements it accepts as a bitmask over the a64Arr index,
|
||||||
// for instructions that exist at a single arrangement and carry that
|
// the fixed flag for instructions that exist at a single arrangement and
|
||||||
// arrangement's bits inside the base already, the fixed flag.
|
// carry that arrangement's bits inside the base already, and the fp flag for
|
||||||
|
// the FP rows, whose size field is the single FP bit (a64FPArrBits) instead
|
||||||
|
// of the integer size.
|
||||||
type a64SimdVSpec struct {
|
type a64SimdVSpec struct {
|
||||||
base uint32
|
base uint32
|
||||||
arrs uint16
|
arrs uint16
|
||||||
fixed bool
|
fixed bool
|
||||||
|
fp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64Arr names the vector arrangements the encoders deal with, indexed by
|
// a64Arr names the vector arrangements the encoders deal with, indexed by
|
||||||
@@ -899,6 +1066,36 @@ func a64ElemLetter(s string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fpSimdArrs bounds the arrangements the FP SIMD forms accept: S and D only,
|
||||||
|
// the toolchain rejecting the half-width spellings outright ("invalid
|
||||||
|
// arrangement"). fpAcrossArrs bounds the across-vector reductions, which do
|
||||||
|
// take the half width.
|
||||||
|
var fpSimdArrs = uint16(1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D)
|
||||||
|
var fpAcrossArrs = uint16(1 << a64Arr4S)
|
||||||
|
|
||||||
|
// a64FPArrBits carries the bits an arrangement contributes to the FP SIMD
|
||||||
|
// words: the FP size field is a single bit at bit 22 (0 for the S widths, 1
|
||||||
|
// for the D widths; the toolchain carries no half-width FP rows) and the
|
||||||
|
// 128-bit flag sits at bit 30. Word-verified against go tool asm.
|
||||||
|
var a64FPArrBits = [a64ArrCount]uint32{
|
||||||
|
a64Arr2S: 0,
|
||||||
|
a64Arr4S: 1 << 30,
|
||||||
|
a64Arr2D: 1<<30 | 1<<22,
|
||||||
|
a64Arr4H: 0,
|
||||||
|
a64Arr8H: 1 << 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdQOnly names the forms whose arrangement contributes the 128-bit
|
||||||
|
// flag alone, without the size bits: the FP converts, the FP round-to-integral
|
||||||
|
// and pairwise compares among them. Word-verified against go tool asm.
|
||||||
|
var a64SimdQOnly = map[string]bool{
|
||||||
|
"VSCVTF": true, "VUCVTF": true, "VFCVTZS": true, "VFCVTZU": true,
|
||||||
|
"VFABS": true, "VFNEG": true, "VFSQRT": true,
|
||||||
|
"VFRINTN": true, "VFRINTP": true, "VFRINTM": true, "VFRINTZ": true,
|
||||||
|
"VFADDP": true, "VFMAXP": true, "VFMAXNMP": true,
|
||||||
|
"VFMAXV": true, "VFMAXNMV": true,
|
||||||
|
}
|
||||||
|
|
||||||
// a64ArrBits carries the fixed bits an arrangement contributes to the
|
// a64ArrBits carries the fixed bits an arrangement contributes to the
|
||||||
// three-same word shape: the element size at bits 23:22 and the 128-bit
|
// three-same word shape: the element size at bits 23:22 and the 128-bit
|
||||||
// flag at bit 30. Bit 29 belongs to the instruction's own base.
|
// flag at bit 30. Bit 29 belongs to the instruction's own base.
|
||||||
@@ -918,29 +1115,211 @@ var a64ArrBits = [a64ArrCount]uint32{
|
|||||||
// instructions (word = base | arrBits | Rm<<16 | Rn<<5 | Rd). Every base
|
// instructions (word = base | arrBits | Rm<<16 | Rn<<5 | Rd). Every base
|
||||||
// word and arrangement bit was read off go tool asm.
|
// word and arrangement bit was read off go tool asm.
|
||||||
var a64SimdVTable = map[string]a64SimdVSpec{
|
var a64SimdVTable = map[string]a64SimdVSpec{
|
||||||
"VADD": {0x0e208400, 0x7f, false},
|
"VADD": {0x0e208400, 0x7f, false, false},
|
||||||
"VSUB": {0x2e208400, 0x7f, false},
|
"VSUB": {0x2e208400, 0x7f, false, false},
|
||||||
"VMUL": {0x0e209c00, 0x3f, false}, // no 2D: integer multiply stops at 4S
|
"VMUL": {0x0e209c00, 0x3f, false, false}, // no 2D: integer multiply stops at 4S
|
||||||
"VAND": {0x0e201c00, 0x03, false}, // logical ops accept 8B and 16B only
|
"VAND": {0x0e201c00, 0x03, false, false}, // logical ops accept 8B and 16B only
|
||||||
"VEOR": {0x2e201c00, 0x03, false},
|
"VEOR": {0x2e201c00, 0x03, false, false},
|
||||||
"VORR": {0x0ea01c00, 0x03, false},
|
"VORR": {0x0ea01c00, 0x03, false, false},
|
||||||
"VADDP": {0x0e20bc00, 0x7f, false},
|
"VADDP": {0x0e20bc00, 0x7f, false, false},
|
||||||
"VZIP1": {0x0e003800, 0x7f, false},
|
"VZIP1": {0x0e003800, 0x7f, false, false},
|
||||||
"VZIP2": {0x0e007800, 0x7f, false},
|
"VZIP2": {0x0e007800, 0x7f, false, false},
|
||||||
"VCMEQ": {0x2e208c00, 0x7f, false},
|
"VCMEQ": {0x2e208c00, 0x7f, false, false},
|
||||||
"VRAX1": {0xce608c00, 1 << a64Arr2D, true}, // SHA3 group, D2 only
|
"VCMGE": {0x0e203c00, 0x7f, false, false},
|
||||||
"VPMULL": {0x0e20e000, 1<<a64Arr8B | 1<<a64ArrD1, false},
|
"VCMGT": {0x0e203400, 0x7f, false, false},
|
||||||
"VPMULL2": {0x0e20e000, 1<<a64Arr16B | 1<<a64Arr2D, false},
|
"VCMHI": {0x2e203400, 0x7f, false, false},
|
||||||
|
"VCMHS": {0x2e203c00, 0x7f, false, false},
|
||||||
|
// FP compares take S and D arrangements only (the toolchain rejects the
|
||||||
|
// byte and half forms), and VFCMLE/VFCMLT have no register form at all.
|
||||||
|
"VFCMEQ": {0x0e20e400, fpSimdArrs, false, true},
|
||||||
|
"VFCMGE": {0x2e20e400, fpSimdArrs, false, true},
|
||||||
|
"VFCMGT": {0x2ea0e400, fpSimdArrs, false, true},
|
||||||
|
// FP arithmetic shares the same arrangement restriction.
|
||||||
|
"VFADD": {0x0e20d400, fpSimdArrs, false, true},
|
||||||
|
"VFSUB": {0x0ea0d400, fpSimdArrs, false, true},
|
||||||
|
"VFMUL": {0x2e20dc00, fpSimdArrs, false, true},
|
||||||
|
"VFDIV": {0x2e20fc00, fpSimdArrs, false, true},
|
||||||
|
"VFMAX": {0x0e20f400, fpSimdArrs, false, true},
|
||||||
|
"VFMIN": {0x0ea0f400, fpSimdArrs, false, true},
|
||||||
|
"VFMAXNM": {0x0e20c400, fpSimdArrs, false, true},
|
||||||
|
"VFMINNM": {0x0ea0c400, fpSimdArrs, false, true},
|
||||||
|
"VFMLA": {0x0e20cc00, fpSimdArrs, false, true},
|
||||||
|
"VFMLS": {0x0ea0cc00, fpSimdArrs, false, true},
|
||||||
|
// Saturating, halving, polynomial and pairwise arithmetic, the logical
|
||||||
|
// VBIT/VBSL family and the FP pairwise forms: word-verified against go
|
||||||
|
// tool asm.
|
||||||
|
"VBIC": {0x0e601c00, 0x03, false, false}, // logical ops accept 8B and 16B only
|
||||||
|
"VBIF": {0x2ee01c00, 0x03, false, false},
|
||||||
|
"VBIT": {0x6ea01c00, 0x03, false, false},
|
||||||
|
"VBSL": {0x6e601c00, 0x03, false, false},
|
||||||
|
"VCMTST": {0x0e208c00, 0x7f, false, false},
|
||||||
|
"VFADDP": {0x2e20d400, fpSimdArrs, false, true},
|
||||||
|
"VFMAXP": {0x2e20f400, fpSimdArrs, false, true},
|
||||||
|
"VFMINP": {0x6ea0f400, fpSimdArrs, false, true},
|
||||||
|
"VFMAXNMP": {0x2e20c400, fpSimdArrs, false, true},
|
||||||
|
"VFMINNMP": {0x6ea0c400, fpSimdArrs, false, true},
|
||||||
|
"VMLA": {0x4ea09400, 0x3f, false, false}, // no 2D: integer multiply stops at 4S
|
||||||
|
"VMLS": {0x6ea09400, 0x3f, false, false},
|
||||||
|
"VORN": {0x4ee01c00, 0x03, false, false},
|
||||||
|
"VSHADD": {0x4ea00400, 0x7f, false, false},
|
||||||
|
"VSRHADD": {0x4ea01400, 0x7f, false, false},
|
||||||
|
"VUHADD": {0x6ea00400, 0x7f, false, false},
|
||||||
|
"VURHADD": {0x6ea01400, 0x7f, false, false},
|
||||||
|
"VSMAX": {0x4ea06400, 0x3f, false, false}, // no 2D: integer max stops at 4S
|
||||||
|
"VSMIN": {0x4ea06c00, 0x3f, false, false},
|
||||||
|
"VSMAXP": {0x4ea0a400, 0x3f, false, false},
|
||||||
|
"VSMINP": {0x4ea0ac00, 0x3f, false, false},
|
||||||
|
"VUMAX": {0x2e206400, 0x3f, false, false},
|
||||||
|
"VUMIN": {0x2e206c00, 0x3f, false, false},
|
||||||
|
"VUMAXP": {0x6ea0a400, 0x3f, false, false},
|
||||||
|
"VUMINP": {0x6ea0ac00, 0x3f, false, false},
|
||||||
|
"VSQADD": {0x4ea00c00, 0x7f, false, false},
|
||||||
|
"VUQADD": {0x6ea00c00, 0x7f, false, false},
|
||||||
|
"VSQSUB": {0x4ea02c00, 0x7f, false, false},
|
||||||
|
"VUQSUB": {0x6ea02c00, 0x7f, false, false},
|
||||||
|
"VSSHL": {0x0e204400, 0x7f, false, false},
|
||||||
|
"VUSHL": {0x2e204400, 0x7f, false, false},
|
||||||
|
"VUZP1": {0x0e001800, 0x7f, false, false},
|
||||||
|
"VUZP2": {0x4ec05800, 0x7f, false, false},
|
||||||
|
"VTRN1": {0x4ec02800, 0x7f, false, false},
|
||||||
|
"VTRN2": {0x4ec06800, 0x7f, false, false},
|
||||||
|
"VRAX1": {0xce608c00, 1 << a64Arr2D, true, false}, // SHA3 group, D2 only
|
||||||
|
"VPMULL": {0x0e20e000, 1<<a64Arr8B | 1<<a64ArrD1, false, false},
|
||||||
|
"VPMULL2": {0x0e20e000, 1<<a64Arr16B | 1<<a64Arr2D, false, false},
|
||||||
|
// Saturating shifts, register forms (the immediate spellings route to
|
||||||
|
// a64FShiftImm).
|
||||||
|
"VSQSHL": {0x0e204c00, 0x7f, false, false},
|
||||||
|
"VUQSHL": {0x2e204c00, 0x7f, false, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdNLForm classifies the narrow/long/wide SIMD families whose
|
||||||
|
// arrangement does not travel on every operand: the encoding's size and Q
|
||||||
|
// bits read off one designated operand and the element widths pair up across
|
||||||
|
// the operands.
|
||||||
|
type a64SimdNLForm uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
a64NLTwoNarrow a64SimdNLForm = iota // (Vn.wide, Vd.narrow): size/Q from Vd
|
||||||
|
a64NLTwoLong // (Vn.narrow, Vd.long): size/Q from Vn
|
||||||
|
a64NLThreeLongMul // (Vm.narrow, Vn.narrow, Vd.long): size/Q from Vn
|
||||||
|
a64NLThreeWide // (Vm.narrow, Vn.wide, Vd.wide): size/Q from Vn
|
||||||
|
a64NLThreeLongShift // ($sh, Vn.narrow, Vd.long): size/Q from Vn, immh = esize+sh
|
||||||
|
a64NLThreeNarrowShift // ($sh, Vn.wide, Vd.narrow): size/Q from Vd, immh = esize-sh
|
||||||
|
)
|
||||||
|
|
||||||
|
// a64SimdNLSpec is one narrow/long/wide instruction: the base word (U, opcode
|
||||||
|
// and fixed bits positioned) and the arrangement form. qonly marks the FCVT
|
||||||
|
// family, whose size field is fixed in the base and only the Q bit follows
|
||||||
|
// the driving arrangement.
|
||||||
|
type a64SimdNLSpec struct {
|
||||||
|
base uint32
|
||||||
|
form a64SimdNLForm
|
||||||
|
qonly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdNLTable holds the families the arrangement-driven three-register and
|
||||||
|
// two-register encoders cannot express. The .2 spellings force the 128-bit
|
||||||
|
// side of the pair through their operand arrangements, so the base carries no
|
||||||
|
// arrangement bits of its own.
|
||||||
|
var a64SimdNLTable = map[string]a64SimdNLSpec{
|
||||||
|
"VSHRN": {0x0f008400, a64NLThreeNarrowShift, false},
|
||||||
|
"VSHRN2": {0x0f008400, a64NLThreeNarrowShift, false},
|
||||||
|
"VSXTL": {0x0f00a400, a64NLTwoLong, false},
|
||||||
|
"VSXTL2": {0x0f00a400, a64NLTwoLong, false},
|
||||||
|
"VUXTL": {0x2f00a400, a64NLTwoLong, false},
|
||||||
|
"VUXTL2": {0x2f00a400, a64NLTwoLong, false},
|
||||||
|
"VXTN": {0x0e212800, a64NLTwoNarrow, false},
|
||||||
|
"VXTN2": {0x0e212800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTN": {0x0e214800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTN2": {0x0e214800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTUN": {0x2e212800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTUN2": {0x2e212800, a64NLTwoNarrow, false},
|
||||||
|
"VUQXTN": {0x2e214800, a64NLTwoNarrow, false},
|
||||||
|
"VUQXTN2": {0x2e214800, a64NLTwoNarrow, false},
|
||||||
|
"VFCVTN": {0x0e616800, a64NLTwoNarrow, true},
|
||||||
|
"VFCVTN2": {0x0e616800, a64NLTwoNarrow, true},
|
||||||
|
"VFCVTL": {0x0e617800, a64NLTwoLong, true},
|
||||||
|
"VFCVTL2": {0x0e617800, a64NLTwoLong, true},
|
||||||
|
"VSSHLL": {0x0f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VSSHLL2": {0x0f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUSHLL": {0x2f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUSHLL2": {0x2f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUADDW": {0x2e201000, a64NLThreeWide, false},
|
||||||
|
"VUADDW2": {0x2e201000, a64NLThreeWide, false},
|
||||||
|
"VUMULL": {0x2e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VUMULL2": {0x2e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VSMULL": {0x0e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VSMULL2": {0x0e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLAL": {0x2e208000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLAL2": {0x2e208000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLAL": {0x0e208000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLAL2": {0x0e208000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLSL": {0x2e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLSL2": {0x2e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLSL": {0x0e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLSL2": {0x0e20a000, a64NLThreeLongMul, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdVZero holds the compare-against-zero words of the SIMD compares
|
||||||
|
// spelled with a $0 first operand (word = base | arrBits | Rn<<5 | Rd).
|
||||||
|
// VCMHI and VCMHS have no zero form: the toolchain reports an illegal
|
||||||
|
// combination for them, so they stay out and the encoder rejects the shape.
|
||||||
|
var a64SimdVZero = map[string]uint32{
|
||||||
|
"VCMEQ": 0x0e209800,
|
||||||
|
"VCMGT": 0x0e208800,
|
||||||
|
"VCMGE": 0x2e208800,
|
||||||
|
"VCMLT": 0x0e20a800,
|
||||||
|
"VCMLE": 0x2e209800,
|
||||||
|
// FP compares against (0.0): the register forms above carry the U and op
|
||||||
|
// bits; the zero forms reshape them.
|
||||||
|
"VFCMEQ": 0x0ea0d800,
|
||||||
|
"VFCMGE": 0x2ea0c800,
|
||||||
|
"VFCMGT": 0x0ea0c800,
|
||||||
|
"VFCMLE": 0x2ea0d800,
|
||||||
|
"VFCMLT": 0x0ea0e800,
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64SimdV2Table holds the arrangement-aware two-register SIMD instructions
|
// a64SimdV2Table holds the arrangement-aware two-register SIMD instructions
|
||||||
// (word = base | arrBits | Rn<<5 | Rd). VMOV is served from here too, with
|
// (word = base | arrBits | Rn<<5 | Rd). VMOV is served from here too, with
|
||||||
// the register pair spelling ORR Vd, Vn, Vm.
|
// the register pair spelling ORR Vd, Vn, Vm.
|
||||||
var a64SimdV2Table = map[string]a64SimdVSpec{
|
var a64SimdV2Table = map[string]a64SimdVSpec{
|
||||||
"VREV32": {0x2e200800, 1<<a64Arr8B | 1<<a64Arr16B | 1<<a64Arr4H | 1<<a64Arr8H, false},
|
"VREV32": {0x2e200800, 1<<a64Arr8B | 1<<a64Arr16B | 1<<a64Arr4H | 1<<a64Arr8H, false, false},
|
||||||
"VREV64": {0x0e200800, 0x3f, false},
|
"VREV64": {0x0e200800, 0x3f, false, false},
|
||||||
"VUADDLV": {0x2e303800, 0x3f, false},
|
"VREV16": {0x0e201800, 1<<a64Arr8B | 1<<a64Arr16B, false, false},
|
||||||
"VMOV": {0x0ea01c00, 1<<a64Arr8B | 1<<a64Arr16B, false},
|
"VUADDLV": {0x2e303800, 0x3f, false, false},
|
||||||
|
"VMOV": {0x0ea01c00, 1<<a64Arr8B | 1<<a64Arr16B, false, false},
|
||||||
|
// Two-register data-processing across one arrangement.
|
||||||
|
"VABS": {0x0e20b800, 0x7f, false, false},
|
||||||
|
"VNEG": {0x2e20b800, 0x7f, false, false},
|
||||||
|
"VCLS": {0x0e204800, 0x7f, false, false},
|
||||||
|
"VCLZ": {0x2e204800, 0x7f, false, false},
|
||||||
|
"VCNT": {0x0e205800, 0x7f, false, false},
|
||||||
|
"VNOT": {0x2e205800, 0x7f, false, false},
|
||||||
|
"VSQABS": {0x0e207800, 0x7f, false, false},
|
||||||
|
"VSQNEG": {0x2e207800, 0x7f, false, false},
|
||||||
|
"VRBIT": {0x2e605800, 0x03, false, false}, // 8B and 16B only
|
||||||
|
"VSCVTF": {0x4e21d800, fpSimdArrs, false, true},
|
||||||
|
"VUCVTF": {0x6e21d800, fpSimdArrs, false, true},
|
||||||
|
"VFCVTZS": {0x4ea1b800, fpSimdArrs, false, true},
|
||||||
|
"VFCVTZU": {0x6ea1b800, fpSimdArrs, false, true},
|
||||||
|
"VFABS": {0x0ea0f800, fpSimdArrs, false, true},
|
||||||
|
"VFNEG": {0x2ea0f800, fpSimdArrs, false, true},
|
||||||
|
"VFSQRT": {0x2ea1f800, fpSimdArrs, false, true},
|
||||||
|
"VFRINTN": {0x0e218800, fpSimdArrs, false, true},
|
||||||
|
"VFRINTP": {0x0ea18800, fpSimdArrs, false, true},
|
||||||
|
"VFRINTM": {0x0e219800, fpSimdArrs, false, true},
|
||||||
|
"VFRINTZ": {0x0ea19800, fpSimdArrs, false, true},
|
||||||
|
// Across-vector reductions: the operand arrangement rides as usual and
|
||||||
|
// the destination stays a bare V register.
|
||||||
|
"VADDV": {0x0e31b800, 0x3f, false, false},
|
||||||
|
"VSMAXV": {0x0e30a800, 0x3f, false, false},
|
||||||
|
"VSMINV": {0x0e31a800, 0x3f, false, false},
|
||||||
|
"VUMAXV": {0x2e30a800, 0x3f, false, false},
|
||||||
|
"VUMINV": {0x2e31a800, 0x3f, false, false},
|
||||||
|
"VFMAXV": {0x2e30f800, fpAcrossArrs, false, false},
|
||||||
|
"VFMINV": {0x2eb0f800, fpAcrossArrs, false, false},
|
||||||
|
"VFMAXNMV": {0x2e30c800, fpAcrossArrs, false, false},
|
||||||
|
"VFMINNMV": {0x2eb0c800, fpAcrossArrs, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64CryptoArr is the arrangement each crypto instruction's operands must
|
// a64CryptoArr is the arrangement each crypto instruction's operands must
|
||||||
@@ -976,6 +1355,7 @@ var a64MRSOps = map[string]uint32{
|
|||||||
// MSR Rn, <sysreg>; the source register rides bits 4:0.
|
// MSR Rn, <sysreg>; the source register rides bits 4:0.
|
||||||
var a64MSRRegOps = map[string]uint32{
|
var a64MSRRegOps = map[string]uint32{
|
||||||
"NZCV": 0xd51b4200, "FPCR": 0xd51b4400, "FPSR": 0xd51b4420,
|
"NZCV": 0xd51b4200, "FPCR": 0xd51b4400, "FPSR": 0xd51b4420,
|
||||||
|
"ELR_EL1": 0xd5184020,
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64MSROps maps the system register names GOROOT writes to their fixed
|
// a64MSROps maps the system register names GOROOT writes to their fixed
|
||||||
@@ -1001,6 +1381,16 @@ var a64PRFOps = map[string]int{
|
|||||||
var a64VLD1Base = [5]uint32{0, 0x0c407000, 0x0c40a000, 0x0c406000, 0x0c402000}
|
var a64VLD1Base = [5]uint32{0, 0x0c407000, 0x0c40a000, 0x0c406000, 0x0c402000}
|
||||||
var a64VST1Base = [5]uint32{0, 0x0c007000, 0x0c00a000, 0x0c006000, 0x0c002000}
|
var a64VST1Base = [5]uint32{0, 0x0c007000, 0x0c00a000, 0x0c006000, 0x0c002000}
|
||||||
|
|
||||||
|
// a64VLDNBase and a64VSTNBase hold the VLD2/VLD3/VLD4 and VST2/VST3/VST4
|
||||||
|
// fixed words (indexed by register count 2..4): the opcode field at bits
|
||||||
|
// 15:12 carries the access kind.
|
||||||
|
var a64VLDNBase = [5]uint32{0, 0, 0x0c408000, 0x0c404000, 0x0c400000}
|
||||||
|
var a64VSTNBase = [5]uint32{0, 0, 0x0c008000, 0x0c004000, 0x0c000000}
|
||||||
|
|
||||||
|
// a64VLDNReplicate holds the VLD2R/VLD3R fixed words beside the existing
|
||||||
|
// VLD1R (0x0d40c000) and VLD4R (0x0d60e000) bases.
|
||||||
|
var a64VLDNReplicate = [5]uint32{0, 0x0d40c000, 0x0d60c000, 0x0d40e000, 0x0d60e000}
|
||||||
|
|
||||||
// a64Vec is a parsed vector operand: the register number, the arrangement
|
// a64Vec is a parsed vector operand: the register number, the arrangement
|
||||||
// ("" when the operand spells none) and, for element forms, the lane index.
|
// ("" when the operand spells none) and, for element forms, the lane index.
|
||||||
type a64Vec struct {
|
type a64Vec struct {
|
||||||
@@ -1124,29 +1514,46 @@ type a64LSType struct {
|
|||||||
size int // 0=byte, 1=half, 2=word, 3=dword
|
size int // 0=byte, 1=half, 2=word, 3=dword
|
||||||
V int // 0=integer, 1=FP
|
V int // 0=integer, 1=FP
|
||||||
opc int // 00=store/unsigned load, 01=store FP, 10=signed load, 11=load FP
|
opc int // 00=store/unsigned load, 01=store FP, 10=signed load, 11=load FP
|
||||||
|
scale int // access width in bytes; the unsigned offset divides by it
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64LoadTable maps MOV width mnemonics to their load/store encoding parameters.
|
// a64LoadTable maps MOV width mnemonics to their load/store encoding parameters.
|
||||||
// For loads, opc selects signed vs unsigned; for stores, we flip the opc.
|
// For loads, opc selects signed vs unsigned; for stores, we flip the opc.
|
||||||
var a64LoadTable = map[string]a64LSType{
|
var a64LoadTable = map[string]a64LSType{
|
||||||
"MOVD": {3, 0, 1}, // LDR X (64-bit, unsigned offset)
|
"MOVD": {3, 0, 1, 8}, // LDR X (64-bit, unsigned offset)
|
||||||
"MOVWU": {2, 0, 1}, // LDR W (32-bit unsigned)
|
"MOVWU": {2, 0, 1, 4}, // LDR W (32-bit unsigned)
|
||||||
"MOVW": {2, 0, 2}, // LDRSW (32-bit signed → 64-bit)
|
"MOVW": {2, 0, 2, 4}, // LDRSW (32-bit signed → 64-bit)
|
||||||
"MOVHU": {1, 0, 1}, // LDRH (16-bit unsigned)
|
"MOVHU": {1, 0, 1, 2}, // LDRH (16-bit unsigned)
|
||||||
"MOVH": {1, 0, 2}, // LDRSH (16-bit signed)
|
"MOVH": {1, 0, 2, 2}, // LDRSH (16-bit signed)
|
||||||
"MOVBU": {0, 0, 1}, // LDRB (8-bit unsigned)
|
"MOVBU": {0, 0, 1, 1}, // LDRB (8-bit unsigned)
|
||||||
"MOVB": {0, 0, 2}, // LDRSB (8-bit signed)
|
"MOVB": {0, 0, 2, 1}, // LDRSB (8-bit signed)
|
||||||
"FMOVS": {2, 1, 1}, // LDR S (32-bit FP)
|
"FMOVS": {2, 1, 1, 4}, // LDR S (32-bit FP)
|
||||||
"FMOVD": {3, 1, 1}, // LDR D (64-bit FP)
|
"FMOVD": {3, 1, 1, 8}, // LDR D (64-bit FP)
|
||||||
|
"FMOVQ": {0, 1, 3, 16}, // LDR/STR Q (128-bit FP): opc=11 selects it
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64StoreOpc returns the store opc for a given load type: integer and FP
|
// a64StoreOpc returns the store opc for a given load type: integer and FP
|
||||||
// stores both encode opc=00 (the load's signedness bit sits in opc[1], which
|
// stores both encode opc=00 (the load's signedness bit sits in opc[1], which
|
||||||
// the store form clears; FP registers are selected by V, not opc).
|
// the store form clears; FP registers are selected by V, not opc). The one
|
||||||
|
// exception is the 128-bit Q width, whose store is the opc=10 spelling: with
|
||||||
|
// opc=00 the size field selects STR B instead (STR Qt is size=00, opc=10).
|
||||||
func a64StoreOpc(t a64LSType) int {
|
func a64StoreOpc(t a64LSType) int {
|
||||||
|
if t.size == 0 && t.V == 1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a64LSScale returns the byte width an access's unsigned offset divides by.
|
||||||
|
// The Q width carries its size in opc (the size field stays 0), yet scales
|
||||||
|
// by 16 like any other 128-bit access, so the exponent alone does not answer.
|
||||||
|
func a64LSScale(t a64LSType) int64 {
|
||||||
|
if t.size == 0 && t.V == 1 {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
return int64(1) << uint(t.size)
|
||||||
|
}
|
||||||
|
|
||||||
// arm64RegClass discriminates integer (R), floating-point (F) registers for
|
// arm64RegClass discriminates integer (R), floating-point (F) registers for
|
||||||
// the MOV pseudo-instruction.
|
// the MOV pseudo-instruction.
|
||||||
type arm64RegClass int
|
type arm64RegClass int
|
||||||
|
|||||||
+1361
-19
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// arm64AcceptedErrorShapes lists the toolchain's arm64error.s spellings gasm
|
||||||
|
// still accepts, each an acceptance superset with a known shape. The list
|
||||||
|
// only shrinks: every tightening of the encoder moves spellings out of it,
|
||||||
|
// and a spelling reappearing here means a regression. The catalogue is
|
||||||
|
// empty as of Go 1.27: every line the toolchain's corpus rejects, gasm
|
||||||
|
// rejects too.
|
||||||
|
var arm64AcceptedErrorShapes = []string{}
|
||||||
|
|
||||||
|
// TestArm64ToolchainErrorParity walks the toolchain's arm64error.s (Go
|
||||||
|
// 1.27, arm64) and requires gasm to reject every case the toolchain rejects.
|
||||||
|
// A live Go toolchain is needed for the source file; the test skips without
|
||||||
|
// one or in -short.
|
||||||
|
func TestArm64ToolchainErrorParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live arm64error.s corpus: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goroot := os.Getenv("GOROOT")
|
||||||
|
if goroot == "" {
|
||||||
|
t.Skip("no GOROOT")
|
||||||
|
}
|
||||||
|
path := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata", "arm64error.s")
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
allowed := map[string]bool{}
|
||||||
|
for _, s := range arm64AcceptedErrorShapes {
|
||||||
|
allowed[s] = true
|
||||||
|
}
|
||||||
|
for raw := range strings.SplitSeq(string(data), "\n") {
|
||||||
|
line := strings.TrimSpace(raw)
|
||||||
|
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "TEXT") || !strings.Contains(line, "ERROR") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body := line
|
||||||
|
if i := strings.Index(body, "//"); i >= 0 {
|
||||||
|
body = strings.TrimSpace(body[:i])
|
||||||
|
}
|
||||||
|
body = strings.ReplaceAll(body, "\t", " ")
|
||||||
|
body = strings.Join(strings.Fields(body), " ")
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("errorparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
if _, aerr := AssembleFileARM64(f); aerr == nil && !allowed[body] {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,571 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// The assembler's side of the extended-instruction layer: this file turns a
|
||||||
|
// parsed arm64 statement into the operand form arch.ExtInstr.Encode consumes
|
||||||
|
// and routes statements only the layer can encode through the registry. It
|
||||||
|
// sits beside the main arm64 encoders, never inside them: the generated
|
||||||
|
// tables and the scalar, NEON and FP paths are untouched, and a statement
|
||||||
|
// reaches this file only when the mnemonic is registered in the extension
|
||||||
|
// layer and at least one operand is a scalable vector or predicate register.
|
||||||
|
//
|
||||||
|
// The spellings are the layer's own Plan 9 forms, the ones its metadata
|
||||||
|
// documents: Zn, Zm, Zd for the unpredicated three-vector class, Zm, Pg/M,
|
||||||
|
// Zdn for the predicated class, imm{, LSL #8}, Zdn for the immediate
|
||||||
|
// classes, and for the predicate family Pm.B, Pn.B, Pg/Z (or Pg.Z), Pd.B
|
||||||
|
// for the logical operations, Pn.B, Pg.Z, Pd.B for the breaks, Pm.T, Pn.T,
|
||||||
|
// Pd.T for the permutations, Rm, Rn, Pd.T for the while compares, PN8-PN15
|
||||||
|
// for the counter destinations, and the bare SETFFR. Stage three adds the
|
||||||
|
// crypto family (Zn.T, Zd.T, Zd.T read-back and the in-place Zd.T, Zd.T),
|
||||||
|
// the predicate counters (Pn.T, Pg, Rd; Pn.T, ZR; Rd, Pn.T, Rd; ZR and R
|
||||||
|
// terminators) and the reductions (Zn.T, Pg, Vd over the SIMD register
|
||||||
|
// V0-V31, with ZR and RSP accepted where the classes take them).
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// arm64ExtStatement converts one instruction's operands into the extended
|
||||||
|
// layer's operand form. pinned reports that the statement belongs to the
|
||||||
|
// layer: the mnemonic is registered in the registry and the operand list
|
||||||
|
// carries at least one scalable vector, predicate or predicate-as-counter
|
||||||
|
// register, or no operands at all (the zero-operand forms such as SETFFR,
|
||||||
|
// which no scalar path could mean instead). A pinned statement can only
|
||||||
|
// encode through the layer, so every operand is read here and its
|
||||||
|
// diagnostic replaces whatever the scalar paths would have said about
|
||||||
|
// operands they cannot read; err is non-nil for a pinned statement whose
|
||||||
|
// operands the layer refuses, and extops is complete only when err is nil.
|
||||||
|
// Unpinned means the statement is nobody's: the caller falls through to the
|
||||||
|
// ordinary arm64 encoders, which keep their exact behaviour for every
|
||||||
|
// scalar, NEON and FP operand list.
|
||||||
|
func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperand, pinned bool, err error) {
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, mnem); !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
if !arm64ExtPinned(mnem, ops) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
ops = arm64ExtMergeLists(ops)
|
||||||
|
out := make([]arch.ExtOperand, 0, len(ops))
|
||||||
|
for i, op := range ops {
|
||||||
|
text := strings.Join(strings.Fields(op.Raw), "")
|
||||||
|
|
||||||
|
// The spelled shift of an immediate class: the shift is an attribute
|
||||||
|
// of the preceding immediate operand (imm{, LSL #8}, Zdn), never an
|
||||||
|
// operand of its own.
|
||||||
|
if amount, ok := strings.CutPrefix(text, "LSL#"); ok {
|
||||||
|
if len(out) == 0 || out[len(out)-1].Kind != arch.ExtImm || out[len(out)-1].HasShift {
|
||||||
|
return nil, true, fmt.Errorf("%s: operand %d (%s): LSL belongs straight after an immediate", mnem, i+1, op.Raw)
|
||||||
|
}
|
||||||
|
n, convErr := strconv.Atoi(amount)
|
||||||
|
if convErr != nil {
|
||||||
|
return nil, true, fmt.Errorf("%s: operand %d (%s): %q is not an LSL amount", mnem, i+1, op.Raw, amount)
|
||||||
|
}
|
||||||
|
out[len(out)-1].Shift, out[len(out)-1].HasShift = n, true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Kind == ast.OpImmediate {
|
||||||
|
ext, ok := arm64ExtImmediate(op)
|
||||||
|
if !ok {
|
||||||
|
return nil, true, fmt.Errorf("%s: operand %d (%s) is not an immediate the layer can read", mnem, i+1, op.Raw)
|
||||||
|
}
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The load and store destination list, [Z13.B] or the multi-register
|
||||||
|
// [Z13.B, Z14.B, Z15.B] of the multiple-structure shapes, its
|
||||||
|
// arrangement part of the instruction's identity.
|
||||||
|
if strings.HasPrefix(text, "[") && strings.HasSuffix(text, "]") {
|
||||||
|
if ext, ok := arm64ExtVectorList(mnem, strings.Trim(text, "[]")); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The gather/scatter memory operand: a parenthesised register pair,
|
||||||
|
// an immediate-offset base or a lone vector base.
|
||||||
|
if ext, ok := arm64ExtSveMem(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ext, ok := arm64ExtVector(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ext, ok := arm64ExtPredicate(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ext, ok := arm64ExtCounter(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if text == "ZR" {
|
||||||
|
out = append(out, arch.ExtZeroRegister())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if text == "RSP" {
|
||||||
|
out = append(out, arch.ExtStackPointer())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ext, ok := arm64ExtSIMD(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ext, ok := arm64ExtGeneral(text); ok {
|
||||||
|
out = append(out, ext)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, true, fmt.Errorf("%s: operand %d (%s) is not an extended-layer operand: want a scalable vector, predicate, general or counter register, or an immediate", mnem, i+1, op.Raw)
|
||||||
|
}
|
||||||
|
return out, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtPinned reports whether the statement belongs to the layer. A
|
||||||
|
// mnemonic the extension layer registers on its own, one the generated
|
||||||
|
// arm64 table does not know, owns every one of its statements: no scalar
|
||||||
|
// path could mean it instead, and the layer's diagnostics replace the
|
||||||
|
// unsupported-instruction complaint. A mnemonic both tables carry (the
|
||||||
|
// SVE aliases of ADD, SUB and MUL) keeps the operand-shape test: any
|
||||||
|
// operand is a scalable vector, predicate or predicate-as-counter
|
||||||
|
// register, the shapes only the extension layer reads, or the statement
|
||||||
|
// carries no operands at all and the mnemonic's zero-operand forms claim
|
||||||
|
// it. The shape test is deliberately loose about the suffixes: P0/B is
|
||||||
|
// not a spelling the layer takes, but the P of it makes the statement the
|
||||||
|
// layer's, and the conversion then diagnoses the operand precisely
|
||||||
|
// instead of leaving it to a scalar path that would report an unrelated
|
||||||
|
// register error.
|
||||||
|
func arm64ExtPinned(mnem string, ops []*ast.Operand) bool {
|
||||||
|
if len(ops) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, shared := a64InstrTable[mnem]; !shared {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, op := range ops {
|
||||||
|
if op.Kind == ast.OpImmediate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text := strings.Join(strings.Fields(op.Raw), "")
|
||||||
|
if _, ok := arm64ExtVector(text); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if arm64ExtPredicateShape(text) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtPredicateShape reports whether text spells a predicate or
|
||||||
|
// predicate-as-counter register at all: PN or P, digits, an optional
|
||||||
|
// arrangement suffix and an optional qualifier after a slash, whatever the
|
||||||
|
// qualifier says. The strict parses in arm64ExtPredicate and
|
||||||
|
// arm64ExtCounter judge the suffix; this shape only decides who the operand
|
||||||
|
// belongs to.
|
||||||
|
func arm64ExtPredicateShape(text string) bool {
|
||||||
|
if text == "" || text[0] != 'P' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
text = text[1:]
|
||||||
|
if rest, found := strings.CutPrefix(text, "N"); found {
|
||||||
|
text = rest
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(text, '/'); i >= 0 {
|
||||||
|
text = text[:i]
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(text, '.'); i >= 0 {
|
||||||
|
text = text[:i]
|
||||||
|
}
|
||||||
|
_, err := strconv.Atoi(text)
|
||||||
|
return err == nil && text != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtImmediate converts a $ immediate into the layer's form. The
|
||||||
|
// parser folds a parenthesised constant expression in full ($(255<<8)) and
|
||||||
|
// reads a bare literal greedily, dropping any trailing operator tokens:
|
||||||
|
// $255<<8 parses as 255 with the shift silently gone. Encoding that silent
|
||||||
|
// prefix would assemble what the text did not say, so an unparenthesised
|
||||||
|
// immediate is accepted only when its whole text reads back as one integer
|
||||||
|
// carrying the parser's value.
|
||||||
|
func arm64ExtImmediate(op *ast.Operand) (arch.ExtOperand, bool) {
|
||||||
|
if op.Kind != ast.OpImmediate || !op.Imm.HasVal {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
text := strings.Join(strings.Fields(strings.TrimPrefix(op.Raw, "$")), "")
|
||||||
|
if !strings.HasPrefix(text, "(") {
|
||||||
|
if _, parseErr := strconv.ParseInt(text, 0, 64); parseErr != nil {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := op.Imm.Val
|
||||||
|
if op.Imm.Neg {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtImm, Imm: v}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtVector parses a scalable vector register operand: Z0..Z31 with an
|
||||||
|
// optional element-size suffix, Z0.S. The arrangement is carried as written
|
||||||
|
// and the encoding validates it against the form.
|
||||||
|
func arm64ExtVector(text string) (arch.ExtOperand, bool) {
|
||||||
|
reg, arr, ok := arm64ExtReg(text, 'Z')
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtZReg, Reg: reg, Arr: arr}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtMergeLists rejoins the bracketed vector lists the parser reads as
|
||||||
|
// separate operands: the comma inside [Z13.B, Z14.B, Z15.B] is an operand
|
||||||
|
// boundary to the parser, so the list arrives as two or more pieces and the
|
||||||
|
// multiple-structure loads and stores need it whole. Pieces from an opening
|
||||||
|
// bracket to the one carrying the closing bracket rejoin over their commas;
|
||||||
|
// everything else passes through untouched.
|
||||||
|
func arm64ExtMergeLists(ops []*ast.Operand) []*ast.Operand {
|
||||||
|
closed := func(op *ast.Operand) bool {
|
||||||
|
return strings.HasSuffix(strings.Join(strings.Fields(op.Raw), ""), "]")
|
||||||
|
}
|
||||||
|
merged := make([]*ast.Operand, 0, len(ops))
|
||||||
|
for i := 0; i < len(ops); i++ {
|
||||||
|
text := strings.Join(strings.Fields(ops[i].Raw), "")
|
||||||
|
if !strings.HasPrefix(text, "[") || closed(ops[i]) {
|
||||||
|
merged = append(merged, ops[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := []string{ops[i].Raw}
|
||||||
|
kind := ops[i].Kind
|
||||||
|
for i+1 < len(ops) {
|
||||||
|
i++
|
||||||
|
parts = append(parts, ops[i].Raw)
|
||||||
|
if closed(ops[i]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged = append(merged, &ast.Operand{Kind: kind, Raw: strings.Join(parts, ",")})
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtVectorList parses the bracketed vector list of the loads and
|
||||||
|
// stores: a single register, [Z13.B], or the multi-register lists of the
|
||||||
|
// LD2-LD4 and ST2-ST4 multiple-structure shapes, [Z13.B, Z14.B, Z15.B],
|
||||||
|
// consecutive registers under one arrangement. The instruction's own digit
|
||||||
|
// names the list's length where it carries one, so a two-register list
|
||||||
|
// under ZLD3 fails here. The encoding carries the first register alone;
|
||||||
|
// the length rides the operand for the encode side.
|
||||||
|
func arm64ExtVectorList(mnem, body string) (arch.ExtOperand, bool) {
|
||||||
|
regs := strings.Split(body, ",")
|
||||||
|
first, ok := arm64ExtVector(regs[0])
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
for i, reg := range regs[1:] {
|
||||||
|
op, ok := arm64ExtVector(reg)
|
||||||
|
if !ok || op.Arr != first.Arr || op.Reg != first.Reg+i+1 {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count := arm64ExtListCount(mnem); count != len(regs) {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
if len(regs) > 1 {
|
||||||
|
first.List = len(regs)
|
||||||
|
}
|
||||||
|
return first, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtListCount reads the list length a load or store mnemonic names,
|
||||||
|
// the digit straight after its ZLD or ZST prefix; the loads and stores
|
||||||
|
// without one carry a single register.
|
||||||
|
func arm64ExtListCount(mnem string) int {
|
||||||
|
rest, ok := strings.CutPrefix(mnem, "ZLD")
|
||||||
|
if !ok {
|
||||||
|
rest, ok = strings.CutPrefix(mnem, "ZST")
|
||||||
|
}
|
||||||
|
if !ok || rest == "" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c := rest[0]; c >= '2' && c <= '4' {
|
||||||
|
return int(c - '0')
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtPredicate parses a predicate register operand: P0..P15 with an
|
||||||
|
// optional element-size suffix (P0.B) and an optional qualifier in either
|
||||||
|
// spelling the corpus and the wired forms use, P0/M and P0.Z.
|
||||||
|
func arm64ExtPredicate(text string) (arch.ExtOperand, bool) {
|
||||||
|
qual := arch.ExtQualNone
|
||||||
|
if base, suffix, found := strings.Cut(text, "/"); found {
|
||||||
|
switch suffix {
|
||||||
|
case "M":
|
||||||
|
qual = arch.ExtQualMerging
|
||||||
|
case "Z":
|
||||||
|
qual = arch.ExtQualZeroing
|
||||||
|
default:
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
text = base
|
||||||
|
} else if base, suffix, found := strings.Cut(text, "."); found &&
|
||||||
|
(suffix == "Z" || suffix == "M") {
|
||||||
|
// The dot qualifier stands in place of an arrangement, the spelling
|
||||||
|
// the toolchain's corpus writes (P1.Z, P14.M).
|
||||||
|
qual = arch.ExtQualMerging
|
||||||
|
if suffix == "Z" {
|
||||||
|
qual = arch.ExtQualZeroing
|
||||||
|
}
|
||||||
|
text = base
|
||||||
|
}
|
||||||
|
reg, arr, ok := arm64ExtReg(text, 'P')
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtPReg, Reg: reg, Arr: arr, Qual: qual}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtCounter parses a predicate-as-counter register operand: PN8..PN15
|
||||||
|
// with an optional element-size suffix, PN14.S. The register range is the
|
||||||
|
// counter range the layer's convention carries; the encoding validates it.
|
||||||
|
func arm64ExtCounter(text string) (arch.ExtOperand, bool) {
|
||||||
|
rest, ok := strings.CutPrefix(text, "PN")
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
reg, arr, ok := arm64ExtRegDigits(rest)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtPNReg, Reg: reg, Arr: arr}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtGeneral parses a general register operand: R0..R30, the plain
|
||||||
|
// spelling the while-compare forms take, beside the ZR and RSP spellings of
|
||||||
|
// the thirty-first slot the conversion above reads. The register range is
|
||||||
|
// left to the encoding, whose diagnostics name it.
|
||||||
|
func arm64ExtGeneral(text string) (arch.ExtOperand, bool) {
|
||||||
|
rest, ok := strings.CutPrefix(text, "R")
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
reg, arr, ok := arm64ExtRegDigits(rest)
|
||||||
|
if !ok || arr != arch.ExtArrNone {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtGReg, Reg: reg}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtSIMD parses a 128-bit SIMD register operand: V0..V31, written
|
||||||
|
// bare, the scalar destination the reductions and the crypto read-back
|
||||||
|
// forms take, or with the counted quadword suffix of the SVE2.1 QV class,
|
||||||
|
// V5.S4 reading four 32-bit lanes (the spellings .B16, .H8, .S4 and .D2;
|
||||||
|
// no other suffix parses). The register range is left to the encoding.
|
||||||
|
func arm64ExtSIMD(text string) (arch.ExtOperand, bool) {
|
||||||
|
rest, ok := strings.CutPrefix(text, "V")
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
arr := arch.ExtArrNone
|
||||||
|
for _, q := range []struct {
|
||||||
|
suffix string
|
||||||
|
arr arch.ExtArrangement
|
||||||
|
}{
|
||||||
|
{"B16", arch.ExtArrB},
|
||||||
|
{"H8", arch.ExtArrH},
|
||||||
|
{"S4", arch.ExtArrS},
|
||||||
|
{"D2", arch.ExtArrD},
|
||||||
|
} {
|
||||||
|
if s := "." + q.suffix; strings.HasSuffix(rest, s) {
|
||||||
|
arr = q.arr
|
||||||
|
rest = rest[:len(rest)-len(s)]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reg, bare, ok := arm64ExtRegDigits(rest)
|
||||||
|
if !ok || bare != arch.ExtArrNone {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Kind: arch.ExtVReg, Reg: reg, Arr: arr}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtSveMem parses the gather/scatter memory operand off a normalised
|
||||||
|
// operand text: the parenthesised pair (R6)(R14), (Z23.D<<1)(R24) and
|
||||||
|
// (Z4.S.UXTW)(R3), the immediate-offset base 6(Z7.S), and the lone vector
|
||||||
|
// base (Z5.D) of the stores. The second parenthesis accepts the
|
||||||
|
// stack-pointer spelling RSP; the ranges and the mode's own rules are left
|
||||||
|
// to the encoding, whose diagnostics name them.
|
||||||
|
func arm64ExtSveMem(text string) (arch.ExtOperand, bool) {
|
||||||
|
// The immediate-offset spelling: digits straight before the parenthesis.
|
||||||
|
if i := strings.IndexByte(text, '('); i > 0 && i == strings.LastIndexByte(text, '(') {
|
||||||
|
disp, err := strconv.ParseUint(text[:i], 10, 32)
|
||||||
|
if err == nil && strings.HasSuffix(text, ")") {
|
||||||
|
op, ok := arm64ExtSveMemGroup(text[i+1 : len(text)-1])
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
if !op.BaseVec || op.Extend != 0 || op.Shift != 0 {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
op.Imm = int64(disp)
|
||||||
|
return op, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The parenthesised forms: one group or two.
|
||||||
|
rest, ok := strings.CutPrefix(text, "(")
|
||||||
|
if !ok || !strings.HasSuffix(text, ")") {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
rest = rest[:len(rest)-1]
|
||||||
|
first := rest
|
||||||
|
op := arch.ExtOperand{Off: -1}
|
||||||
|
if base, second, found := strings.Cut(rest, ")("); found {
|
||||||
|
first = base
|
||||||
|
off, ok := arm64ExtSveMemOffset(second)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
op = off
|
||||||
|
}
|
||||||
|
group, ok := arm64ExtSveMemGroup(first)
|
||||||
|
if !ok {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
group.Off = op.Off
|
||||||
|
group.Reg31 = op.Reg31
|
||||||
|
return group, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtSveMemGroup parses one parenthesised memory register: R6, R6<<3,
|
||||||
|
// Z23.D, Z23.D<<1, Z4.S.UXTW or Z7.D.SXTW. The general registers run
|
||||||
|
// R0-R30 and the scalable vectors Z0-Z31 with an .S or .D element size and
|
||||||
|
// an optional UXTW or SXTW extension; the ranges are left to the encoding.
|
||||||
|
func arm64ExtSveMemGroup(text string) (arch.ExtOperand, bool) {
|
||||||
|
op := arch.ExtOperand{Kind: arch.ExtSveMem, Off: -1}
|
||||||
|
if base, shift, found := strings.Cut(text, "<<"); found {
|
||||||
|
n, err := strconv.Atoi(shift)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
op.Shift = n
|
||||||
|
text = base
|
||||||
|
}
|
||||||
|
parts := strings.Split(text, ".")
|
||||||
|
switch parts[0][0] {
|
||||||
|
case 'R':
|
||||||
|
reg, err := strconv.Atoi(parts[0][1:])
|
||||||
|
if err != nil || len(parts) != 1 {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
op.Reg = reg
|
||||||
|
case 'Z':
|
||||||
|
reg, err := strconv.Atoi(parts[0][1:])
|
||||||
|
if err != nil || len(parts) < 2 || len(parts) > 3 {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
switch parts[1] {
|
||||||
|
case "S":
|
||||||
|
op.Arr = arch.ExtArrS
|
||||||
|
case "D":
|
||||||
|
op.Arr = arch.ExtArrD
|
||||||
|
default:
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
op.Reg = reg
|
||||||
|
op.BaseVec = true
|
||||||
|
if len(parts) == 3 {
|
||||||
|
switch parts[2] {
|
||||||
|
case "UXTW":
|
||||||
|
op.Extend = 1
|
||||||
|
case "SXTW":
|
||||||
|
op.Extend = 2
|
||||||
|
default:
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return op, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtSveMemOffset parses the second parenthesis of a gather/scatter
|
||||||
|
// memory operand: a plain R0-R30 or the stack-pointer spelling RSP.
|
||||||
|
func arm64ExtSveMemOffset(text string) (arch.ExtOperand, bool) {
|
||||||
|
if text == "RSP" {
|
||||||
|
return arch.ExtOperand{Off: 31, Reg31: 2}, true
|
||||||
|
}
|
||||||
|
if len(text) < 2 || text[0] != 'R' {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
reg, err := strconv.Atoi(text[1:])
|
||||||
|
if err != nil {
|
||||||
|
return arch.ExtOperand{}, false
|
||||||
|
}
|
||||||
|
return arch.ExtOperand{Off: reg}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtRegDigits parses the digits and optional arrangement suffix of a
|
||||||
|
// register spelling once the letter prefix is gone.
|
||||||
|
func arm64ExtRegDigits(text string) (reg int, arr arch.ExtArrangement, ok bool) {
|
||||||
|
if base, suffix, found := strings.Cut(text, "."); found {
|
||||||
|
switch suffix {
|
||||||
|
case "B":
|
||||||
|
arr = arch.ExtArrB
|
||||||
|
case "H":
|
||||||
|
arr = arch.ExtArrH
|
||||||
|
case "S":
|
||||||
|
arr = arch.ExtArrS
|
||||||
|
case "D":
|
||||||
|
arr = arch.ExtArrD
|
||||||
|
case "Q":
|
||||||
|
arr = arch.ExtArrQ
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
text = base
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(text)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return n, arr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64ExtReg parses Pn or Zn with an optional arrangement suffix off a
|
||||||
|
// normalised operand text. The register range is left to the encoding: the
|
||||||
|
// layer's own diagnostics name the range a form carries.
|
||||||
|
func arm64ExtReg(text string, letter byte) (reg int, arr arch.ExtArrangement, ok bool) {
|
||||||
|
if len(text) < 2 || text[0] != letter {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
digits := text[1:]
|
||||||
|
if base, suffix, found := strings.Cut(digits, "."); found {
|
||||||
|
switch suffix {
|
||||||
|
case "B":
|
||||||
|
arr = arch.ExtArrB
|
||||||
|
case "H":
|
||||||
|
arr = arch.ExtArrH
|
||||||
|
case "S":
|
||||||
|
arr = arch.ExtArrS
|
||||||
|
case "D":
|
||||||
|
arr = arch.ExtArrD
|
||||||
|
case "Q":
|
||||||
|
arr = arch.ExtArrQ
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
digits = base
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(digits)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return n, arr, true
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
+106
-2
@@ -53,7 +53,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// arm64FrameInfo holds the frame layout derived from a TEXT directive.
|
// arm64FrameInfo holds the frame layout derived from a TEXT directive.
|
||||||
@@ -69,6 +69,12 @@ type arm64FrameInfo struct {
|
|||||||
// autosize below StackSmall as NOSPLIT.
|
// autosize below StackSmall as NOSPLIT.
|
||||||
needSplit bool
|
needSplit bool
|
||||||
splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig
|
splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig
|
||||||
|
|
||||||
|
// tls holds the names the file's own GLOBL declarations mark TLSBSS:
|
||||||
|
// the toolchain's aclass keys the TLS-LE load off the symbol's type
|
||||||
|
// (objabi.STLSBSS), which only the file's declarations reveal. A nil
|
||||||
|
// map answers no, the plain static-symbol load.
|
||||||
|
tls map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// arm64ComputeFrame derives the frame layout for a TEXT function.
|
// arm64ComputeFrame derives the frame layout for a TEXT function.
|
||||||
@@ -87,7 +93,10 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
|
|||||||
if fi.frame != 0 || !fi.leaf {
|
if fi.frame != 0 || !fi.leaf {
|
||||||
fi.autosize = fi.frame + 8 // space for the saved LR
|
fi.autosize = fi.frame + 8 // space for the saved LR
|
||||||
// The toolchain always adds an extrasize: 8 when the total leaves a
|
// The toolchain always adds an extrasize: 8 when the total leaves a
|
||||||
// 16-byte alignment gap, another 16 when already aligned.
|
// 16-byte alignment gap, another 16 when already aligned. An
|
||||||
|
// autosize of zero (the $-8 convention included) is frameless and
|
||||||
|
// takes neither.
|
||||||
|
if fi.autosize != 0 {
|
||||||
switch fi.autosize % 16 {
|
switch fi.autosize % 16 {
|
||||||
case 8:
|
case 8:
|
||||||
fi.autosize += 8
|
fi.autosize += 8
|
||||||
@@ -99,6 +108,13 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
|
|||||||
fi.autosize += 16 - (fi.autosize % 16)
|
fi.autosize += 16 - (fi.autosize % 16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if fi.autosize == 0 {
|
||||||
|
// The NOFRAME shape: the toolchain forces the leaf mark on any
|
||||||
|
// autosize-zero function (calls included), so nothing saves LR and
|
||||||
|
// no stack-split guard runs.
|
||||||
|
fi.leaf = true
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case fi.noSplit:
|
case fi.noSplit:
|
||||||
case fi.autosize < stackSmall && fi.leaf:
|
case fi.autosize < stackSmall && fi.leaf:
|
||||||
@@ -301,6 +317,37 @@ func arm64Return(fi arm64FrameInfo) []byte {
|
|||||||
return a64WordsLE(ws...)
|
return a64WordsLE(ws...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64RetInstr encodes a RET. The plain form runs the frame epilogue and
|
||||||
|
// branches to LR; RET Rn runs the epilogue and branches to the register
|
||||||
|
// (asm7.go case 78); RET sym(SB) runs the epilogue and branches to the
|
||||||
|
// symbol with the call relocation, the toolchain's retJMP tail call.
|
||||||
|
func arm64RetInstr(fi arm64FrameInfo, ops []*ast.Operand, relocs *[]Reloc) []byte {
|
||||||
|
out := arm64Return(fi)
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
op := ops[0]
|
||||||
|
switch {
|
||||||
|
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" && op.Addr.Base == "" && op.Addr.Index == "":
|
||||||
|
if rn := arm64RegNum(op.Addr.Sym.Name); rn >= 0 {
|
||||||
|
// The operand form replaces the default BR LR word.
|
||||||
|
return append(out[:len(out)-4], a64wordLE(0xd65f0000|uint32(rn)<<5)...)
|
||||||
|
}
|
||||||
|
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" && op.Addr.Base == "" && op.Addr.Index == "":
|
||||||
|
if relocs != nil {
|
||||||
|
*relocs = append(*relocs, Reloc{
|
||||||
|
Off: 0,
|
||||||
|
After: 4,
|
||||||
|
Name: op.Addr.Sym.Name,
|
||||||
|
Addend: op.Addr.Sym.Offset,
|
||||||
|
Kind: RelArm64Branch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return append(out[:len(out)-4], a64wordLE(0x14000000)...) // B sym(SB)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// arm64PrologueSpadjPC returns the function-relative byte offset where the
|
// arm64PrologueSpadjPC returns the function-relative byte offset where the
|
||||||
// prologue has finished decrementing SP (the delta becomes autosize).
|
// prologue has finished decrementing SP (the delta becomes autosize).
|
||||||
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
||||||
@@ -362,6 +409,59 @@ func arm64ResolvePseudo(sym *ast.Symbol, fi arm64FrameInfo) (base int, off int32
|
|||||||
return -1, 0
|
return -1, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrValue returns the SP-relative displacement a $sym+off(FP)
|
||||||
|
// or $sym+off(SP) immediate-address operand stands for, the toolchain's
|
||||||
|
// aclass arithmetic (asm7.go): a parameter reference sits autosize+8 above
|
||||||
|
// the hardware SP, and a pseudo-SP reference sits frame+8 above it, the
|
||||||
|
// alignment padding cancelling out of the autosize.
|
||||||
|
func arm64FrameAddrValue(sym *ast.Symbol, fi arm64FrameInfo) int64 {
|
||||||
|
switch sym.Pseudo {
|
||||||
|
case "FP":
|
||||||
|
return sym.Offset + int64(fi.autosize) + 8
|
||||||
|
default: // SP
|
||||||
|
return sym.Offset + int64(fi.frame) + 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64IsAddcon reports whether v is an addcon value (asm7.go isaddcon): an
|
||||||
|
// unsigned imm12, or a multiple of 4096 whose shifted form fits imm12.
|
||||||
|
func arm64IsAddcon(v int64) bool {
|
||||||
|
if v < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v&0xFFF == 0 {
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return v <= 0xFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrWords returns the word sequence of the toolchain's optab
|
||||||
|
// case 4 for a frame-relative address (the C_AACON and C_AACON2 rows): one
|
||||||
|
// ADD/SUB imm12 word inside the addcon band, SUB carrying a negative
|
||||||
|
// displacement, and otherwise the hi<<12 word from SP followed by the low
|
||||||
|
// word added in place. The 24-bit band never reaches the REGTMP
|
||||||
|
// materialisation the ADD/SUB immediate ladder uses: aclass classifies the
|
||||||
|
// address straight into C_AACON2.
|
||||||
|
func arm64FrameAddrWords(v int64, rd int) []uint32 {
|
||||||
|
word := func(v int64, rn int) uint32 {
|
||||||
|
op := uint32(0) // ADD
|
||||||
|
if v < 0 {
|
||||||
|
op = 1 // SUB
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
sh := uint32(0)
|
||||||
|
if v&0xFFF000 != 0 { // asm7.go oaddi: the shift form when low 12 bits are clear
|
||||||
|
sh = 1
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return a64AddSub(1, op, 0, sh, uint32(v), uint32(rn), uint32(rd))
|
||||||
|
}
|
||||||
|
if arm64IsAddcon(v) || arm64IsAddcon(-v) {
|
||||||
|
return []uint32{word(v, 31)}
|
||||||
|
}
|
||||||
|
return []uint32{word(v&^int64(0xFFF), 31), word(v&0xFFF, rd)}
|
||||||
|
}
|
||||||
|
|
||||||
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
||||||
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
||||||
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
||||||
@@ -454,6 +554,10 @@ func arm64GuardBytes(fi arm64FrameInfo, blockStart int) []byte {
|
|||||||
return a64WordsLE(ws...)
|
return a64WordsLE(ws...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64MoreStackBlockLen is the byte length of arm64MoreStackBlock: the
|
||||||
|
// saved LR, the BL and the branch back, three words whatever the target.
|
||||||
|
const arm64MoreStackBlockLen = 12
|
||||||
|
|
||||||
// arm64MoreStackBlock emits the trailing block: MOVD R30, R3 (save LR),
|
// arm64MoreStackBlock emits the trailing block: MOVD R30, R3 (save LR),
|
||||||
// BL runtime.morestack_noctxt, B back to the function start. The BL carries
|
// BL runtime.morestack_noctxt, B back to the function start. The BL carries
|
||||||
// the R_CALLARM64 relocation.
|
// the R_CALLARM64 relocation.
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assembleArm64Source parses src and assembles it for arm64, returning the
|
||||||
|
// first function's words little-endian.
|
||||||
|
func assembleArm64Source(t *testing.T, src string) []uint32 {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("test_arm64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("got %d funcs, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
return wordsOf(img.Code[img.Funcs[0].Offset : img.Funcs[0].Offset+img.Funcs[0].Size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrEncoding(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·fr(SB), NOSPLIT, $432-24
|
||||||
|
MOVD $argframe+0(FP), R3
|
||||||
|
MOVD $big+4096(FP), R4
|
||||||
|
MOVD $ret-8(FP), R2
|
||||||
|
MOVD $x-64(FP), R5
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// Prologue (4: large frame) then the body at words 4..9, the toolchain's
|
||||||
|
// own encodings for the same statements:
|
||||||
|
// ADD $456, RSP, R3 (456 = 448 + 8 + 0)
|
||||||
|
// ADD $(1<<12), RSP, R4 (4552, the hi<<12 half)
|
||||||
|
// ADD $456, R4, R4 (then the lo half)
|
||||||
|
// ADD $448, RSP, R2 (448 - 8 + 8)
|
||||||
|
// ADD $392, RSP, R5 (448 - 64 + 8)
|
||||||
|
// ADD $0, RSP, R19 (the SP register move)
|
||||||
|
// ADD $0, R20, RSP
|
||||||
|
want := []uint32{
|
||||||
|
0xd10703f4, 0xa93ffa9d, 0x9100029f, 0xd10023fd,
|
||||||
|
0x910723e3, 0x914007e4, 0x91072084, 0x910703e2,
|
||||||
|
0x910623e5, 0x910003f3, 0x9100029f,
|
||||||
|
}
|
||||||
|
if len(ws) < len(want) {
|
||||||
|
t.Fatalf("got %d words, want at least %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrSizes(t *testing.T) {
|
||||||
|
// One imm12 word inside the addcon band, two inside the 24-bit band.
|
||||||
|
tests := []struct {
|
||||||
|
v int64
|
||||||
|
verb int
|
||||||
|
}{
|
||||||
|
{456, 1},
|
||||||
|
{0xFFF, 1},
|
||||||
|
{0x1000, 1}, // the shifted imm12 form
|
||||||
|
{0x1005, 2}, // the hi<<12 plus lo pair
|
||||||
|
{0xFFFFFF, 2},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := len(arm64FrameAddrWords(tt.v, 3))
|
||||||
|
if got != tt.verb {
|
||||||
|
t.Errorf("arm64FrameAddrWords(%d) took %d words, want %d", tt.v, got, tt.verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ws := arm64FrameAddrWords(0x1000, 3); len(ws) != 1 || ws[0] != 0x914007e3 {
|
||||||
|
t.Errorf("arm64FrameAddrWords(0x1000) = %08x, want the shifted ADD 914007e3", ws[0])
|
||||||
|
}
|
||||||
|
if !arm64IsAddcon(0xFFF) || arm64IsAddcon(0x1001) || arm64IsAddcon(-1) {
|
||||||
|
t.Error("arm64IsAddcon misclassifies the band edges")
|
||||||
|
}
|
||||||
|
fp := &ast.Symbol{Name: "x", Pseudo: "FP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(fp, arm64FrameInfo{autosize: 448}); v != 464 {
|
||||||
|
t.Errorf("FP address: got %d, want 464", v)
|
||||||
|
}
|
||||||
|
sp := &ast.Symbol{Name: "x", Pseudo: "SP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(sp, arm64FrameInfo{frame: 432}); v != 448 {
|
||||||
|
t.Errorf("SP address: got %d, want 448", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrRejects(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"width", `TEXT ·f(SB), NOSPLIT, $16-8
|
||||||
|
MOVW $x+0(FP), R7
|
||||||
|
RET
|
||||||
|
`, "illegal combination",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pool band", `TEXT ·f(SB), NOSPLIT, $20000000-8
|
||||||
|
MOVD $x+20000000(FP), R7
|
||||||
|
RET
|
||||||
|
`, "literal pool",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("test_arm64.s", tt.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
_, err := AssembleFileARM64(f)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%s: no error, want one naming %q", tt.name, tt.want)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("%s: error %q, want it to name %q", tt.name, err, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64SPMoveEncoding(t *testing.T) {
|
||||||
|
src := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
MOVD ZR, R4
|
||||||
|
MOVD RSP, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// The SP register moves ride ADD $0; the zero move stays ORR.
|
||||||
|
want := []uint32{
|
||||||
|
0x910003f3, // ADD $0, RSP, R19
|
||||||
|
0x9100029f, // ADD $0, R20, RSP
|
||||||
|
0xaa1f03e4, // ORR R4, ZR, ZR
|
||||||
|
0x910003ff, // ADD $0, RSP, RSP
|
||||||
|
0xd65f03c0, // RET
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rej := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVW RSP, R7
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("test_arm64.s", rej)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFileARM64(f); err == nil || !strings.Contains(err.Error(), "illegal combination") {
|
||||||
|
t.Errorf("MOVW RSP: error %v, want an illegal-combination refusal", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64AliasLiveness(t *testing.T) {
|
||||||
|
// A define inside a dead conditional branch must not become an alias:
|
||||||
|
// go_tls.h's `#ifdef GOARCH_arm` block defines LR as R14, which would
|
||||||
|
// silently renumber the link register on arm64, where LR is R30.
|
||||||
|
src := `#define RARG R5
|
||||||
|
#ifdef GOARCH_arm
|
||||||
|
#define LR R14
|
||||||
|
#endif
|
||||||
|
TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD LR, R0
|
||||||
|
MOVD RARG, R1
|
||||||
|
MOVD R14, R2
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0xaa1e03e0, // ORR R0, ZR, R30: LR stayed the link register
|
||||||
|
0xaa0503e1, // ORR R1, ZR, R5: the live alias applied
|
||||||
|
0xaa0e03e2, // ORR R2, ZR, R14: R14 is R14
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64ADRNoChainChase(t *testing.T) {
|
||||||
|
// The toolchain's jump-to-jump collapse rewrites branch targets only:
|
||||||
|
// a B to a chain-leading label is redirected, an ADR to the same label
|
||||||
|
// resolves to the label itself.
|
||||||
|
src := `TEXT ·adrchain(SB), NOSPLIT, $0-0
|
||||||
|
B a
|
||||||
|
a:
|
||||||
|
B b
|
||||||
|
b:
|
||||||
|
ADR a, R0
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0x14000002, // B +2: chased through a to b
|
||||||
|
0x14000001, // B +1: a's own jump to b
|
||||||
|
0x10ffffe0, // ADR a, R0: -4, the label itself, unchased
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var le = binary.LittleEndian
|
||||||
|
|
||||||
|
// kindSTEXTFIPS is objabi's STEXTFIPS: the fips140 packages' text kind.
|
||||||
|
const kindSTEXTFIPS = 2
|
||||||
|
|
||||||
|
// gorootARM64Packages names the GOROOT packages whose arm64 assembly the
|
||||||
|
// parity harness pins: every *_arm64.s of each package, as the real build
|
||||||
|
// assembles it, the package's generated go_asm.h included. Together they
|
||||||
|
// carry the heavy real-world shapes: the runtime's TLS and stack plumbing,
|
||||||
|
// the cryptographic kernels, big-number arithmetic and the bytealg search
|
||||||
|
// loops.
|
||||||
|
var gorootARM64Packages = []string{
|
||||||
|
"runtime",
|
||||||
|
"internal/bytealg",
|
||||||
|
"internal/cpu",
|
||||||
|
"internal/chacha8rand",
|
||||||
|
"internal/runtime/maps",
|
||||||
|
"reflect",
|
||||||
|
"math/big",
|
||||||
|
"hash/crc32",
|
||||||
|
"crypto/md5",
|
||||||
|
"crypto/sha1",
|
||||||
|
"crypto/internal/fips140/aes",
|
||||||
|
"crypto/internal/fips140/aes/gcm",
|
||||||
|
"crypto/internal/fips140/bigmod",
|
||||||
|
"crypto/internal/fips140/nistec",
|
||||||
|
"crypto/internal/fips140/sha256",
|
||||||
|
"crypto/internal/fips140/sha512",
|
||||||
|
"crypto/internal/fips140/sha3",
|
||||||
|
"crypto/internal/fips140/subtle",
|
||||||
|
}
|
||||||
|
|
||||||
|
// gorootARM64GapFiles names the files kept out of the byte parity set by
|
||||||
|
// known, pre-existing gaps, each with the reason. A file here is skipped,
|
||||||
|
// not silently dropped: the gaps are findings, and closing one is a matter
|
||||||
|
// of removing its entry and watching the file pin itself.
|
||||||
|
var gorootARM64GapFiles = map[string]string{
|
||||||
|
"runtime/asm_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge",
|
||||||
|
"runtime/sys_linux_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge (cgoSigtramp, clone)",
|
||||||
|
"runtime/preempt_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge (asyncPreempt)",
|
||||||
|
"runtime/race_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue shape diverges (racecallbackthunk)",
|
||||||
|
"runtime/rt0_linux_arm64.s": "the #ifdef GOOS selection diverges: gasm keeps a word the toolchain drops",
|
||||||
|
}
|
||||||
|
|
||||||
|
// gorootOtherGOOS matches the file names of the ports the linux build never
|
||||||
|
// assembles: the harness pins the linux arm64 set.
|
||||||
|
var gorootOtherGOOS = regexp.MustCompile(`_(darwin|ios|freebsd|netbsd|openbsd|windows|android|plan9|aix|js|wasip1)_`)
|
||||||
|
|
||||||
|
// TestGOROOTARM64Parity assembles each package's arm64 files with gasm and
|
||||||
|
// with the installed toolchain and holds the functions' bytes equal,
|
||||||
|
// relocation sites masked. The toolchain side needs the go_asm.h the build
|
||||||
|
// generates for the package, so the harness rebuilds it once with -work and
|
||||||
|
// harvests the header the compiler wrote; the -gcflags flag exists only to
|
||||||
|
// make that rebuild happen, the header's constants do not depend on it.
|
||||||
|
func TestGOROOTARM64Parity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle and per-package rebuild: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
out, err := exec.Command(goBin, "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("go env GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
goroot := strings.TrimSpace(string(out))
|
||||||
|
include := filepath.Join(goroot, "pkg", "include")
|
||||||
|
|
||||||
|
totalFns, totalBytes := 0, 0
|
||||||
|
for _, pkg := range gorootARM64Packages {
|
||||||
|
t.Run(pkg, func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
work := harvestGoAsm(t, goBin, pkg)
|
||||||
|
defer os.RemoveAll(work)
|
||||||
|
headers, err := filepath.Glob(filepath.Join(work, "b*", "go_asm.h"))
|
||||||
|
if err != nil || len(headers) == 0 {
|
||||||
|
t.Fatalf("no generated go_asm.h under %s", work)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go_asm.h"), mustRead(t, headers[0]), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
files, err := filepath.Glob(filepath.Join(goroot, "src", pkg, "*_arm64.s"))
|
||||||
|
if err != nil || len(files) == 0 {
|
||||||
|
t.Fatalf("no arm64 assembly found for %s", pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
base := filepath.Base(f)
|
||||||
|
if gorootOtherGOOS.MatchString(base) {
|
||||||
|
continue // another port's file: the linux build never assembles it
|
||||||
|
}
|
||||||
|
t.Run(base, func(t *testing.T) {
|
||||||
|
if reason, gap := gorootARM64GapFiles[pkg+"/"+base]; gap {
|
||||||
|
t.Skip(reason)
|
||||||
|
}
|
||||||
|
// The parser side: the file's own directory resolves the
|
||||||
|
// package's headers, the harvested directory carries
|
||||||
|
// go_asm.h, and the platform conditionals read the same
|
||||||
|
// predefines the go command drives go tool asm with.
|
||||||
|
src := string(mustRead(t, f))
|
||||||
|
af, errs := parser.ParseWithOptions(f, src, parser.Options{
|
||||||
|
Expand: true,
|
||||||
|
IncludeDirs: []string{dir, filepath.Join(goroot, "src", pkg), include},
|
||||||
|
Predefines: map[string]string{
|
||||||
|
"GOARCH_arm64": "1",
|
||||||
|
"GOOS_linux": "1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(af)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The oracle side: the build's own invocation, the
|
||||||
|
// generated header directory first.
|
||||||
|
objPath := filepath.Join(t.TempDir(), "oracle.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm",
|
||||||
|
"-I", dir, "-I", filepath.Join(goroot, "src", pkg), "-I", include,
|
||||||
|
"-D", "GOOS_linux", "-D", "GOARCH_arm64", "-std",
|
||||||
|
"-p", pkg, "-o", objPath, f)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
if oout, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("go tool asm %s: %v\n%s", base, err, oout)
|
||||||
|
}
|
||||||
|
byLocal := oracleFuncText(t, mustRead(t, objPath))
|
||||||
|
|
||||||
|
// The object's symdef order is the source order, gasm's
|
||||||
|
// function list too, so same-named functions pair up in
|
||||||
|
// definition order: a file-local kernel beside its
|
||||||
|
// package-level twin (runtime·racefuncenter and
|
||||||
|
// racefuncenter<>) carries the plain name twice in the
|
||||||
|
// object and the reader cannot see the locality.
|
||||||
|
seen := map[string]int{}
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
bodies := byLocal[fn.Name]
|
||||||
|
idx := seen[fn.Name]
|
||||||
|
seen[fn.Name] = idx + 1
|
||||||
|
if idx >= len(bodies) {
|
||||||
|
keys := make([]string, 0, len(byLocal))
|
||||||
|
for name := range byLocal {
|
||||||
|
keys = append(keys, name)
|
||||||
|
}
|
||||||
|
t.Errorf("%s: not in the oracle output (%d functions: %s)",
|
||||||
|
fn.Name, len(byLocal), strings.Join(keys, ", "))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
goCode := maskCode(append([]byte(nil), bodies[idx]...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
for w := 0; w < cmpLen/4; w++ {
|
||||||
|
g := le.Uint32(gasmCode[w*4:])
|
||||||
|
o := le.Uint32(goCode[w*4:])
|
||||||
|
if g != o {
|
||||||
|
t.Errorf("%s: word %d (offset %d) differs: gasm %08x go %08x", fn.Name, w, w*4, g, o)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(goCode) > len(gasmCode) {
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Errorf("%s: non-zero trailing bytes in the oracle output", fn.Name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalFns++
|
||||||
|
totalBytes += len(gasmCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Logf("GOROOT arm64 parity: %d functions, %d bytes identical", totalFns, totalBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracleFuncText extracts every TEXT function of a toolchain object, the
|
||||||
|
// non-package and the hashed (file-local) definitions both, keyed by the
|
||||||
|
// local name: GOROOT keeps several kernels file-local (cmpbody<>,
|
||||||
|
// encryptBlockAsm<>), and those ride the hashed definition blocks the
|
||||||
|
// non-package reader never sees. The value is the functions' bodies in
|
||||||
|
// symdef order: a file-local kernel beside its package-level twin carries
|
||||||
|
// the same plain name twice (the object reader cannot see the locality),
|
||||||
|
// and the encoder pairs them up in definition order.
|
||||||
|
func oracleFuncText(t *testing.T, obj []byte) map[string][][]byte {
|
||||||
|
t.Helper()
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
data := v.blk(blkData)
|
||||||
|
didx := v.blk(blkDataIdx)
|
||||||
|
out := make(map[string][][]byte)
|
||||||
|
di := 0
|
||||||
|
for _, bi := range []int{blkSymdef, blkHashed64def, blkHasheddef, blkNonpkgdef} {
|
||||||
|
for _, s := range v.syms(bi) {
|
||||||
|
// STEXT and STEXTFIPS both: the fips140 packages' text carries
|
||||||
|
// the FIPS kind in Go 1.27 and up.
|
||||||
|
if (s.typ == kindSTEXT || s.typ == kindSTEXTFIPS) && s.size > 0 && 4*di+8 <= len(didx) {
|
||||||
|
off := le.Uint32(didx[4*di:])
|
||||||
|
if int(off)+int(s.size) <= len(data) {
|
||||||
|
name := s.name
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
name = after
|
||||||
|
}
|
||||||
|
out[name] = append(out[name], data[off:int(off)+int(s.size)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
di++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// harvestGoAsm rebuilds pkg once with -work and returns the work directory
|
||||||
|
// holding the compiler's generated go_asm.h. A fully cached build leaves
|
||||||
|
// the work directory empty, so the compile action is given a unique, inert
|
||||||
|
// flag value each run (the inlining level never touches the header's
|
||||||
|
// constants) and re-runs for the target package alone.
|
||||||
|
func harvestGoAsm(t *testing.T, goBin, pkg string) string {
|
||||||
|
t.Helper()
|
||||||
|
nonce := time.Now().UnixNano() % 1000000
|
||||||
|
cmd := exec.Command(goBin, "build", "-x", "-work",
|
||||||
|
"-gcflags", pkg+"=-N", "-gcflags", pkg+"=-l=7"+strconv.FormatInt(nonce, 10),
|
||||||
|
"-o", "/dev/null", pkg)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rebuild %s: %v\n%s", pkg, err, out)
|
||||||
|
}
|
||||||
|
m := regexp.MustCompile(`WORK=(\S+)`).FindSubmatch(out)
|
||||||
|
if m == nil {
|
||||||
|
t.Fatalf("rebuild %s: no WORK directory in the build log", pkg)
|
||||||
|
}
|
||||||
|
return string(m[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustRead reads path, failing the test when it cannot.
|
||||||
|
func mustRead(t *testing.T, path string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The mid-function literal pool flush, pinned against the toolchain. A
|
||||||
|
// kernel of distinct pooled stores walks the conservative displacement bound
|
||||||
|
// (asm7.go's maxPCDisp) inside one body, so the pool must drain exactly
|
||||||
|
// where cmd/internal/obj/arm64's checkpool drains it: the bytes, the flush
|
||||||
|
// points and the reach of every load literal are the toolchain's own.
|
||||||
|
|
||||||
|
// pooledKernel renders a kernel of n distinct pooled stores: each offset
|
||||||
|
// sits a step of 8 past the aligned split band (every fourth one aligned,
|
||||||
|
// but 0x2000000+8i+4 never is), so every statement pools its own four-byte
|
||||||
|
// word and expands to eight instruction bytes, the densest walk towards the
|
||||||
|
// distance bound an arm64 body can make. head carries the TEXT line for the
|
||||||
|
// splitting variant, tail the closing statements.
|
||||||
|
func pooledKernel(head string, n int, tail string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("#include \"textflag.h\"\n")
|
||||||
|
if head != "" {
|
||||||
|
b.WriteString(head)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
b.WriteString("TEXT \u00b7poolmid(SB), NOSPLIT, $0-0\n")
|
||||||
|
for i := range n {
|
||||||
|
b.WriteString("\tMOVD\tR1, ")
|
||||||
|
b.WriteString(decimal(0x2000000 + 8*i + 4))
|
||||||
|
b.WriteString("(R2)\n")
|
||||||
|
}
|
||||||
|
b.WriteString(tail)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// decimal formats v in decimal.
|
||||||
|
func decimal(v int) string {
|
||||||
|
if v == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
var buf [20]byte
|
||||||
|
i := len(buf)
|
||||||
|
for v > 0 {
|
||||||
|
i--
|
||||||
|
buf[i] = byte('0' + v%10)
|
||||||
|
v /= 10
|
||||||
|
}
|
||||||
|
return string(buf[i:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// assemblePooled parses and assembles a generated kernel, returning the
|
||||||
|
// image and the single function's code words.
|
||||||
|
func assemblePooled(t *testing.T, head string, n int, tail string) (*Image, []uint32) {
|
||||||
|
t.Helper()
|
||||||
|
src := pooledKernel(head, n, tail)
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "poolmid_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse(path, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("functions = %d, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
return img, leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64BranchWord reports whether w is an unconditional B: op 000101 in bits
|
||||||
|
// 31..26, the encoding the flush guards ride.
|
||||||
|
func a64BranchWord(w uint32) bool {
|
||||||
|
return w>>26 == 0x05
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64BranchTarget decodes a B word's target byte offset from its own pc.
|
||||||
|
func a64BranchTarget(w uint32, pc int) int {
|
||||||
|
d := int(w & 0x03FFFFFF)
|
||||||
|
if d&(1<<25) != 0 {
|
||||||
|
d |= ^0x03FFFFFF
|
||||||
|
}
|
||||||
|
return pc + 4*d
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64LoadLiteral reports whether w is a LDR W/X literal (the pool's loads
|
||||||
|
// into REGTMP: word forms 0x18 and 0x58 in the top byte) and decodes its
|
||||||
|
// target byte displacement.
|
||||||
|
func a64LoadLiteral(w uint32) (int, bool) {
|
||||||
|
if w>>24 != 0x18 && w>>24 != 0x58 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
d := int(w>>5) & 0x7FFFF
|
||||||
|
if d&(1<<18) != 0 {
|
||||||
|
d |= ^0x7FFFF
|
||||||
|
}
|
||||||
|
return d * 4, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestArm64PoolFlushStructure assembles the distance-bound kernel and pins
|
||||||
|
// the flush structure on gasm's own image: exactly one mid-body branch over
|
||||||
|
// a drained segment, no code words inside it, every load literal inside the
|
||||||
|
// conservative displacement bound, and the segment's byte range free of line
|
||||||
|
// rows: the pool words carry the flushing statement's source line, so the
|
||||||
|
// pc-line tables see no delta across them.
|
||||||
|
func TestArm64PoolFlushStructure(t *testing.T) {
|
||||||
|
const n = 44000 // one flush: the bound arrives at about 43690 statements
|
||||||
|
img, words := assemblePooled(t, "", n, "\tRET\n")
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
|
||||||
|
var branches []int
|
||||||
|
for i, w := range words {
|
||||||
|
if a64BranchWord(w) {
|
||||||
|
branches = append(branches, i*4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(branches) != 1 {
|
||||||
|
t.Fatalf("branch words = %d, want exactly the one flush guard", len(branches))
|
||||||
|
}
|
||||||
|
branchPC := branches[0]
|
||||||
|
target := a64BranchTarget(words[branchPC/4], branchPC)
|
||||||
|
segStart, segEnd := branchPC+4, target
|
||||||
|
if segEnd <= segStart || segEnd%4 != 0 {
|
||||||
|
t.Fatalf("flush branch target %d leaves no legal word range after %d", target, branchPC)
|
||||||
|
}
|
||||||
|
if segEnd+4 > len(words)*4 {
|
||||||
|
t.Fatalf("flush branch target %d runs past the image (%d words)", target, len(words))
|
||||||
|
}
|
||||||
|
// The drained segment holds pool words only: four-byte constants, no
|
||||||
|
// branch opcodes, no literal loads.
|
||||||
|
for off := segStart; off < segEnd; off += 4 {
|
||||||
|
w := words[off/4]
|
||||||
|
if a64BranchWord(w) {
|
||||||
|
t.Fatalf("word at %d inside the drained segment is a branch: %08x", off, w)
|
||||||
|
}
|
||||||
|
if _, lit := a64LoadLiteral(w); lit {
|
||||||
|
t.Fatalf("word at %d inside the drained segment is a literal load: %08x", off, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every load literal resolves inside the conservative bound and inside
|
||||||
|
// the image: the property the flush exists to keep.
|
||||||
|
for i, w := range words {
|
||||||
|
d, ok := a64LoadLiteral(w)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pc := i * 4
|
||||||
|
if pc+d < 0 || pc+d >= len(words)*4 {
|
||||||
|
t.Fatalf("literal load at %d targets %d, outside the image", pc, pc+d)
|
||||||
|
}
|
||||||
|
if d >= a64MaxPCDisp || d <= -a64MaxPCDisp {
|
||||||
|
t.Fatalf("literal load at %d displaces %d, outside the conservative bound", pc, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The drained words carry no line rows of their own: the toolchain gives
|
||||||
|
// them the flushing statement's Pos so the pc-line tables see no delta.
|
||||||
|
for _, l := range fn.Lines {
|
||||||
|
if l.Offset >= segStart && l.Offset < segEnd {
|
||||||
|
t.Fatalf("line row at %d sits inside the drained segment [%d, %d)", l.Offset, segStart, segEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestArm64PoolFlushDifferential assembles the kernel family with gasm and
|
||||||
|
// with the installed toolchain and holds the bytes equal, the toolchain's
|
||||||
|
// tail alignment padding excepted: one flush, a fall-through end behind the
|
||||||
|
// UNDEF guard, two flushes, and a splitting function whose guard prefix and
|
||||||
|
// morestack block sit behind a shifted pool.
|
||||||
|
func TestArm64PoolFlushDifferential(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
|
for _, k := range []struct {
|
||||||
|
name string
|
||||||
|
head string
|
||||||
|
n int
|
||||||
|
tail string
|
||||||
|
}{
|
||||||
|
{"one flush, RET end", "", 44000, "\tRET\n"},
|
||||||
|
{"one flush, UNDEF end", "", 44000, ""},
|
||||||
|
{"two flushes", "", 90000, "\tRET\n"},
|
||||||
|
{"split function", "TEXT \u00b7poolmid(SB), $8-0", 44000, "\tRET\n"},
|
||||||
|
} {
|
||||||
|
t.Run(k.name, func(t *testing.T) {
|
||||||
|
if k.head != "" {
|
||||||
|
// The split variant spells its own TEXT: the generator's
|
||||||
|
// NOSPLIT line must give way to it.
|
||||||
|
runPoolFlushCase(t, pooledKernelFor(k.head, k.n, k.tail))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runPoolFlushCase(t, pooledKernel("", k.n, k.tail))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pooledKernelFor renders the kernel with an explicit TEXT line, the split
|
||||||
|
// variant's shape: no NOSPLIT, so the frame forces the stack-split guard.
|
||||||
|
func pooledKernelFor(text string, n int, tail string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("#include \"textflag.h\"\n")
|
||||||
|
b.WriteString(text)
|
||||||
|
b.WriteString("\n")
|
||||||
|
for i := range n {
|
||||||
|
b.WriteString("\tMOVD\tR1, ")
|
||||||
|
b.WriteString(decimal(0x2000000 + 8*i + 4))
|
||||||
|
b.WriteString("(R2)\n")
|
||||||
|
}
|
||||||
|
b.WriteString(tail)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runPoolFlushCase assembles one generated kernel both ways and compares
|
||||||
|
// the function's bytes, relocation sites masked, the toolchain's trailing
|
||||||
|
// alignment zeros excepted.
|
||||||
|
func runPoolFlushCase(t *testing.T, src string) {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "poolmid_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse(path, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
gt := oracleFuncCode(t, toolAsmObject(t, path, "arm64"))
|
||||||
|
byLocal := make(map[string][]byte, len(gt))
|
||||||
|
for name, code := range gt {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
name = after
|
||||||
|
}
|
||||||
|
byLocal[name] = code
|
||||||
|
}
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
goCode, ok := byLocal[fn.Name]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: not in the oracle output (%d functions)", fn.Name, len(gt))
|
||||||
|
}
|
||||||
|
goCode = maskCode(append([]byte(nil), goCode...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
for w := 0; w < cmpLen/4; w++ {
|
||||||
|
g := binary.LittleEndian.Uint32(gasmCode[w*4:])
|
||||||
|
o := binary.LittleEndian.Uint32(goCode[w*4:])
|
||||||
|
if g != o {
|
||||||
|
t.Fatalf("%s: word %d (offset %d) differs: gasm %08x go %08x", fn.Name, w, w*4, g, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: prefixes equal but lengths differ (gasm %d, oracle %d)", fn.Name, len(gasmCode), len(goCode))
|
||||||
|
}
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Fatalf("%s: non-zero trailing bytes in the oracle output", fn.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
-1
@@ -7,7 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseArm64File is a helper assembling one arm64 source file.
|
// parseArm64File is a helper assembling one arm64 source file.
|
||||||
@@ -124,3 +124,59 @@ func TestArm64GOObjRelocTypes(t *testing.T) {
|
|||||||
// The detailed layout is covered by the goobj tests; here we only pin
|
// The detailed layout is covered by the goobj tests; here we only pin
|
||||||
// that emission succeeds with the new relocation kinds in play.
|
// that emission succeeds with the new relocation kinds in play.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestArm64TLSLoad pins the local-exec TLS load: a symbol the file's own
|
||||||
|
// GLOBL marks TLSBSS loads as one MOVZ word carrying the R_ARM64_TLS_LE
|
||||||
|
// relocation (asm7.go case 69), the shape `go tool asm` emits for the
|
||||||
|
// runtime's tls_g accesses. A non-TLS GLOBL keeps the ADRP+LDR pair.
|
||||||
|
func TestArm64TLSLoad(t *testing.T) {
|
||||||
|
img := parseArm64File(t, "#include \"textflag.h\"\n\n"+
|
||||||
|
"TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+
|
||||||
|
"\tMOVD tlsvar(SB), R0\n"+
|
||||||
|
"\tMOVD plain(SB), R1\n"+
|
||||||
|
"\tRET\n"+
|
||||||
|
"GLOBL tlsvar(SB), TLSBSS, $8\n"+
|
||||||
|
"GLOBL plain(SB), NOPTR, $8\n")
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
if fn.Size != 4+8+4 {
|
||||||
|
t.Fatalf("function size = %d, want 16", fn.Size)
|
||||||
|
}
|
||||||
|
if w := binary.LittleEndian.Uint32(img.Code[fn.Offset:]); w != 0xd2800000 {
|
||||||
|
t.Errorf("TLS load word = %08x, want MOVZ 0 (d2800000)", w)
|
||||||
|
}
|
||||||
|
var tlsSeen, plainSeen bool
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.Name != "tlsvar" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tlsSeen = true
|
||||||
|
if r.Kind != RelArm64TLSLE {
|
||||||
|
t.Errorf("tlsvar reloc kind = %v, want RelArm64TLSLE", r.Kind)
|
||||||
|
}
|
||||||
|
if r.Off != 0 {
|
||||||
|
t.Errorf("tlsvar reloc off = %d, want 0", r.Off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.Name == "plain" && r.Kind == RelArm64LDST64 {
|
||||||
|
plainSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !tlsSeen {
|
||||||
|
t.Error("no tlsvar relocation recorded")
|
||||||
|
}
|
||||||
|
if !plainSeen {
|
||||||
|
t.Error("the plain GLOBL load lost its ADRP+LDR relocation")
|
||||||
|
}
|
||||||
|
// The other widths have no TLS row: the toolchain refuses them.
|
||||||
|
f, errs := parser.Parse("k_arm64.s", "TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+
|
||||||
|
"\tMOVW tlsvar(SB), R0\n"+
|
||||||
|
"\tRET\n"+
|
||||||
|
"GLOBL tlsvar(SB), TLSBSS, $8\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFileARM64(f); err == nil {
|
||||||
|
t.Error("MOVW of a TLS symbol assembled, want an illegal combination")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,600 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// arm64 system registers and system-instruction aliases.
|
||||||
|
//
|
||||||
|
// The tables are transcribed from the data the Go toolchain itself carries
|
||||||
|
// (cmd/internal/obj/arm64/sysRegEnc.go and the sysInstFields map of asm7.go),
|
||||||
|
// which the ARM ARM defines: every system register is the packed field set
|
||||||
|
// op0<<19 | op1<<16 | CRn<<12 | CRm<<8 | op2<<5, and the read/write flags are
|
||||||
|
// the toolchain's own access classification. The encoding tables live here so
|
||||||
|
// the encoder stays testable against the GOROOT testdata word for word.
|
||||||
|
|
||||||
|
// a64SysReg is one system register: the packed encoding fields and the
|
||||||
|
// directions the register supports.
|
||||||
|
type a64SysReg struct {
|
||||||
|
v uint32
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SysRegs maps the system register names the toolchain knows to their
|
||||||
|
// encodings. MRS reads 0xd5300000 | v | Rd and MSR writes
|
||||||
|
// 0xd5100000 | v | Rt.
|
||||||
|
var a64SysRegs = map[string]a64SysReg{
|
||||||
|
"ACTLR_EL1": a64SysReg{0x181020, true, true},
|
||||||
|
"AFSR0_EL1": a64SysReg{0x185100, true, true},
|
||||||
|
"AFSR1_EL1": a64SysReg{0x185120, true, true},
|
||||||
|
"AIDR_EL1": a64SysReg{0x1900e0, true, false},
|
||||||
|
"AMAIR_EL1": a64SysReg{0x18a300, true, true},
|
||||||
|
"AMCFGR_EL0": a64SysReg{0x1bd220, true, false},
|
||||||
|
"AMCGCR_EL0": a64SysReg{0x1bd240, true, false},
|
||||||
|
"AMCNTENCLR0_EL0": a64SysReg{0x1bd280, true, true},
|
||||||
|
"AMCNTENCLR1_EL0": a64SysReg{0x1bd300, true, true},
|
||||||
|
"AMCNTENSET0_EL0": a64SysReg{0x1bd2a0, true, true},
|
||||||
|
"AMCNTENSET1_EL0": a64SysReg{0x1bd320, true, true},
|
||||||
|
"AMCR_EL0": a64SysReg{0x1bd200, true, true},
|
||||||
|
"AMEVCNTR00_EL0": a64SysReg{0x1bd400, true, true},
|
||||||
|
"AMEVCNTR01_EL0": a64SysReg{0x1bd420, true, true},
|
||||||
|
"AMEVCNTR02_EL0": a64SysReg{0x1bd440, true, true},
|
||||||
|
"AMEVCNTR03_EL0": a64SysReg{0x1bd460, true, true},
|
||||||
|
"AMEVCNTR04_EL0": a64SysReg{0x1bd480, true, true},
|
||||||
|
"AMEVCNTR05_EL0": a64SysReg{0x1bd4a0, true, true},
|
||||||
|
"AMEVCNTR06_EL0": a64SysReg{0x1bd4c0, true, true},
|
||||||
|
"AMEVCNTR07_EL0": a64SysReg{0x1bd4e0, true, true},
|
||||||
|
"AMEVCNTR08_EL0": a64SysReg{0x1bd500, true, true},
|
||||||
|
"AMEVCNTR09_EL0": a64SysReg{0x1bd520, true, true},
|
||||||
|
"AMEVCNTR010_EL0": a64SysReg{0x1bd540, true, true},
|
||||||
|
"AMEVCNTR011_EL0": a64SysReg{0x1bd560, true, true},
|
||||||
|
"AMEVCNTR012_EL0": a64SysReg{0x1bd580, true, true},
|
||||||
|
"AMEVCNTR013_EL0": a64SysReg{0x1bd5a0, true, true},
|
||||||
|
"AMEVCNTR014_EL0": a64SysReg{0x1bd5c0, true, true},
|
||||||
|
"AMEVCNTR015_EL0": a64SysReg{0x1bd5e0, true, true},
|
||||||
|
"AMEVCNTR10_EL0": a64SysReg{0x1bdc00, true, true},
|
||||||
|
"AMEVCNTR11_EL0": a64SysReg{0x1bdc20, true, true},
|
||||||
|
"AMEVCNTR12_EL0": a64SysReg{0x1bdc40, true, true},
|
||||||
|
"AMEVCNTR13_EL0": a64SysReg{0x1bdc60, true, true},
|
||||||
|
"AMEVCNTR14_EL0": a64SysReg{0x1bdc80, true, true},
|
||||||
|
"AMEVCNTR15_EL0": a64SysReg{0x1bdca0, true, true},
|
||||||
|
"AMEVCNTR16_EL0": a64SysReg{0x1bdcc0, true, true},
|
||||||
|
"AMEVCNTR17_EL0": a64SysReg{0x1bdce0, true, true},
|
||||||
|
"AMEVCNTR18_EL0": a64SysReg{0x1bdd00, true, true},
|
||||||
|
"AMEVCNTR19_EL0": a64SysReg{0x1bdd20, true, true},
|
||||||
|
"AMEVCNTR110_EL0": a64SysReg{0x1bdd40, true, true},
|
||||||
|
"AMEVCNTR111_EL0": a64SysReg{0x1bdd60, true, true},
|
||||||
|
"AMEVCNTR112_EL0": a64SysReg{0x1bdd80, true, true},
|
||||||
|
"AMEVCNTR113_EL0": a64SysReg{0x1bdda0, true, true},
|
||||||
|
"AMEVCNTR114_EL0": a64SysReg{0x1bddc0, true, true},
|
||||||
|
"AMEVCNTR115_EL0": a64SysReg{0x1bdde0, true, true},
|
||||||
|
"AMEVTYPER00_EL0": a64SysReg{0x1bd600, true, false},
|
||||||
|
"AMEVTYPER01_EL0": a64SysReg{0x1bd620, true, false},
|
||||||
|
"AMEVTYPER02_EL0": a64SysReg{0x1bd640, true, false},
|
||||||
|
"AMEVTYPER03_EL0": a64SysReg{0x1bd660, true, false},
|
||||||
|
"AMEVTYPER04_EL0": a64SysReg{0x1bd680, true, false},
|
||||||
|
"AMEVTYPER05_EL0": a64SysReg{0x1bd6a0, true, false},
|
||||||
|
"AMEVTYPER06_EL0": a64SysReg{0x1bd6c0, true, false},
|
||||||
|
"AMEVTYPER07_EL0": a64SysReg{0x1bd6e0, true, false},
|
||||||
|
"AMEVTYPER08_EL0": a64SysReg{0x1bd700, true, false},
|
||||||
|
"AMEVTYPER09_EL0": a64SysReg{0x1bd720, true, false},
|
||||||
|
"AMEVTYPER010_EL0": a64SysReg{0x1bd740, true, false},
|
||||||
|
"AMEVTYPER011_EL0": a64SysReg{0x1bd760, true, false},
|
||||||
|
"AMEVTYPER012_EL0": a64SysReg{0x1bd780, true, false},
|
||||||
|
"AMEVTYPER013_EL0": a64SysReg{0x1bd7a0, true, false},
|
||||||
|
"AMEVTYPER014_EL0": a64SysReg{0x1bd7c0, true, false},
|
||||||
|
"AMEVTYPER015_EL0": a64SysReg{0x1bd7e0, true, false},
|
||||||
|
"AMEVTYPER10_EL0": a64SysReg{0x1bde00, true, true},
|
||||||
|
"AMEVTYPER11_EL0": a64SysReg{0x1bde20, true, true},
|
||||||
|
"AMEVTYPER12_EL0": a64SysReg{0x1bde40, true, true},
|
||||||
|
"AMEVTYPER13_EL0": a64SysReg{0x1bde60, true, true},
|
||||||
|
"AMEVTYPER14_EL0": a64SysReg{0x1bde80, true, true},
|
||||||
|
"AMEVTYPER15_EL0": a64SysReg{0x1bdea0, true, true},
|
||||||
|
"AMEVTYPER16_EL0": a64SysReg{0x1bdec0, true, true},
|
||||||
|
"AMEVTYPER17_EL0": a64SysReg{0x1bdee0, true, true},
|
||||||
|
"AMEVTYPER18_EL0": a64SysReg{0x1bdf00, true, true},
|
||||||
|
"AMEVTYPER19_EL0": a64SysReg{0x1bdf20, true, true},
|
||||||
|
"AMEVTYPER110_EL0": a64SysReg{0x1bdf40, true, true},
|
||||||
|
"AMEVTYPER111_EL0": a64SysReg{0x1bdf60, true, true},
|
||||||
|
"AMEVTYPER112_EL0": a64SysReg{0x1bdf80, true, true},
|
||||||
|
"AMEVTYPER113_EL0": a64SysReg{0x1bdfa0, true, true},
|
||||||
|
"AMEVTYPER114_EL0": a64SysReg{0x1bdfc0, true, true},
|
||||||
|
"AMEVTYPER115_EL0": a64SysReg{0x1bdfe0, true, true},
|
||||||
|
"AMUSERENR_EL0": a64SysReg{0x1bd260, true, true},
|
||||||
|
"APDAKeyHi_EL1": a64SysReg{0x182220, true, true},
|
||||||
|
"APDAKeyLo_EL1": a64SysReg{0x182200, true, true},
|
||||||
|
"APDBKeyHi_EL1": a64SysReg{0x182260, true, true},
|
||||||
|
"APDBKeyLo_EL1": a64SysReg{0x182240, true, true},
|
||||||
|
"APGAKeyHi_EL1": a64SysReg{0x182320, true, true},
|
||||||
|
"APGAKeyLo_EL1": a64SysReg{0x182300, true, true},
|
||||||
|
"APIAKeyHi_EL1": a64SysReg{0x182120, true, true},
|
||||||
|
"APIAKeyLo_EL1": a64SysReg{0x182100, true, true},
|
||||||
|
"APIBKeyHi_EL1": a64SysReg{0x182160, true, true},
|
||||||
|
"APIBKeyLo_EL1": a64SysReg{0x182140, true, true},
|
||||||
|
"CCSIDR2_EL1": a64SysReg{0x190040, true, false},
|
||||||
|
"CCSIDR_EL1": a64SysReg{0x190000, true, false},
|
||||||
|
"CLIDR_EL1": a64SysReg{0x190020, true, false},
|
||||||
|
"CNTFRQ_EL0": a64SysReg{0x1be000, true, true},
|
||||||
|
"CNTKCTL_EL1": a64SysReg{0x18e100, true, true},
|
||||||
|
"CNTP_CTL_EL0": a64SysReg{0x1be220, true, true},
|
||||||
|
"CNTP_CVAL_EL0": a64SysReg{0x1be240, true, true},
|
||||||
|
"CNTP_TVAL_EL0": a64SysReg{0x1be200, true, true},
|
||||||
|
"CNTPCT_EL0": a64SysReg{0x1be020, true, false},
|
||||||
|
"CNTPS_CTL_EL1": a64SysReg{0x1fe220, true, true},
|
||||||
|
"CNTPS_CVAL_EL1": a64SysReg{0x1fe240, true, true},
|
||||||
|
"CNTPS_TVAL_EL1": a64SysReg{0x1fe200, true, true},
|
||||||
|
"CNTV_CTL_EL0": a64SysReg{0x1be320, true, true},
|
||||||
|
"CNTV_CVAL_EL0": a64SysReg{0x1be340, true, true},
|
||||||
|
"CNTV_TVAL_EL0": a64SysReg{0x1be300, true, true},
|
||||||
|
"CNTVCT_EL0": a64SysReg{0x1be040, true, false},
|
||||||
|
"CONTEXTIDR_EL1": a64SysReg{0x18d020, true, true},
|
||||||
|
"CPACR_EL1": a64SysReg{0x181040, true, true},
|
||||||
|
"CSSELR_EL1": a64SysReg{0x1a0000, true, true},
|
||||||
|
"CTR_EL0": a64SysReg{0x1b0020, true, false},
|
||||||
|
"CurrentEL": a64SysReg{0x184240, true, false},
|
||||||
|
"DAIF": a64SysReg{0x1b4220, true, true},
|
||||||
|
"DBGAUTHSTATUS_EL1": a64SysReg{0x107ec0, true, false},
|
||||||
|
"DBGBCR0_EL1": a64SysReg{0x1000a0, true, true},
|
||||||
|
"DBGBCR1_EL1": a64SysReg{0x1001a0, true, true},
|
||||||
|
"DBGBCR2_EL1": a64SysReg{0x1002a0, true, true},
|
||||||
|
"DBGBCR3_EL1": a64SysReg{0x1003a0, true, true},
|
||||||
|
"DBGBCR4_EL1": a64SysReg{0x1004a0, true, true},
|
||||||
|
"DBGBCR5_EL1": a64SysReg{0x1005a0, true, true},
|
||||||
|
"DBGBCR6_EL1": a64SysReg{0x1006a0, true, true},
|
||||||
|
"DBGBCR7_EL1": a64SysReg{0x1007a0, true, true},
|
||||||
|
"DBGBCR8_EL1": a64SysReg{0x1008a0, true, true},
|
||||||
|
"DBGBCR9_EL1": a64SysReg{0x1009a0, true, true},
|
||||||
|
"DBGBCR10_EL1": a64SysReg{0x100aa0, true, true},
|
||||||
|
"DBGBCR11_EL1": a64SysReg{0x100ba0, true, true},
|
||||||
|
"DBGBCR12_EL1": a64SysReg{0x100ca0, true, true},
|
||||||
|
"DBGBCR13_EL1": a64SysReg{0x100da0, true, true},
|
||||||
|
"DBGBCR14_EL1": a64SysReg{0x100ea0, true, true},
|
||||||
|
"DBGBCR15_EL1": a64SysReg{0x100fa0, true, true},
|
||||||
|
"DBGBVR0_EL1": a64SysReg{0x100080, true, true},
|
||||||
|
"DBGBVR1_EL1": a64SysReg{0x100180, true, true},
|
||||||
|
"DBGBVR2_EL1": a64SysReg{0x100280, true, true},
|
||||||
|
"DBGBVR3_EL1": a64SysReg{0x100380, true, true},
|
||||||
|
"DBGBVR4_EL1": a64SysReg{0x100480, true, true},
|
||||||
|
"DBGBVR5_EL1": a64SysReg{0x100580, true, true},
|
||||||
|
"DBGBVR6_EL1": a64SysReg{0x100680, true, true},
|
||||||
|
"DBGBVR7_EL1": a64SysReg{0x100780, true, true},
|
||||||
|
"DBGBVR8_EL1": a64SysReg{0x100880, true, true},
|
||||||
|
"DBGBVR9_EL1": a64SysReg{0x100980, true, true},
|
||||||
|
"DBGBVR10_EL1": a64SysReg{0x100a80, true, true},
|
||||||
|
"DBGBVR11_EL1": a64SysReg{0x100b80, true, true},
|
||||||
|
"DBGBVR12_EL1": a64SysReg{0x100c80, true, true},
|
||||||
|
"DBGBVR13_EL1": a64SysReg{0x100d80, true, true},
|
||||||
|
"DBGBVR14_EL1": a64SysReg{0x100e80, true, true},
|
||||||
|
"DBGBVR15_EL1": a64SysReg{0x100f80, true, true},
|
||||||
|
"DBGCLAIMCLR_EL1": a64SysReg{0x1079c0, true, true},
|
||||||
|
"DBGCLAIMSET_EL1": a64SysReg{0x1078c0, true, true},
|
||||||
|
"DBGDTR_EL0": a64SysReg{0x130400, true, true},
|
||||||
|
"DBGDTRRX_EL0": a64SysReg{0x130500, true, false},
|
||||||
|
"DBGDTRTX_EL0": a64SysReg{0x130500, false, true},
|
||||||
|
"DBGPRCR_EL1": a64SysReg{0x101480, true, true},
|
||||||
|
"DBGWCR0_EL1": a64SysReg{0x1000e0, true, true},
|
||||||
|
"DBGWCR1_EL1": a64SysReg{0x1001e0, true, true},
|
||||||
|
"DBGWCR2_EL1": a64SysReg{0x1002e0, true, true},
|
||||||
|
"DBGWCR3_EL1": a64SysReg{0x1003e0, true, true},
|
||||||
|
"DBGWCR4_EL1": a64SysReg{0x1004e0, true, true},
|
||||||
|
"DBGWCR5_EL1": a64SysReg{0x1005e0, true, true},
|
||||||
|
"DBGWCR6_EL1": a64SysReg{0x1006e0, true, true},
|
||||||
|
"DBGWCR7_EL1": a64SysReg{0x1007e0, true, true},
|
||||||
|
"DBGWCR8_EL1": a64SysReg{0x1008e0, true, true},
|
||||||
|
"DBGWCR9_EL1": a64SysReg{0x1009e0, true, true},
|
||||||
|
"DBGWCR10_EL1": a64SysReg{0x100ae0, true, true},
|
||||||
|
"DBGWCR11_EL1": a64SysReg{0x100be0, true, true},
|
||||||
|
"DBGWCR12_EL1": a64SysReg{0x100ce0, true, true},
|
||||||
|
"DBGWCR13_EL1": a64SysReg{0x100de0, true, true},
|
||||||
|
"DBGWCR14_EL1": a64SysReg{0x100ee0, true, true},
|
||||||
|
"DBGWCR15_EL1": a64SysReg{0x100fe0, true, true},
|
||||||
|
"DBGWVR0_EL1": a64SysReg{0x1000c0, true, true},
|
||||||
|
"DBGWVR1_EL1": a64SysReg{0x1001c0, true, true},
|
||||||
|
"DBGWVR2_EL1": a64SysReg{0x1002c0, true, true},
|
||||||
|
"DBGWVR3_EL1": a64SysReg{0x1003c0, true, true},
|
||||||
|
"DBGWVR4_EL1": a64SysReg{0x1004c0, true, true},
|
||||||
|
"DBGWVR5_EL1": a64SysReg{0x1005c0, true, true},
|
||||||
|
"DBGWVR6_EL1": a64SysReg{0x1006c0, true, true},
|
||||||
|
"DBGWVR7_EL1": a64SysReg{0x1007c0, true, true},
|
||||||
|
"DBGWVR8_EL1": a64SysReg{0x1008c0, true, true},
|
||||||
|
"DBGWVR9_EL1": a64SysReg{0x1009c0, true, true},
|
||||||
|
"DBGWVR10_EL1": a64SysReg{0x100ac0, true, true},
|
||||||
|
"DBGWVR11_EL1": a64SysReg{0x100bc0, true, true},
|
||||||
|
"DBGWVR12_EL1": a64SysReg{0x100cc0, true, true},
|
||||||
|
"DBGWVR13_EL1": a64SysReg{0x100dc0, true, true},
|
||||||
|
"DBGWVR14_EL1": a64SysReg{0x100ec0, true, true},
|
||||||
|
"DBGWVR15_EL1": a64SysReg{0x100fc0, true, true},
|
||||||
|
"DCZID_EL0": a64SysReg{0x1b00e0, true, false},
|
||||||
|
"DISR_EL1": a64SysReg{0x18c120, true, true},
|
||||||
|
"DIT": a64SysReg{0x1b42a0, true, true},
|
||||||
|
"DLR_EL0": a64SysReg{0x1b4520, true, true},
|
||||||
|
"DSPSR_EL0": a64SysReg{0x1b4500, true, true},
|
||||||
|
"ELR_EL1": a64SysReg{0x184020, true, true},
|
||||||
|
"ERRIDR_EL1": a64SysReg{0x185300, true, false},
|
||||||
|
"ERRSELR_EL1": a64SysReg{0x185320, true, true},
|
||||||
|
"ERXADDR_EL1": a64SysReg{0x185460, true, true},
|
||||||
|
"ERXCTLR_EL1": a64SysReg{0x185420, true, true},
|
||||||
|
"ERXFR_EL1": a64SysReg{0x185400, true, false},
|
||||||
|
"ERXMISC0_EL1": a64SysReg{0x185500, true, true},
|
||||||
|
"ERXMISC1_EL1": a64SysReg{0x185520, true, true},
|
||||||
|
"ERXMISC2_EL1": a64SysReg{0x185540, true, true},
|
||||||
|
"ERXMISC3_EL1": a64SysReg{0x185560, true, true},
|
||||||
|
"ERXPFGCDN_EL1": a64SysReg{0x1854c0, true, true},
|
||||||
|
"ERXPFGCTL_EL1": a64SysReg{0x1854a0, true, true},
|
||||||
|
"ERXPFGF_EL1": a64SysReg{0x185480, true, false},
|
||||||
|
"ERXSTATUS_EL1": a64SysReg{0x185440, true, true},
|
||||||
|
"ESR_EL1": a64SysReg{0x185200, true, true},
|
||||||
|
"FAR_EL1": a64SysReg{0x186000, true, true},
|
||||||
|
"FPCR": a64SysReg{0x1b4400, true, true},
|
||||||
|
"FPSR": a64SysReg{0x1b4420, true, true},
|
||||||
|
"GCR_EL1": a64SysReg{0x1810c0, true, true},
|
||||||
|
"GMID_EL1": a64SysReg{0x31400, true, false},
|
||||||
|
"ICC_AP0R0_EL1": a64SysReg{0x18c880, true, true},
|
||||||
|
"ICC_AP0R1_EL1": a64SysReg{0x18c8a0, true, true},
|
||||||
|
"ICC_AP0R2_EL1": a64SysReg{0x18c8c0, true, true},
|
||||||
|
"ICC_AP0R3_EL1": a64SysReg{0x18c8e0, true, true},
|
||||||
|
"ICC_AP1R0_EL1": a64SysReg{0x18c900, true, true},
|
||||||
|
"ICC_AP1R1_EL1": a64SysReg{0x18c920, true, true},
|
||||||
|
"ICC_AP1R2_EL1": a64SysReg{0x18c940, true, true},
|
||||||
|
"ICC_AP1R3_EL1": a64SysReg{0x18c960, true, true},
|
||||||
|
"ICC_ASGI1R_EL1": a64SysReg{0x18cbc0, false, true},
|
||||||
|
"ICC_BPR0_EL1": a64SysReg{0x18c860, true, true},
|
||||||
|
"ICC_BPR1_EL1": a64SysReg{0x18cc60, true, true},
|
||||||
|
"ICC_CTLR_EL1": a64SysReg{0x18cc80, true, true},
|
||||||
|
"ICC_DIR_EL1": a64SysReg{0x18cb20, false, true},
|
||||||
|
"ICC_EOIR0_EL1": a64SysReg{0x18c820, false, true},
|
||||||
|
"ICC_EOIR1_EL1": a64SysReg{0x18cc20, false, true},
|
||||||
|
"ICC_HPPIR0_EL1": a64SysReg{0x18c840, true, false},
|
||||||
|
"ICC_HPPIR1_EL1": a64SysReg{0x18cc40, true, false},
|
||||||
|
"ICC_IAR0_EL1": a64SysReg{0x18c800, true, false},
|
||||||
|
"ICC_IAR1_EL1": a64SysReg{0x18cc00, true, false},
|
||||||
|
"ICC_IGRPEN0_EL1": a64SysReg{0x18ccc0, true, true},
|
||||||
|
"ICC_IGRPEN1_EL1": a64SysReg{0x18cce0, true, true},
|
||||||
|
"ICC_PMR_EL1": a64SysReg{0x184600, true, true},
|
||||||
|
"ICC_RPR_EL1": a64SysReg{0x18cb60, true, false},
|
||||||
|
"ICC_SGI0R_EL1": a64SysReg{0x18cbe0, false, true},
|
||||||
|
"ICC_SGI1R_EL1": a64SysReg{0x18cba0, false, true},
|
||||||
|
"ICC_SRE_EL1": a64SysReg{0x18cca0, true, true},
|
||||||
|
"ICV_AP0R0_EL1": a64SysReg{0x18c880, true, true},
|
||||||
|
"ICV_AP0R1_EL1": a64SysReg{0x18c8a0, true, true},
|
||||||
|
"ICV_AP0R2_EL1": a64SysReg{0x18c8c0, true, true},
|
||||||
|
"ICV_AP0R3_EL1": a64SysReg{0x18c8e0, true, true},
|
||||||
|
"ICV_AP1R0_EL1": a64SysReg{0x18c900, true, true},
|
||||||
|
"ICV_AP1R1_EL1": a64SysReg{0x18c920, true, true},
|
||||||
|
"ICV_AP1R2_EL1": a64SysReg{0x18c940, true, true},
|
||||||
|
"ICV_AP1R3_EL1": a64SysReg{0x18c960, true, true},
|
||||||
|
"ICV_BPR0_EL1": a64SysReg{0x18c860, true, true},
|
||||||
|
"ICV_BPR1_EL1": a64SysReg{0x18cc60, true, true},
|
||||||
|
"ICV_CTLR_EL1": a64SysReg{0x18cc80, true, true},
|
||||||
|
"ICV_DIR_EL1": a64SysReg{0x18cb20, false, true},
|
||||||
|
"ICV_EOIR0_EL1": a64SysReg{0x18c820, false, true},
|
||||||
|
"ICV_EOIR1_EL1": a64SysReg{0x18cc20, false, true},
|
||||||
|
"ICV_HPPIR0_EL1": a64SysReg{0x18c840, true, false},
|
||||||
|
"ICV_HPPIR1_EL1": a64SysReg{0x18cc40, true, false},
|
||||||
|
"ICV_IAR0_EL1": a64SysReg{0x18c800, true, false},
|
||||||
|
"ICV_IAR1_EL1": a64SysReg{0x18cc00, true, false},
|
||||||
|
"ICV_IGRPEN0_EL1": a64SysReg{0x18ccc0, true, true},
|
||||||
|
"ICV_IGRPEN1_EL1": a64SysReg{0x18cce0, true, true},
|
||||||
|
"ICV_PMR_EL1": a64SysReg{0x184600, true, true},
|
||||||
|
"ICV_RPR_EL1": a64SysReg{0x18cb60, true, false},
|
||||||
|
"ID_AA64AFR0_EL1": a64SysReg{0x180580, true, false},
|
||||||
|
"ID_AA64AFR1_EL1": a64SysReg{0x1805a0, true, false},
|
||||||
|
"ID_AA64DFR0_EL1": a64SysReg{0x180500, true, false},
|
||||||
|
"ID_AA64DFR1_EL1": a64SysReg{0x180520, true, false},
|
||||||
|
"ID_AA64ISAR0_EL1": a64SysReg{0x180600, true, false},
|
||||||
|
"ID_AA64ISAR1_EL1": a64SysReg{0x180620, true, false},
|
||||||
|
"ID_AA64MMFR0_EL1": a64SysReg{0x180700, true, false},
|
||||||
|
"ID_AA64MMFR1_EL1": a64SysReg{0x180720, true, false},
|
||||||
|
"ID_AA64MMFR2_EL1": a64SysReg{0x180740, true, false},
|
||||||
|
"ID_AA64PFR0_EL1": a64SysReg{0x180400, true, false},
|
||||||
|
"ID_AA64PFR1_EL1": a64SysReg{0x180420, true, false},
|
||||||
|
"ID_AA64ZFR0_EL1": a64SysReg{0x180480, true, false},
|
||||||
|
"ID_AFR0_EL1": a64SysReg{0x180160, true, false},
|
||||||
|
"ID_DFR0_EL1": a64SysReg{0x180140, true, false},
|
||||||
|
"ID_ISAR0_EL1": a64SysReg{0x180200, true, false},
|
||||||
|
"ID_ISAR1_EL1": a64SysReg{0x180220, true, false},
|
||||||
|
"ID_ISAR2_EL1": a64SysReg{0x180240, true, false},
|
||||||
|
"ID_ISAR3_EL1": a64SysReg{0x180260, true, false},
|
||||||
|
"ID_ISAR4_EL1": a64SysReg{0x180280, true, false},
|
||||||
|
"ID_ISAR5_EL1": a64SysReg{0x1802a0, true, false},
|
||||||
|
"ID_ISAR6_EL1": a64SysReg{0x1802e0, true, false},
|
||||||
|
"ID_MMFR0_EL1": a64SysReg{0x180180, true, false},
|
||||||
|
"ID_MMFR1_EL1": a64SysReg{0x1801a0, true, false},
|
||||||
|
"ID_MMFR2_EL1": a64SysReg{0x1801c0, true, false},
|
||||||
|
"ID_MMFR3_EL1": a64SysReg{0x1801e0, true, false},
|
||||||
|
"ID_MMFR4_EL1": a64SysReg{0x1802c0, true, false},
|
||||||
|
"ID_PFR0_EL1": a64SysReg{0x180100, true, false},
|
||||||
|
"ID_PFR1_EL1": a64SysReg{0x180120, true, false},
|
||||||
|
"ID_PFR2_EL1": a64SysReg{0x180380, true, false},
|
||||||
|
"ISR_EL1": a64SysReg{0x18c100, true, false},
|
||||||
|
"LORC_EL1": a64SysReg{0x18a460, true, true},
|
||||||
|
"LOREA_EL1": a64SysReg{0x18a420, true, true},
|
||||||
|
"LORID_EL1": a64SysReg{0x18a4e0, true, false},
|
||||||
|
"LORN_EL1": a64SysReg{0x18a440, true, true},
|
||||||
|
"LORSA_EL1": a64SysReg{0x18a400, true, true},
|
||||||
|
"MAIR_EL1": a64SysReg{0x18a200, true, true},
|
||||||
|
"MDCCINT_EL1": a64SysReg{0x100200, true, true},
|
||||||
|
"MDCCSR_EL0": a64SysReg{0x130100, true, false},
|
||||||
|
"MDRAR_EL1": a64SysReg{0x101000, true, false},
|
||||||
|
"MDSCR_EL1": a64SysReg{0x100240, true, true},
|
||||||
|
"MIDR_EL1": a64SysReg{0x180000, true, false},
|
||||||
|
"MPAM0_EL1": a64SysReg{0x18a520, true, true},
|
||||||
|
"MPAM1_EL1": a64SysReg{0x18a500, true, true},
|
||||||
|
"MPAMIDR_EL1": a64SysReg{0x18a480, true, false},
|
||||||
|
"MPIDR_EL1": a64SysReg{0x1800a0, true, false},
|
||||||
|
"MVFR0_EL1": a64SysReg{0x180300, true, false},
|
||||||
|
"MVFR1_EL1": a64SysReg{0x180320, true, false},
|
||||||
|
"MVFR2_EL1": a64SysReg{0x180340, true, false},
|
||||||
|
"NZCV": a64SysReg{0x1b4200, true, true},
|
||||||
|
"OSDLR_EL1": a64SysReg{0x101380, true, true},
|
||||||
|
"OSDTRRX_EL1": a64SysReg{0x100040, true, true},
|
||||||
|
"OSDTRTX_EL1": a64SysReg{0x100340, true, true},
|
||||||
|
"OSECCR_EL1": a64SysReg{0x100640, true, true},
|
||||||
|
"OSLAR_EL1": a64SysReg{0x101080, false, true},
|
||||||
|
"OSLSR_EL1": a64SysReg{0x101180, true, false},
|
||||||
|
"PAN": a64SysReg{0x184260, true, true},
|
||||||
|
"PAR_EL1": a64SysReg{0x187400, true, true},
|
||||||
|
"PMBIDR_EL1": a64SysReg{0x189ae0, true, false},
|
||||||
|
"PMBLIMITR_EL1": a64SysReg{0x189a00, true, true},
|
||||||
|
"PMBPTR_EL1": a64SysReg{0x189a20, true, true},
|
||||||
|
"PMBSR_EL1": a64SysReg{0x189a60, true, true},
|
||||||
|
"PMCCFILTR_EL0": a64SysReg{0x1befe0, true, true},
|
||||||
|
"PMCCNTR_EL0": a64SysReg{0x1b9d00, true, true},
|
||||||
|
"PMCEID0_EL0": a64SysReg{0x1b9cc0, true, false},
|
||||||
|
"PMCEID1_EL0": a64SysReg{0x1b9ce0, true, false},
|
||||||
|
"PMCNTENCLR_EL0": a64SysReg{0x1b9c40, true, true},
|
||||||
|
"PMCNTENSET_EL0": a64SysReg{0x1b9c20, true, true},
|
||||||
|
"PMCR_EL0": a64SysReg{0x1b9c00, true, true},
|
||||||
|
"PMEVCNTR0_EL0": a64SysReg{0x1be800, true, true},
|
||||||
|
"PMEVCNTR1_EL0": a64SysReg{0x1be820, true, true},
|
||||||
|
"PMEVCNTR2_EL0": a64SysReg{0x1be840, true, true},
|
||||||
|
"PMEVCNTR3_EL0": a64SysReg{0x1be860, true, true},
|
||||||
|
"PMEVCNTR4_EL0": a64SysReg{0x1be880, true, true},
|
||||||
|
"PMEVCNTR5_EL0": a64SysReg{0x1be8a0, true, true},
|
||||||
|
"PMEVCNTR6_EL0": a64SysReg{0x1be8c0, true, true},
|
||||||
|
"PMEVCNTR7_EL0": a64SysReg{0x1be8e0, true, true},
|
||||||
|
"PMEVCNTR8_EL0": a64SysReg{0x1be900, true, true},
|
||||||
|
"PMEVCNTR9_EL0": a64SysReg{0x1be920, true, true},
|
||||||
|
"PMEVCNTR10_EL0": a64SysReg{0x1be940, true, true},
|
||||||
|
"PMEVCNTR11_EL0": a64SysReg{0x1be960, true, true},
|
||||||
|
"PMEVCNTR12_EL0": a64SysReg{0x1be980, true, true},
|
||||||
|
"PMEVCNTR13_EL0": a64SysReg{0x1be9a0, true, true},
|
||||||
|
"PMEVCNTR14_EL0": a64SysReg{0x1be9c0, true, true},
|
||||||
|
"PMEVCNTR15_EL0": a64SysReg{0x1be9e0, true, true},
|
||||||
|
"PMEVCNTR16_EL0": a64SysReg{0x1bea00, true, true},
|
||||||
|
"PMEVCNTR17_EL0": a64SysReg{0x1bea20, true, true},
|
||||||
|
"PMEVCNTR18_EL0": a64SysReg{0x1bea40, true, true},
|
||||||
|
"PMEVCNTR19_EL0": a64SysReg{0x1bea60, true, true},
|
||||||
|
"PMEVCNTR20_EL0": a64SysReg{0x1bea80, true, true},
|
||||||
|
"PMEVCNTR21_EL0": a64SysReg{0x1beaa0, true, true},
|
||||||
|
"PMEVCNTR22_EL0": a64SysReg{0x1beac0, true, true},
|
||||||
|
"PMEVCNTR23_EL0": a64SysReg{0x1beae0, true, true},
|
||||||
|
"PMEVCNTR24_EL0": a64SysReg{0x1beb00, true, true},
|
||||||
|
"PMEVCNTR25_EL0": a64SysReg{0x1beb20, true, true},
|
||||||
|
"PMEVCNTR26_EL0": a64SysReg{0x1beb40, true, true},
|
||||||
|
"PMEVCNTR27_EL0": a64SysReg{0x1beb60, true, true},
|
||||||
|
"PMEVCNTR28_EL0": a64SysReg{0x1beb80, true, true},
|
||||||
|
"PMEVCNTR29_EL0": a64SysReg{0x1beba0, true, true},
|
||||||
|
"PMEVCNTR30_EL0": a64SysReg{0x1bebc0, true, true},
|
||||||
|
"PMEVTYPER0_EL0": a64SysReg{0x1bec00, true, true},
|
||||||
|
"PMEVTYPER1_EL0": a64SysReg{0x1bec20, true, true},
|
||||||
|
"PMEVTYPER2_EL0": a64SysReg{0x1bec40, true, true},
|
||||||
|
"PMEVTYPER3_EL0": a64SysReg{0x1bec60, true, true},
|
||||||
|
"PMEVTYPER4_EL0": a64SysReg{0x1bec80, true, true},
|
||||||
|
"PMEVTYPER5_EL0": a64SysReg{0x1beca0, true, true},
|
||||||
|
"PMEVTYPER6_EL0": a64SysReg{0x1becc0, true, true},
|
||||||
|
"PMEVTYPER7_EL0": a64SysReg{0x1bece0, true, true},
|
||||||
|
"PMEVTYPER8_EL0": a64SysReg{0x1bed00, true, true},
|
||||||
|
"PMEVTYPER9_EL0": a64SysReg{0x1bed20, true, true},
|
||||||
|
"PMEVTYPER10_EL0": a64SysReg{0x1bed40, true, true},
|
||||||
|
"PMEVTYPER11_EL0": a64SysReg{0x1bed60, true, true},
|
||||||
|
"PMEVTYPER12_EL0": a64SysReg{0x1bed80, true, true},
|
||||||
|
"PMEVTYPER13_EL0": a64SysReg{0x1beda0, true, true},
|
||||||
|
"PMEVTYPER14_EL0": a64SysReg{0x1bedc0, true, true},
|
||||||
|
"PMEVTYPER15_EL0": a64SysReg{0x1bede0, true, true},
|
||||||
|
"PMEVTYPER16_EL0": a64SysReg{0x1bee00, true, true},
|
||||||
|
"PMEVTYPER17_EL0": a64SysReg{0x1bee20, true, true},
|
||||||
|
"PMEVTYPER18_EL0": a64SysReg{0x1bee40, true, true},
|
||||||
|
"PMEVTYPER19_EL0": a64SysReg{0x1bee60, true, true},
|
||||||
|
"PMEVTYPER20_EL0": a64SysReg{0x1bee80, true, true},
|
||||||
|
"PMEVTYPER21_EL0": a64SysReg{0x1beea0, true, true},
|
||||||
|
"PMEVTYPER22_EL0": a64SysReg{0x1beec0, true, true},
|
||||||
|
"PMEVTYPER23_EL0": a64SysReg{0x1beee0, true, true},
|
||||||
|
"PMEVTYPER24_EL0": a64SysReg{0x1bef00, true, true},
|
||||||
|
"PMEVTYPER25_EL0": a64SysReg{0x1bef20, true, true},
|
||||||
|
"PMEVTYPER26_EL0": a64SysReg{0x1bef40, true, true},
|
||||||
|
"PMEVTYPER27_EL0": a64SysReg{0x1bef60, true, true},
|
||||||
|
"PMEVTYPER28_EL0": a64SysReg{0x1bef80, true, true},
|
||||||
|
"PMEVTYPER29_EL0": a64SysReg{0x1befa0, true, true},
|
||||||
|
"PMEVTYPER30_EL0": a64SysReg{0x1befc0, true, true},
|
||||||
|
"PMINTENCLR_EL1": a64SysReg{0x189e40, true, true},
|
||||||
|
"PMINTENSET_EL1": a64SysReg{0x189e20, true, true},
|
||||||
|
"PMMIR_EL1": a64SysReg{0x189ec0, true, false},
|
||||||
|
"PMOVSCLR_EL0": a64SysReg{0x1b9c60, true, true},
|
||||||
|
"PMOVSSET_EL0": a64SysReg{0x1b9e60, true, true},
|
||||||
|
"PMSCR_EL1": a64SysReg{0x189900, true, true},
|
||||||
|
"PMSELR_EL0": a64SysReg{0x1b9ca0, true, true},
|
||||||
|
"PMSEVFR_EL1": a64SysReg{0x1899a0, true, true},
|
||||||
|
"PMSFCR_EL1": a64SysReg{0x189980, true, true},
|
||||||
|
"PMSICR_EL1": a64SysReg{0x189940, true, true},
|
||||||
|
"PMSIDR_EL1": a64SysReg{0x1899e0, true, false},
|
||||||
|
"PMSIRR_EL1": a64SysReg{0x189960, true, true},
|
||||||
|
"PMSLATFR_EL1": a64SysReg{0x1899c0, true, true},
|
||||||
|
"PMSWINC_EL0": a64SysReg{0x1b9c80, false, true},
|
||||||
|
"PMUSERENR_EL0": a64SysReg{0x1b9e00, true, true},
|
||||||
|
"PMXEVCNTR_EL0": a64SysReg{0x1b9d40, true, true},
|
||||||
|
"PMXEVTYPER_EL0": a64SysReg{0x1b9d20, true, true},
|
||||||
|
"REVIDR_EL1": a64SysReg{0x1800c0, true, false},
|
||||||
|
"RGSR_EL1": a64SysReg{0x1810a0, true, true},
|
||||||
|
"RMR_EL1": a64SysReg{0x18c040, true, true},
|
||||||
|
"RNDR": a64SysReg{0x1b2400, true, false},
|
||||||
|
"RNDRRS": a64SysReg{0x1b2420, true, false},
|
||||||
|
"RVBAR_EL1": a64SysReg{0x18c020, true, false},
|
||||||
|
"SCTLR_EL1": a64SysReg{0x181000, true, true},
|
||||||
|
"SCXTNUM_EL0": a64SysReg{0x1bd0e0, true, true},
|
||||||
|
"SCXTNUM_EL1": a64SysReg{0x18d0e0, true, true},
|
||||||
|
"SP_EL0": a64SysReg{0x184100, true, true},
|
||||||
|
"SP_EL1": a64SysReg{0x1c4100, true, true},
|
||||||
|
"SPSel": a64SysReg{0x184200, true, true},
|
||||||
|
"SPSR_abt": a64SysReg{0x1c4320, true, true},
|
||||||
|
"SPSR_EL1": a64SysReg{0x184000, true, true},
|
||||||
|
"SPSR_fiq": a64SysReg{0x1c4360, true, true},
|
||||||
|
"SPSR_irq": a64SysReg{0x1c4300, true, true},
|
||||||
|
"SPSR_und": a64SysReg{0x1c4340, true, true},
|
||||||
|
"SSBS": a64SysReg{0x1b42c0, true, true},
|
||||||
|
"TCO": a64SysReg{0x1b42e0, true, true},
|
||||||
|
"TCR_EL1": a64SysReg{0x182040, true, true},
|
||||||
|
"TFSR_EL1": a64SysReg{0x185600, true, true},
|
||||||
|
"TFSRE0_EL1": a64SysReg{0x185620, true, true},
|
||||||
|
"TPIDR_EL0": a64SysReg{0x1bd040, true, true},
|
||||||
|
"TPIDR_EL1": a64SysReg{0x18d080, true, true},
|
||||||
|
"TPIDRRO_EL0": a64SysReg{0x1bd060, true, true},
|
||||||
|
"TRFCR_EL1": a64SysReg{0x181220, true, true},
|
||||||
|
"TTBR0_EL1": a64SysReg{0x182000, true, true},
|
||||||
|
"TTBR1_EL1": a64SysReg{0x182020, true, true},
|
||||||
|
"UAO": a64SysReg{0x184280, true, true},
|
||||||
|
"VBAR_EL1": a64SysReg{0x18c000, true, true},
|
||||||
|
"ZCR_EL1": a64SysReg{0x181200, true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SysInst is one TLBI alias: the fields the SYS encoding carries beside
|
||||||
|
// the fixed op0 = 01 and CRn = 8.
|
||||||
|
type a64SysInst struct {
|
||||||
|
op1, cm, op2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64TLBIOps maps the TLBI operation names to their fields; the register
|
||||||
|
// operand is optional and defaults to ZR.
|
||||||
|
var a64TLBIOps = map[string]a64SysInst{
|
||||||
|
"ALLE1": {0x4, 0x7, 0x4},
|
||||||
|
"ALLE1IS": {0x4, 0x3, 0x4},
|
||||||
|
"ALLE1OS": {0x4, 0x1, 0x4},
|
||||||
|
"ALLE2": {0x4, 0x7, 0x0},
|
||||||
|
"ALLE2IS": {0x4, 0x3, 0x0},
|
||||||
|
"ALLE2OS": {0x4, 0x1, 0x0},
|
||||||
|
"ALLE3": {0x6, 0x7, 0x0},
|
||||||
|
"ALLE3IS": {0x6, 0x3, 0x0},
|
||||||
|
"ALLE3OS": {0x6, 0x1, 0x0},
|
||||||
|
"ASIDE1": {0x0, 0x7, 0x2},
|
||||||
|
"ASIDE1IS": {0x0, 0x3, 0x2},
|
||||||
|
"ASIDE1OS": {0x0, 0x1, 0x2},
|
||||||
|
"IPAS2E1": {0x4, 0x4, 0x1},
|
||||||
|
"IPAS2E1IS": {0x4, 0x0, 0x1},
|
||||||
|
"IPAS2E1OS": {0x4, 0x4, 0x0},
|
||||||
|
"IPAS2LE1": {0x4, 0x4, 0x5},
|
||||||
|
"IPAS2LE1IS": {0x4, 0x0, 0x5},
|
||||||
|
"IPAS2LE1OS": {0x4, 0x4, 0x4},
|
||||||
|
"RIPAS2E1": {0x4, 0x4, 0x2},
|
||||||
|
"RIPAS2E1IS": {0x4, 0x0, 0x2},
|
||||||
|
"RIPAS2E1OS": {0x4, 0x4, 0x3},
|
||||||
|
"RIPAS2LE1": {0x4, 0x4, 0x6},
|
||||||
|
"RIPAS2LE1IS": {0x4, 0x0, 0x6},
|
||||||
|
"RIPAS2LE1OS": {0x4, 0x4, 0x7},
|
||||||
|
"RVAAE1": {0x0, 0x6, 0x3},
|
||||||
|
"RVAAE1IS": {0x0, 0x2, 0x3},
|
||||||
|
"RVAAE1OS": {0x0, 0x5, 0x3},
|
||||||
|
"RVAALE1": {0x0, 0x6, 0x7},
|
||||||
|
"RVAALE1IS": {0x0, 0x2, 0x7},
|
||||||
|
"RVAALE1OS": {0x0, 0x5, 0x7},
|
||||||
|
"RVAE1": {0x0, 0x6, 0x1},
|
||||||
|
"RVAE1IS": {0x0, 0x2, 0x1},
|
||||||
|
"RVAE1OS": {0x0, 0x5, 0x1},
|
||||||
|
"RVAE2": {0x4, 0x6, 0x1},
|
||||||
|
"RVAE2IS": {0x4, 0x2, 0x1},
|
||||||
|
"RVAE2OS": {0x4, 0x5, 0x1},
|
||||||
|
"RVAE3": {0x6, 0x6, 0x1},
|
||||||
|
"RVAE3IS": {0x6, 0x2, 0x1},
|
||||||
|
"RVAE3OS": {0x6, 0x5, 0x1},
|
||||||
|
"RVALE1": {0x0, 0x6, 0x5},
|
||||||
|
"RVALE1IS": {0x0, 0x2, 0x5},
|
||||||
|
"RVALE1OS": {0x0, 0x5, 0x5},
|
||||||
|
"RVALE2": {0x4, 0x6, 0x5},
|
||||||
|
"RVALE2IS": {0x4, 0x2, 0x5},
|
||||||
|
"RVALE2OS": {0x4, 0x5, 0x5},
|
||||||
|
"RVALE3": {0x6, 0x6, 0x5},
|
||||||
|
"RVALE3IS": {0x6, 0x2, 0x5},
|
||||||
|
"RVALE3OS": {0x6, 0x5, 0x5},
|
||||||
|
"VAAE1": {0x0, 0x7, 0x3},
|
||||||
|
"VAAE1IS": {0x0, 0x3, 0x3},
|
||||||
|
"VAAE1OS": {0x0, 0x1, 0x3},
|
||||||
|
"VAALE1": {0x0, 0x7, 0x7},
|
||||||
|
"VAALE1IS": {0x0, 0x3, 0x7},
|
||||||
|
"VAALE1OS": {0x0, 0x1, 0x7},
|
||||||
|
"VAE1": {0x0, 0x7, 0x1},
|
||||||
|
"VAE1IS": {0x0, 0x3, 0x1},
|
||||||
|
"VAE1OS": {0x0, 0x1, 0x1},
|
||||||
|
"VAE2": {0x4, 0x7, 0x1},
|
||||||
|
"VAE2IS": {0x4, 0x3, 0x1},
|
||||||
|
"VAE2OS": {0x4, 0x1, 0x1},
|
||||||
|
"VAE3": {0x6, 0x7, 0x1},
|
||||||
|
"VAE3IS": {0x6, 0x3, 0x1},
|
||||||
|
"VAE3OS": {0x6, 0x1, 0x1},
|
||||||
|
"VALE1": {0x0, 0x7, 0x5},
|
||||||
|
"VALE1IS": {0x0, 0x3, 0x5},
|
||||||
|
"VALE1OS": {0x0, 0x1, 0x5},
|
||||||
|
"VALE2": {0x4, 0x7, 0x5},
|
||||||
|
"VALE2IS": {0x4, 0x3, 0x5},
|
||||||
|
"VALE2OS": {0x4, 0x1, 0x5},
|
||||||
|
"VALE3": {0x6, 0x7, 0x5},
|
||||||
|
"VALE3IS": {0x6, 0x3, 0x5},
|
||||||
|
"VALE3OS": {0x6, 0x1, 0x5},
|
||||||
|
"VMALLE1": {0x0, 0x7, 0x0},
|
||||||
|
"VMALLE1IS": {0x0, 0x3, 0x0},
|
||||||
|
"VMALLE1OS": {0x0, 0x1, 0x0},
|
||||||
|
"VMALLS12E1": {0x4, 0x7, 0x6},
|
||||||
|
"VMALLS12E1IS": {0x4, 0x3, 0x6},
|
||||||
|
"VMALLS12E1OS": {0x4, 0x1, 0x6},
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64TLBITakesReg reports whether a TLBI operation spells a by-address
|
||||||
|
// invalidation that carries the address in its optional second register
|
||||||
|
// (asm7.go's sysInstFields hasOperand2). The whole-entry spellings are
|
||||||
|
// exactly the VMALL-prefixed and the ALL-prefixed ones; everything else
|
||||||
|
// (VAE*, VAAE*, VALE*, RVAA*, RVAE*, ASIDE1*, IPAS2*, RIPAS2*) is
|
||||||
|
// by-address and takes the register.
|
||||||
|
func arm64TLBITakesReg(name string) bool {
|
||||||
|
return !strings.HasPrefix(name, "VMALL") && !strings.HasPrefix(name, "ALL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64DCOps2 maps the DC operation names to their fields; the register
|
||||||
|
// operand is mandatory.
|
||||||
|
var a64DCOps2 = map[string]a64SysInst{
|
||||||
|
"CGDSW": {0x0, 0xa, 0x6},
|
||||||
|
"CGDVAC": {0x3, 0xa, 0x5},
|
||||||
|
"CGDVADP": {0x3, 0xd, 0x5},
|
||||||
|
"CGDVAP": {0x3, 0xc, 0x5},
|
||||||
|
"CGSW": {0x0, 0xa, 0x4},
|
||||||
|
"CGVAC": {0x3, 0xa, 0x3},
|
||||||
|
"CGVADP": {0x3, 0xd, 0x3},
|
||||||
|
"CGVAP": {0x3, 0xc, 0x3},
|
||||||
|
"CIGDSW": {0x0, 0xe, 0x6},
|
||||||
|
"CIGDVAC": {0x3, 0xe, 0x5},
|
||||||
|
"CIGSW": {0x0, 0xe, 0x4},
|
||||||
|
"CIGVAC": {0x3, 0xe, 0x3},
|
||||||
|
"CISW": {0x0, 0xe, 0x2},
|
||||||
|
"CIVAC": {0x3, 0xe, 0x1},
|
||||||
|
"CSW": {0x0, 0xa, 0x2},
|
||||||
|
"CVAC": {0x3, 0xa, 0x1},
|
||||||
|
"CVADP": {0x3, 0xd, 0x1},
|
||||||
|
"CVAP": {0x3, 0xc, 0x1},
|
||||||
|
"CVAU": {0x3, 0xb, 0x1},
|
||||||
|
"GVA": {0x3, 0x4, 0x3},
|
||||||
|
"GZVA": {0x3, 0x4, 0x4},
|
||||||
|
"IGDSW": {0x0, 0x6, 0x6},
|
||||||
|
"IGDVAC": {0x0, 0x6, 0x5},
|
||||||
|
"IGSW": {0x0, 0x6, 0x4},
|
||||||
|
"IGVAC": {0x0, 0x6, 0x3},
|
||||||
|
"ISW": {0x0, 0x6, 0x2},
|
||||||
|
"IVAC": {0x0, 0x6, 0x1},
|
||||||
|
"ZVA": {0x3, 0x4, 0x1},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64RPRFOps maps the range-prefetch operation names to their 6-bit values.
|
||||||
|
var a64RPRFOps = map[string]uint32{
|
||||||
|
"PLDKEEP": 0,
|
||||||
|
"PLDSTRM": 4,
|
||||||
|
"PSTKEEP": 1,
|
||||||
|
"PSTSTRM": 5,
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestARM64SysRegsDifferential proves the whole system-register table against
|
||||||
|
// the toolchain at once: one TEXT whose body reads every register the table
|
||||||
|
// carries (and writes every writable one), assembled by gasm and by
|
||||||
|
// go tool asm, must agree byte for byte. A single wrong op0/op1/CRn/CRm/op2
|
||||||
|
// packing names its register through the first differing word.
|
||||||
|
func TestARM64SysRegsDifferential(t *testing.T) {
|
||||||
|
names := make([]string, 0, len(a64SysRegs))
|
||||||
|
for name := range a64SysRegs {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
slices.Sort(names)
|
||||||
|
|
||||||
|
var body strings.Builder
|
||||||
|
for i, name := range names {
|
||||||
|
// R18 is the arm64 platform register and R29-R31 carry dedicated
|
||||||
|
// meanings; a plain read/write destination keeps to R0-R17.
|
||||||
|
reg := fmt.Sprintf("R%d", i%18)
|
||||||
|
if a64SysRegs[name].read {
|
||||||
|
body.WriteString(fmt.Sprintf("\tMRS %s, %s\n", name, reg))
|
||||||
|
}
|
||||||
|
if a64SysRegs[name].write {
|
||||||
|
body.WriteString(fmt.Sprintf("\tMSR %s, %s\n", reg, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·sysregs(SB), NOSPLIT, $0\n" + body.String() + "\tRET\n"
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "sysregs_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertARM64Differential(t, path, src, "sysregs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestARM64FamiliesDifferential pins the non-sysreg families the arm64
|
||||||
|
// campaign added: the LSE compare-and-swap pairs, the VMOVI immediate, the
|
||||||
|
// SIMD narrow/long shift pairs, the VLD2/VLD3/VLD4 and VST2/VST3/VST4
|
||||||
|
// structure accesses with their post-index and replicate forms, LDPSW, the
|
||||||
|
// pointer-authentication hint and the DC maintenance operation. Every
|
||||||
|
// spelling is the toolchain's own, taken from its arm64 testdata, and the
|
||||||
|
// bytes must agree word for word.
|
||||||
|
func TestARM64FamiliesDifferential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·families(SB), NOSPLIT, $0
|
||||||
|
CASPD (R2, R3), (R2), (R8, R9)
|
||||||
|
CASPW (R6, R7), (R8), (R4, R5)
|
||||||
|
VMOVI $82, V0.B16
|
||||||
|
VMOVI $146, V22.B16
|
||||||
|
VSSHLL $0, V1.B8, V2.H8
|
||||||
|
VSSHLL $7, V1.B8, V2.H8
|
||||||
|
VSSHLL2 $0, V1.B16, V2.H8
|
||||||
|
VSHRN $7, V1.H8, V0.B8
|
||||||
|
VSHRN2 $31, V1.D2, V0.S4
|
||||||
|
VLD2 (R29), [V23.H8, V24.H8]
|
||||||
|
VLD2.P 16(R0), [V18.B8, V19.B8]
|
||||||
|
VLD2.P (R1)(R2), [V15.S2, V16.S2]
|
||||||
|
VLD3 (R27), [V11.S4, V12.S4, V13.S4]
|
||||||
|
VLD3.P 48(RSP), [V11.S4, V12.S4, V13.S4]
|
||||||
|
VLD4 (R15), [V10.H4, V11.H4, V12.H4, V13.H4]
|
||||||
|
VLD4.P 32(R24), [V31.B8, V0.B8, V1.B8, V2.B8]
|
||||||
|
VLD1R (R1), [V9.B8]
|
||||||
|
VLD1R.P (R0), [V0.B16]
|
||||||
|
VLD1R.P 2(R1), [V2.H4]
|
||||||
|
VLD2R (R15), [V15.H4, V16.H4]
|
||||||
|
VLD2R.P 16(R0), [V0.D2, V1.D2]
|
||||||
|
VLD4R (R0), [V0.B8, V1.B8, V2.B8, V3.B8]
|
||||||
|
VLD4R.P 16(RSP), [V31.S4, V0.S4, V1.S4, V2.S4]
|
||||||
|
VST2 [V22.H8, V23.H8], (R23)
|
||||||
|
VST2.P [V14.H4, V15.H4], 16(R17)
|
||||||
|
VST2.P [V14.H4, V15.H4], (R3)(R17)
|
||||||
|
VST3 [V1.D2, V2.D2, V3.D2], (R11)
|
||||||
|
VST3.P [V18.S4, V19.S4, V20.S4], 48(R25)
|
||||||
|
VST4 [V22.D2, V23.D2, V24.D2, V25.D2], (R3)
|
||||||
|
VST4.P [V14.D2, V15.D2, V16.D2, V17.D2], 64(R15)
|
||||||
|
LDPSW (R0), (R1, R2)
|
||||||
|
LDPSW 4(R0), (R1, R2)
|
||||||
|
LDPSW -4(R0), (R1, R2)
|
||||||
|
PACIASP
|
||||||
|
DC IVAC, R1
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "families_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertARM64Differential(t, path, src, "families")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertARM64Differential assembles the same source with gasm and with the
|
||||||
|
// toolchain for arm64 and requires the named function's code bytes to agree.
|
||||||
|
// The live oracle is a deliberate-run comparison, so -short skips it (the
|
||||||
|
// push pipeline's mode); the golden bytes of the individual encoders are
|
||||||
|
// pinned separately in every mode.
|
||||||
|
func assertARM64Differential(t *testing.T, path, src, fn string) {
|
||||||
|
t.Helper()
|
||||||
|
oracle := oracleFuncCode(t, toolAsmObject(t, path, "arm64"))
|
||||||
|
|
||||||
|
// The oracle keys its functions by the qualified object name
|
||||||
|
// (pkg.name); match on the local part.
|
||||||
|
want := map[string][]byte{}
|
||||||
|
for name, code := range oracle {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
want[after] = code
|
||||||
|
} else {
|
||||||
|
want[name] = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if want[fn] == nil {
|
||||||
|
t.Fatalf("the oracle object carries no function %q (has %v)", fn, keysOf(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
f, perrs := parser.Parse(path, src)
|
||||||
|
if len(perrs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", perrs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
got := trimTrailingZeroWords(img.Code)
|
||||||
|
wantB := trimTrailingZeroWords(want[fn])
|
||||||
|
if len(got) != len(wantB) {
|
||||||
|
t.Fatalf("gasm %d bytes, oracle %d bytes", len(got), len(wantB))
|
||||||
|
}
|
||||||
|
for i := range wantB {
|
||||||
|
if got[i] != wantB[i] {
|
||||||
|
t.Fatalf("word %d differs: gasm %08x, oracle %08x", i/4,
|
||||||
|
binary.LittleEndian.Uint32(got[i:i+4]), binary.LittleEndian.Uint32(wantB[i:i+4]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimTrailingZeroWords drops whole zero words off the end of a code span:
|
||||||
|
// an object pads a function to its alignment, and the raw image does not.
|
||||||
|
// A difference in the middle survives the trim untouched.
|
||||||
|
func trimTrailingZeroWords(b []byte) []byte {
|
||||||
|
for len(b) >= 4 {
|
||||||
|
last := b[len(b)-4:]
|
||||||
|
if last[0]|last[1]|last[2]|last[3] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = b[:len(b)-4]
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
+802
-56
File diff suppressed because it is too large.
Load diff
+208
-2
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstText parses src and returns its first TEXT function.
|
// firstText parses src and returns its first TEXT function.
|
||||||
@@ -370,6 +370,58 @@ end:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAssembleNumericPCJumps pins the numeric ±N(PC) branch operands: N
|
||||||
|
// counts instruction statements, skipping labels, in both directions (the
|
||||||
|
// runtime's exit loops write JMP -3(PC)), N = 0 parks on the jump itself.
|
||||||
|
func TestAssembleNumericPCJumps(t *testing.T) {
|
||||||
|
fn := firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·exit(SB), NOSPLIT, $0
|
||||||
|
MOVB $1, AL
|
||||||
|
lab:
|
||||||
|
MOVB $2, AL
|
||||||
|
MOVB $3, AL
|
||||||
|
JMP -3(PC)
|
||||||
|
MOVB $4, AL
|
||||||
|
park:
|
||||||
|
JMP 0(PC)
|
||||||
|
MOVB $5, AL
|
||||||
|
JMP 2(PC)
|
||||||
|
MOVB $6, AL
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Assemble: %v", err)
|
||||||
|
}
|
||||||
|
// From the Go-assembled function:
|
||||||
|
// MOVB $1, AL b001
|
||||||
|
// MOVB $2, AL b002
|
||||||
|
// MOVB $3, AL b003
|
||||||
|
// JMP -3(PC) ebf8 (three instructions back, past lab:)
|
||||||
|
// MOVB $4, AL b004
|
||||||
|
// JMP 0(PC) ebfe (the park loop)
|
||||||
|
// MOVB $5, AL b005
|
||||||
|
// JMP 2(PC) eb02 (over MOVB $6 to the RET)
|
||||||
|
// MOVB $6, AL b006
|
||||||
|
// RET c3
|
||||||
|
want := []byte{
|
||||||
|
0xb0, 0x01,
|
||||||
|
0xb0, 0x02,
|
||||||
|
0xb0, 0x03,
|
||||||
|
0xeb, 0xf8,
|
||||||
|
0xb0, 0x04,
|
||||||
|
0xeb, 0xfe,
|
||||||
|
0xb0, 0x05,
|
||||||
|
0xeb, 0x02,
|
||||||
|
0xb0, 0x06,
|
||||||
|
0xc3,
|
||||||
|
}
|
||||||
|
if hexBytes(code) != hexBytes(want) {
|
||||||
|
t.Errorf("numeric-PC mismatch:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAssemblePrefetch(t *testing.T) {
|
func TestAssemblePrefetch(t *testing.T) {
|
||||||
fn := firstText(t, `
|
fn := firstText(t, `
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
@@ -439,3 +491,157 @@ func TestSubSPEncodings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAssemblePseudoStatements runs LOCK/REP, BYTE/WORD and END through the
|
||||||
|
// full statement pipeline, pinned against go tool asm (Go 1.27, amd64). It
|
||||||
|
// asserts the three behaviours the toolchain shows: each prefix statement is
|
||||||
|
// a standalone byte with a PC of its own (so a label placed on the LOCK
|
||||||
|
// points at the F0), the data pseudo-ops write their literal bytes inline,
|
||||||
|
// and END terminates nothing (the statements after it still belong to the
|
||||||
|
// function and carry no trace of it).
|
||||||
|
func TestAssemblePseudoStatements(t *testing.T) {
|
||||||
|
fn := firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·pseudo(SB), NOSPLIT, $0-0
|
||||||
|
pfx:
|
||||||
|
LOCK
|
||||||
|
CMPXCHGQ AX, (BX)
|
||||||
|
REP
|
||||||
|
MOVSQ
|
||||||
|
BYTE $0x0f
|
||||||
|
BYTE $0x1f
|
||||||
|
WORD $0x1234
|
||||||
|
END
|
||||||
|
BYTE $0x02
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, labels, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Assemble: %v", err)
|
||||||
|
}
|
||||||
|
// go tool asm: f0 480fb103 f3 48a5 0f 1f 3412 02 c3
|
||||||
|
want := []byte{
|
||||||
|
0xf0,
|
||||||
|
0x48, 0x0f, 0xb1, 0x03,
|
||||||
|
0xf3, 0x48, 0xa5,
|
||||||
|
0x0f, 0x1f, 0x34, 0x12,
|
||||||
|
0x02, 0xc3,
|
||||||
|
}
|
||||||
|
if hexBytes(code) != hexBytes(want) {
|
||||||
|
t.Errorf("pseudo statements:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
|
||||||
|
}
|
||||||
|
// The label sits on the LOCK byte, exactly where the toolchain's PC
|
||||||
|
// listing puts it.
|
||||||
|
if off := labels["pfx"]; off != 0 {
|
||||||
|
t.Errorf("label pfx = %d, want 0 (the LOCK's own byte)", off)
|
||||||
|
}
|
||||||
|
// The trailing BYTE lands where the layout says: after the 8 bytes of
|
||||||
|
// LOCK, CMPXCHGQ, REP and MOVSQ plus the 4 data bytes, END contributing
|
||||||
|
// none.
|
||||||
|
if code[12] != 0x02 {
|
||||||
|
t.Errorf("byte at 12 = %02x, want 02 (the BYTE after END)", code[12])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAssembleAdjspBalance pins the toolchain's push/pop balance rule over
|
||||||
|
// ADJSP: the straight-line sum of the adjustments must be zero at each
|
||||||
|
// RET, branches in between counting for nothing (verified against go tool
|
||||||
|
// asm: ADJSP $16 before a RET is reported as "unbalanced PUSH/POP", a
|
||||||
|
// $16/$-16 pair with a JMP in between assembles).
|
||||||
|
func TestAssembleAdjspBalance(t *testing.T) {
|
||||||
|
// Balanced pair with a branch in between, bytes pinned from go tool asm.
|
||||||
|
fn := firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·adjsp(SB), NOSPLIT, $0-0
|
||||||
|
ADJSP $16
|
||||||
|
JMP body
|
||||||
|
body:
|
||||||
|
ADJSP $-16
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Assemble: %v", err)
|
||||||
|
}
|
||||||
|
want := []byte{0x48, 0x83, 0xEC, 0x10, 0xEB, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xC3}
|
||||||
|
if hexBytes(code) != hexBytes(want) {
|
||||||
|
t.Errorf("adjsp pair:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbalanced at the RET: the toolchain diagnoses, so must we.
|
||||||
|
_, _, err = Assemble(firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·unbalanced(SB), NOSPLIT, $0-0
|
||||||
|
ADJSP $16
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "unbalanced PUSH/POP") {
|
||||||
|
t.Errorf("unbalanced ADJSP: err = %v, want unbalanced PUSH/POP", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The check runs per RET: a closed pair before the first RET does not
|
||||||
|
// excuse an open adjustment before the second.
|
||||||
|
_, _, err = Assemble(firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·tworet(SB), NOSPLIT, $0-0
|
||||||
|
ADJSP $8
|
||||||
|
ADJSP $-8
|
||||||
|
RET
|
||||||
|
mid:
|
||||||
|
ADJSP $8
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "unbalanced PUSH/POP") {
|
||||||
|
t.Errorf("second RET with open ADJSP: err = %v, want unbalanced PUSH/POP", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A framed function: the assembler's own prologue and epilogue
|
||||||
|
// contribute matching deltas, so the pair in the body still balances,
|
||||||
|
// and the bytes match go tool asm end to end.
|
||||||
|
fn = firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·framed(SB), $16-8
|
||||||
|
ADJSP $8
|
||||||
|
ADJSP $-8
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, err = Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Assemble framed: %v", err)
|
||||||
|
}
|
||||||
|
want = []byte{
|
||||||
|
0x55, 0x48, 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x10, // prologue
|
||||||
|
0x48, 0x83, 0xEC, 0x08, // ADJSP $8
|
||||||
|
0x48, 0x83, 0xC4, 0x08, // ADJSP $-8
|
||||||
|
0x48, 0x83, 0xC4, 0x10, 0x5D, // epilogue
|
||||||
|
0xC3,
|
||||||
|
}
|
||||||
|
if hexBytes(code) != hexBytes(want) {
|
||||||
|
t.Errorf("framed adjsp:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAssembleRegRange pins the bracketed register range at the statement
|
||||||
|
// level: exactly four consecutive same-width vector registers assemble, the
|
||||||
|
// toolchain's rejected shapes all report an error.
|
||||||
|
func TestAssembleRegRange(t *testing.T) {
|
||||||
|
asm := func(t *testing.T, op string) ([]byte, error) {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("f_amd64.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tV4FMADDPS 17(SP), "+op+", K2, Z0\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse %s: %v", op, errs)
|
||||||
|
}
|
||||||
|
code, _, err := Assemble(f.Decls[0].(*ast.Text))
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
for _, op := range []string{"[Z0-Z3]", "[Z4-Z7]", "[Z28-Z31]"} {
|
||||||
|
if _, err := asm(t, op); err != nil {
|
||||||
|
t.Errorf("%s: %v", op, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, op := range []string{"[Z0-Z4]", "[Z0-Z2]", "[Z0-Z0]", "[Z4-Z0]", "[Z1-Z0]", "[AX-Z3]", "[Z0-AX]"} {
|
||||||
|
if _, err := asm(t, op); err == nil {
|
||||||
|
t.Errorf("%s: assembled, want an error", op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fuzzIncludeDirs points the expansion path at the package's testdata include
|
||||||
|
// directory, so a seed's #include resolves the way the CLI's -I list does.
|
||||||
|
var fuzzIncludeDirs = []string{filepath.Join("testdata", "include")}
|
||||||
|
|
||||||
|
// corpusSeeds seeds every fuzz target with the repository's kernels, so a
|
||||||
|
// plain `go test` run replays each seed as a regression case and CI exercises
|
||||||
|
// them without any fuzzing budget. Kernels of a foreign architecture
|
||||||
|
// exercise the rejection path (the fixed target reports them as diagnostics);
|
||||||
|
// kernels of the target's own architecture reach its encoder.
|
||||||
|
func corpusSeeds(f *testing.F) {
|
||||||
|
for _, pattern := range []string{
|
||||||
|
"../testdata/*.s",
|
||||||
|
"../testdata/verify/*.s",
|
||||||
|
} {
|
||||||
|
files, _ := filepath.Glob(pattern)
|
||||||
|
for _, path := range files {
|
||||||
|
if b, err := os.ReadFile(path); err == nil {
|
||||||
|
f.Add(string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// archCorpusSeeds seeds a target with every assembly file of its architecture
|
||||||
|
// seed directory, testdata/seeds/<dir>. The files hold the target-specific
|
||||||
|
// corpus: the instruction families GOROOT's own assembler corpus exercises for
|
||||||
|
// the architecture, minimalised, plus the boundary shapes the encoder's range
|
||||||
|
// gates live on. They are committed assembly, so `gasm fmt` and `gasm lint`
|
||||||
|
// gate them the way they gate every other .s file. A plain `go test` run
|
||||||
|
// replays each one as a regression case.
|
||||||
|
func archCorpusSeeds(f *testing.F, dir string) {
|
||||||
|
files, _ := filepath.Glob(filepath.Join("testdata", "seeds", dir, "*.s"))
|
||||||
|
for _, path := range files {
|
||||||
|
if b, err := os.ReadFile(path); err == nil {
|
||||||
|
f.Add(string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzAssemble is the whole fuzz body, shared by every target and one line
|
||||||
|
// apart between them: parse with macro and include expansion, assemble
|
||||||
|
// through the target's file-level entry, and hold the invariants. The
|
||||||
|
// contract:
|
||||||
|
//
|
||||||
|
// - no panic, however malformed the source (a crash fails the target);
|
||||||
|
// - a rejected file yields a diagnostic and never a partial emission:
|
||||||
|
// the assembler returns a nil image beside its error, and the
|
||||||
|
// diagnostic is not empty;
|
||||||
|
// - the output is deterministic: the same source, parsed and assembled
|
||||||
|
// again from scratch, produces the same bytes;
|
||||||
|
// - no unbounded memory: the fence around every test run kills a run that
|
||||||
|
// amplifies its input, and the timebox turns a hang into a campaign
|
||||||
|
// failure to bisect.
|
||||||
|
//
|
||||||
|
// A file the parser rejects still reaches the assembler: the parser is
|
||||||
|
// line-oriented and tolerant, so it hands back a usable file either way, and
|
||||||
|
// the assembler's own contract is to answer any file it is given with bytes
|
||||||
|
// or with a diagnostic, never with a panic.
|
||||||
|
func fuzzAssemble(t *testing.T, name string, src string, assemble func(*ast.File) (*Image, error)) {
|
||||||
|
file, _ := parser.ParseWithOptions(name, src,
|
||||||
|
parser.Options{Expand: true, IncludeDirs: fuzzIncludeDirs})
|
||||||
|
if file == nil {
|
||||||
|
t.Fatal("ParseWithOptions returned a nil file")
|
||||||
|
}
|
||||||
|
img, err := assemble(file)
|
||||||
|
if err != nil {
|
||||||
|
if img != nil {
|
||||||
|
t.Fatal("the assembler returned an image beside its error: a rejected file must not emit")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(err.Error()) == "" {
|
||||||
|
t.Fatal("rejection carries an empty diagnostic")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Determinism: a second assembly of the same file must produce the same
|
||||||
|
// bytes. One parse serves both runs, so any mutation the assembler makes
|
||||||
|
// to the syntax tree it was handed shows up as differing bytes, and the
|
||||||
|
// workers' footprint under the shared memory fence stays that of a single
|
||||||
|
// parse.
|
||||||
|
img2, err2 := assemble(file)
|
||||||
|
if err2 != nil {
|
||||||
|
t.Fatalf("the second assembly failed where the first succeeded: %v", err2)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(img.Bytes(), img2.Bytes()) {
|
||||||
|
t.Fatal("the same source assembled to different bytes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzAssembleAMD64 hammers the full parse-and-assemble pipeline for the
|
||||||
|
// fixed amd64 target with arbitrary source: expansion (macros and includes)
|
||||||
|
// included, matching the CLI's own pipeline.
|
||||||
|
func FuzzAssembleAMD64(f *testing.F) {
|
||||||
|
corpusSeeds(f)
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $16-8\n\tMOVQ x+0(FP), AX\n\tMOVQ AX, ret+8(FP)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $256-0\n\tCALL ·helper(SB)\n\tRET\nTEXT ·helper(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#define L(n) MOVQ $n, AX\nTEXT ·f(SB), NOSPLIT, $0\n\tL(7)\n\tRET\n")
|
||||||
|
f.Add("#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#include \"fuzzdefs.h\"\nTEXT ·f(SB), $16-8\n\tMOVQ KONST, AX\n\tMOVQ ARG(x), BX\n\tRET\n")
|
||||||
|
f.Add("DATA d<>+0(SB)/8, $0xf4f8fcff\nDATA d<>+4(SB)/4, $1\nGLOBL d<>(SB), RODATA, $8\n" +
|
||||||
|
"TEXT ·f(SB), NOSPLIT, $0\n\tMOVQ d<>(SB), AX\n\tRET\n")
|
||||||
|
f.Add("DATA s+0(SB)/8, $\"hi there\"\nGLOBL s(SB), $8\nDATA p+0(SB)/8, $s(SB)\nGLOBL p(SB), $8\n")
|
||||||
|
f.Add("DATA d+0(SB)/8, $0xFFFFFFFFFFFFFFFF\nGLOBL d(SB), $8\n" +
|
||||||
|
"TEXT ·f(SB), $0-8\n\tMOVQ $0xFFFFFFFFFFFFFFFF, AX\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\nL1:\n\tMOVQ AX, BX\n\tJMP L1\n\tJMP -3(PC)\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tLOOP L1\nL1:\n\tLOOPE L1\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tJMP *AX\n\tCALL (BX)\n\tJMP (R12)(R8*4)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tMOVQ TLS, AX\n\tMOVQ 8(AX)(TLS*1), BX\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tPCALIGN $16\n\tMOVQ AX, BX\n\tPCALIGN $32\n\tMOVQ AX, BX\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADJSP $16\n\tMOVQ AX, -8(SP)\n\tADJSP $-16\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADDSD $1.5, X0\n\tMULSD $(-1.0), X1\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tSHLL CX, R11:AX\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tPCDATA $0, $1\n\tFUNCDATA $0, ·meta(SB)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tCALL runtime·morestack_noctxt(SB)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $32-0\n\tMOVQ AX, x-8(SP)\n\tMOVQ BX, x-16(SP)(CX*1)\n\tRET\n")
|
||||||
|
// Shapes that must be rejected: each pins a diagnostic path the seeds
|
||||||
|
// above never reach.
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tBOGUSINSTR AX, BX\n\tRET\n")
|
||||||
|
f.Add("GLOBL d(SB), $-8\n")
|
||||||
|
f.Add("GLOBL d(SB), $-1\n")
|
||||||
|
f.Add("GLOBL d(SB), $0x4000001\n")
|
||||||
|
f.Add("GLOBL d(SB), $0x7FFFFFFFFFFFFFFF\n")
|
||||||
|
f.Add("DATA d+0(SB)/9, $1\nGLOBL d(SB), $8\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADJSP $16\n\tRET\n")
|
||||||
|
f.Add("#define A A\nA\n")
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, src string) {
|
||||||
|
fuzzAssemble(t, "fuzz_amd64.s", src, func(f *ast.File) (*Image, error) {
|
||||||
|
return AssembleFile(f)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzAssembleARM64 hammers the same pipeline for the fixed arm64 target,
|
||||||
|
// whose encoder carries its own immediate classification, memory-offset
|
||||||
|
// gates and literal pool. The seeds pin the recent encoder families: the
|
||||||
|
// system registers and barriers, the LSE atomics and exclusive pairs, the
|
||||||
|
// NEON structure loads and stores, the ADDCON2 offset split, the pooled
|
||||||
|
// vector constants, and the macro and include expansion over arm64
|
||||||
|
// spellings. The rejection shapes pin the diagnostic paths the accepted
|
||||||
|
// seeds never reach.
|
||||||
|
func FuzzAssembleARM64(f *testing.F) {
|
||||||
|
corpusSeeds(f)
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $16-8\n\tMOVW x+0(FP), R0\n\tMOVW R0, ret+8(FP)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $256-0\n\tCALL ·helper(SB)\n\tRET\nTEXT ·helper(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#define L(n) MOVD $n, R0\nTEXT ·f(SB), NOSPLIT, $0\n\tL(7)\n\tRET\n")
|
||||||
|
f.Add("#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#include \"fuzzdefs.h\"\nTEXT ·f(SB), $16-8\n\tMOVD KONST, R0\n\tMOVD ARG(x), R1\n\tRET\n")
|
||||||
|
f.Add("DATA d<>+0(SB)/8, $0xf4f8fcff\nDATA d<>+4(SB)/4, $1\nGLOBL d<>(SB), RODATA, $8\n" +
|
||||||
|
"TEXT ·f(SB), NOSPLIT, $0\n\tMOVD d<>(SB), R0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tMRS DCZID_EL0, R3\n\tMRS CNTVCT_EL0, R0\n\tMSR $3, SPSel\n" +
|
||||||
|
"\tMSR $9, DAIFSet\n\tDMB $15\n\tDSB $4\n\tISB $1\n\tDC ZVA, R4\n\tSVC $0\n\tBRK $35943\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tLDADDB R2, (R1), R3\n\tLDADDD R2, (R1), ZR\n\tCASW R2, (R1), R3\n" +
|
||||||
|
"\tSWPD R2, (R1), ZR\n\tLDXR (R1), R2\n\tLDAXRW (R1), R5\n\tSTXR R2, (R1), R6\n\tSTLXRB R3, (R1), R6\n" +
|
||||||
|
"\tLDXP (R1), (R2, R3)\n\tSTXP (R2, R3), (R1), R6\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVLD1 (R2), [V21.B16]\n\tVLD1.P 32(R1), [V2.B16, V3.B16]\n" +
|
||||||
|
"\tVLD1R (R0), [V0.B16]\n\tVST1 [V2.S4, V3.S4], (R14)\n\tVST1.P [V2.B16], (R1)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADD $0xaaaaaa, R2, R3\n\tSUB $0x186a0, R2, R3\n\tADDW $0x60060, R2\n\tCMP $40960, R0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVMOVD $0x123456789ABCDEF0, V0\n\tVMOVQ $0x12345678, $0x9ABCDEF0, V1\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0-8\n\tMOVW $-1, R0\n\tB after1\n\tMOVD $0x0001000200030004, R1\n" +
|
||||||
|
"after1:\n\tMOVD R1, ret+0(FP)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\nL1:\n\tCBZ R1, L1\n\tTBZ $3, R2, L1\n\tBEQ L1\n\tJMP L1\n\tCALL (R5)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tPCALIGN $16\n\tMOVD R0, R1\n\tPCALIGN $32\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tPCDATA $0, $1\n\tFUNCDATA $0, ·meta(SB)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $32-0\n\tMOVD R0, x-8(SP)\n\tRET\n")
|
||||||
|
// Shapes that must be rejected: each pins a diagnostic path the seeds
|
||||||
|
// above never reach.
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tBOGUSINSTR R0, R1\n\tRET\n")
|
||||||
|
f.Add("GLOBL d(SB), $-8\n")
|
||||||
|
f.Add("GLOBL d(SB), $0x4000001\n")
|
||||||
|
f.Add("DATA d+0(SB)/9, $1\nGLOBL d(SB), $8\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADD R1, X99\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tMOVD $1, R1\n\tMOVD 0x1000000(R1), R2\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVLD1 (R2), [V21.B17]\n\tRET\n")
|
||||||
|
f.Add("#define A A\nA\n")
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, src string) {
|
||||||
|
fuzzAssemble(t, "fuzz_arm64.s", src, AssembleFileARM64)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzAssembleRISCV64 hammers the same pipeline for the fixed riscv64 target.
|
||||||
|
// The seed corpus lives in testdata/seeds/riscv64: the instruction families
|
||||||
|
// GOROOT's riscv64 assembler corpus exercises (the immediate-range ladder of
|
||||||
|
// the I-type arithmetic, the load/store and branch offsets, the atomics, the
|
||||||
|
// FP conversions and the fused multiply-adds), the RVV configuration and
|
||||||
|
// arithmetic classes with their mask forms, the RVC-compressible shapes, the
|
||||||
|
// CSR instructions and the Zbb/Zba/Zbs bit-manipulation set, minimalised.
|
||||||
|
// The inline seeds below pin the shared file-level surface and the rejection
|
||||||
|
// shapes, one diagnostic path each.
|
||||||
|
func FuzzAssembleRISCV64(f *testing.F) {
|
||||||
|
corpusSeeds(f)
|
||||||
|
archCorpusSeeds(f, "riscv64")
|
||||||
|
// Minimal seeds for the shared file-level surface, spelled the riscv64
|
||||||
|
// way: the guard classes, the macro and include expansion, and the data
|
||||||
|
// section with its symbol-valued fields.
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $16-8\n\tMOV x+0(FP), X10\n\tMOV X10, ret+8(FP)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $256-0\n\tCALL ·helper(SB)\n\tRET\nTEXT ·helper(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#define L(n) ADDI $n, X10, X10\nTEXT ·f(SB), NOSPLIT, $0\n\tL(7)\n\tRET\n")
|
||||||
|
f.Add("#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#include \"fuzzdefs.h\"\nTEXT ·f(SB), $16-8\n\tMOV KONST, X10\n\tMOV ARG(x), X11\n\tRET\n")
|
||||||
|
f.Add("DATA d<>+0(SB)/8, $0xf4f8fcff\nDATA d<>+4(SB)/4, $1\nGLOBL d<>(SB), RODATA, $8\n" +
|
||||||
|
"TEXT ·f(SB), NOSPLIT, $0\n\tMOV d<>(SB), X10\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tPCALIGN $16\n\tADD X11, X10, X10\n\tPCALIGN $2048\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tPCDATA $0, $1\n\tFUNCDATA $0, ·meta(SB)\n\tRET\n")
|
||||||
|
// Shapes that must be rejected: each pins a diagnostic path the accepted
|
||||||
|
// seeds never reach.
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tBOGUSINSTR X10, X11\n\tRET\n")
|
||||||
|
f.Add("GLOBL d(SB), $-8\n")
|
||||||
|
f.Add("GLOBL d(SB), $0x4000001\n")
|
||||||
|
f.Add("DATA d+0(SB)/9, $1\nGLOBL d(SB), $8\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADD X11, X32\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADDI $2048, X5, X6\n\tADD X11, X40, X6\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tCSRRW $0x1000, X5, X6\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tSLLI $64, X5, X6\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVLE8V (X10), V32\n\tRET\n")
|
||||||
|
// Operand-starved spellings that used to panic the layout and encode
|
||||||
|
// passes; each must come back as a diagnostic.
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tJALR\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tROR $3\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVLE8V (X10)\n\tRET\n")
|
||||||
|
f.Add("#define A A\nA\n")
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, src string) {
|
||||||
|
fuzzAssemble(t, "fuzz_riscv64.s", src, AssembleFileRISCV)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzAssembleLOONG64 hammers the same pipeline for the fixed loong64 target.
|
||||||
|
// The seed corpus lives in testdata/seeds/loong64: the LSX and LASX register
|
||||||
|
// banks with their immediate forms and range gates (the si5 compares, the
|
||||||
|
// biased shifts, the VSHUF4I/VPERMI/VEXTRINS immediates), the ll/sc offset
|
||||||
|
// ladder with its three encoding spans, the pointer loads and stores, the
|
||||||
|
// atomics with their dbar forms, the branches, the bit-field instructions and
|
||||||
|
// the register-class moves, minimalised. The inline seeds below pin the
|
||||||
|
// shared file-level surface and the rejection shapes, one diagnostic path
|
||||||
|
// each.
|
||||||
|
func FuzzAssembleLOONG64(f *testing.F) {
|
||||||
|
corpusSeeds(f)
|
||||||
|
archCorpusSeeds(f, "loong64")
|
||||||
|
// Minimal seeds for the shared file-level surface, spelled the loong64
|
||||||
|
// way.
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $16-8\n\tMOVV x+0(FP), R4\n\tMOVV R4, ret+8(FP)\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $256-0\n\tCALL ·helper(SB)\n\tRET\nTEXT ·helper(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#define L(n) ADDV $n, R4, R4\nTEXT ·f(SB), NOSPLIT, $0\n\tL(7)\n\tRET\n")
|
||||||
|
f.Add("#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
f.Add("#include \"fuzzdefs.h\"\nTEXT ·f(SB), $16-8\n\tMOVV KONST, R4\n\tMOVV ARG(x), R5\n\tRET\n")
|
||||||
|
f.Add("DATA d<>+0(SB)/8, $0xf4f8fcff\nDATA d<>+4(SB)/4, $1\nGLOBL d<>(SB), RODATA, $8\n" +
|
||||||
|
"TEXT ·f(SB), NOSPLIT, $0\n\tMOVV d<>(SB), R4\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tPCALIGN $16\n\tADDV R5, R4, R4\n\tPCALIGN $2048\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tPCDATA $0, $1\n\tFUNCDATA $0, ·meta(SB)\n\tRET\n")
|
||||||
|
// Shapes that must be rejected: each pins a diagnostic path the accepted
|
||||||
|
// seeds never reach.
|
||||||
|
f.Add("TEXT ·f(SB), $0\n\tBOGUSINSTR R4, R5\n\tRET\n")
|
||||||
|
f.Add("GLOBL d(SB), $-8\n")
|
||||||
|
f.Add("GLOBL d(SB), $0x4000001\n")
|
||||||
|
f.Add("DATA d+0(SB)/9, $1\nGLOBL d(SB), $8\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tADD R5, R32\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tBEQZ V0, L1\nL1:\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVADDV V1, V2, X3\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVSEQV $32, V2, V3\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tVSHUF4IV $16, V2, V1\n\tRET\n")
|
||||||
|
f.Add("TEXT ·f(SB), NOSPLIT, $0\n\tBSTRPICKV $64, R4, $5, R6\n\tRET\n")
|
||||||
|
f.Add("#define A A\nA\n")
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, src string) {
|
||||||
|
fuzzAssemble(t, "fuzz_loong64.s", src, AssembleFileLOONG64)
|
||||||
|
})
|
||||||
|
}
|
||||||
+65
-10
@@ -43,6 +43,9 @@ const (
|
|||||||
stInfoShift = 4
|
stInfoShift = 4
|
||||||
|
|
||||||
rX8664PC32 = 2
|
rX8664PC32 = 2
|
||||||
|
// R_X86_64_32 (debug/elf): the absolute 32-bit address of a symbol, the
|
||||||
|
// R_ADDR shape a 4-byte DATA field carries.
|
||||||
|
rX8664Abs32 = 10
|
||||||
// R_X86_64_TPOFF32 (debug/elf): the local-exec TLS offset the stack
|
// R_X86_64_TPOFF32 (debug/elf): the local-exec TLS offset the stack
|
||||||
// guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation.
|
// guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation.
|
||||||
rX8664TPOFF32 = 23
|
rX8664TPOFF32 = 23
|
||||||
@@ -158,6 +161,50 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8,
|
||||||
|
// $other(SB)") become .rela.data entries: an absolute relocation of the
|
||||||
|
// DATA line's width at the field's data-section offset, S + A with no
|
||||||
|
// PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields
|
||||||
|
// cannot hold an address, so they are refused rather than truncated.
|
||||||
|
var dataRelas []elfRela
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
idx, ok := symIdx[r.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
var typ uint32
|
||||||
|
switch r.Siz {
|
||||||
|
case 8:
|
||||||
|
typ = rX8664Abs64
|
||||||
|
case 4:
|
||||||
|
typ = rX8664Abs32
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz)
|
||||||
|
}
|
||||||
|
dataRelas = append(dataRelas, elfRela{
|
||||||
|
off: uint64(d.Offset + r.Off),
|
||||||
|
sym: idx,
|
||||||
|
typ: typ,
|
||||||
|
addend: r.Addend,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section presence: .rela.text only when there are code relocations,
|
||||||
|
// .rela.data only when a DATA line holds a symbol value.
|
||||||
|
hasRela := len(relas) > 0
|
||||||
|
hasDataRela := len(dataRelas) > 0
|
||||||
|
nSections := 6 // NULL, .text, .data, .symtab, .strtab, .shstrtab
|
||||||
|
if hasRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
secSymtab, secStrtab := 3, 4
|
||||||
|
secShstr := nSections - 1
|
||||||
|
|
||||||
// Serialise the string tables.
|
// Serialise the string tables.
|
||||||
stNames := newElfStrtab()
|
stNames := newElfStrtab()
|
||||||
for _, s := range syms {
|
for _, s := range syms {
|
||||||
@@ -167,19 +214,13 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
stSections.add(".rela.data")
|
||||||
|
}
|
||||||
for _, n := range dwarfSectionNames {
|
for _, n := range dwarfSectionNames {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section presence: .rela.text only when there are relocations.
|
|
||||||
hasRela := len(relas) > 0
|
|
||||||
nSections := 6 // NULL, .text, .data, .symtab, .strtab, .shstrtab
|
|
||||||
if hasRela {
|
|
||||||
nSections = 7
|
|
||||||
}
|
|
||||||
secSymtab, secStrtab := 3, 4
|
|
||||||
secShstr := nSections - 1
|
|
||||||
|
|
||||||
// Lay the file out: header, section data, section headers.
|
// Lay the file out: header, section data, section headers.
|
||||||
var out []byte
|
var out []byte
|
||||||
out = append(out, make([]byte, 64)...) // ELF header, filled last
|
out = append(out, make([]byte, 64)...) // ELF header, filled last
|
||||||
@@ -214,7 +255,7 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
strtabOff := len(out)
|
strtabOff := len(out)
|
||||||
out = append(out, stNames.bytes()...)
|
out = append(out, stNames.bytes()...)
|
||||||
|
|
||||||
var relaOff int
|
var relaOff, relaDataOff int
|
||||||
if hasRela {
|
if hasRela {
|
||||||
align(8)
|
align(8)
|
||||||
relaOff = len(out)
|
relaOff = len(out)
|
||||||
@@ -226,6 +267,17 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
out = append(out, b[:]...)
|
out = append(out, b[:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
align(8)
|
||||||
|
relaDataOff = len(out)
|
||||||
|
for _, r := range dataRelas {
|
||||||
|
var b [24]byte
|
||||||
|
le.PutUint64(b[0:], r.off)
|
||||||
|
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
|
||||||
|
le.PutUint64(b[16:], uint64(r.addend))
|
||||||
|
out = append(out, b[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
@@ -284,6 +336,9 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
if hasRela {
|
if hasRela {
|
||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
|
||||||
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
|
|
||||||
// DWARF section headers; their indices follow the write order.
|
// DWARF section headers; their indices follow the write order.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
||||||
|
|||||||
+110
-2
@@ -12,8 +12,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The object-file tests share one source: two exported functions, one
|
// The object-file tests share one source: two exported functions, one
|
||||||
@@ -750,3 +750,111 @@ func readFormSkip(t *testing.T, r *ulebIter, form uint64) {
|
|||||||
t.Fatalf("unsupported form %#x", form)
|
t.Fatalf("unsupported form %#x", form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestELFObjectDataRelocation checks that a symbol-valued DATA field ("DATA
|
||||||
|
// s+0(SB)/8, $other(SB)") reaches the ELF object as a .rela.data entry: an
|
||||||
|
// absolute 64-bit relocation at the field's offset within .data, against
|
||||||
|
// the named symbol, external targets included.
|
||||||
|
func TestELFObjectDataRelocation(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("t_amd64.s", `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $24
|
||||||
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
||||||
|
DATA holder+8(SB)/8, $holder(SB)
|
||||||
|
DATA holder+16(SB)/8, $extvar(SB)
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFObject()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFObject: %v", err)
|
||||||
|
}
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
relaData := ef.Section(".rela.data")
|
||||||
|
if relaData == nil {
|
||||||
|
t.Fatal("missing .rela.data section")
|
||||||
|
}
|
||||||
|
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
|
||||||
|
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
|
||||||
|
}
|
||||||
|
if ef.Sections[relaData.Info].Name != ".data" {
|
||||||
|
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
|
||||||
|
}
|
||||||
|
relas, err := relaData.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var got []struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}
|
||||||
|
for i := 0; i+24 <= len(relas); i += 24 {
|
||||||
|
got = append(got, struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}{
|
||||||
|
off: binary.LittleEndian.Uint64(relas[i:]),
|
||||||
|
// r_info packs the type in the low dword and the symbol index
|
||||||
|
// in the high dword.
|
||||||
|
typ: binary.LittleEndian.Uint32(relas[i+8:]),
|
||||||
|
sym: binary.LittleEndian.Uint32(relas[i+12:]),
|
||||||
|
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := func(idx uint32) string {
|
||||||
|
if idx >= 1 && int(idx) <= len(syms) {
|
||||||
|
return syms[idx-1].Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// The offsets are data-section-relative: the field's DATA offset plus
|
||||||
|
// the symbol's position in .data (the layout aligns each symbol to 16).
|
||||||
|
base := uint64(0)
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
if d.Name == "holder" {
|
||||||
|
base = uint64(d.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := []struct {
|
||||||
|
off uint64
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{off: base + 0, typ: uint32(elf.R_X86_64_64), addend: 5, target: "Keep"},
|
||||||
|
{off: base + 8, typ: uint32(elf.R_X86_64_64), addend: 0, target: "holder"},
|
||||||
|
{off: base + 16, typ: uint32(elf.R_X86_64_64), addend: 0, target: "extvar"},
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
|
||||||
|
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
|
||||||
|
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
|
||||||
|
}
|
||||||
|
if n := name(g.sym); n != w.target {
|
||||||
|
t.Errorf("entry %d names %q, want %q", i, n, w.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
-11
@@ -18,12 +18,19 @@ const (
|
|||||||
rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset)
|
rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset)
|
||||||
rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction)
|
rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction)
|
||||||
rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset)
|
rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset)
|
||||||
|
// R_AARCH64_TLSLE_MOVW_TPREL_G0 (debug/elf 547): the local-exec TLS
|
||||||
|
// load's MOVZ field, the module offset at bits [15:0].
|
||||||
|
rArm64TLSLEMovwTprelG0 = 547
|
||||||
|
// R_AARCH64_ABS32 (debug/elf 258): the absolute 32-bit address of a
|
||||||
|
// symbol, the R_ADDR shape a 4-byte DATA field carries. ABS64 (257)
|
||||||
|
// lives with the DWARF fixup constants as rAARCH64Abs64.
|
||||||
|
rArm64Abs32 = 258
|
||||||
)
|
)
|
||||||
|
|
||||||
// ELFAARCH64Object returns the image as an ELF64 relocatable object file for
|
// ELFAARCH64Object returns the image as an ELF64 relocatable object file for
|
||||||
// AArch64 (EM_AARCH64, 64-bit, little-endian). The structure mirrors the
|
// AArch64 (EM_AARCH64, 64-bit, little-endian). The structure mirrors the
|
||||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an
|
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an
|
||||||
// optional .rela.text.
|
// optional .rela.text and an optional .rela.data.
|
||||||
func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
||||||
le := binary.LittleEndian
|
le := binary.LittleEndian
|
||||||
|
|
||||||
@@ -127,12 +134,61 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
||||||
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend},
|
||||||
)
|
)
|
||||||
|
case RelArm64TLSLE:
|
||||||
|
// The local-exec MOVZ: one relocation over the imm16 field.
|
||||||
|
relas = append(relas, elfRela{
|
||||||
|
off: uint64(fn.Offset + r.Off), typ: rArm64TLSLEMovwTprelG0, sym: idx, addend: r.Addend,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8,
|
||||||
|
// $other(SB)") become .rela.data entries: an absolute relocation of the
|
||||||
|
// DATA line's width at the field's data-section offset, S + A with no
|
||||||
|
// PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields
|
||||||
|
// cannot hold an address, so they are refused rather than truncated.
|
||||||
|
var dataRelas []elfRela
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
idx, ok := symIdx[r.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
var typ uint32
|
||||||
|
switch r.Siz {
|
||||||
|
case 8:
|
||||||
|
typ = rAARCH64Abs64
|
||||||
|
case 4:
|
||||||
|
typ = rArm64Abs32
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz)
|
||||||
|
}
|
||||||
|
dataRelas = append(dataRelas, elfRela{
|
||||||
|
off: uint64(d.Offset + r.Off),
|
||||||
|
sym: idx,
|
||||||
|
typ: typ,
|
||||||
|
addend: r.Addend,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section presence: .rela.text only when there are code relocations,
|
||||||
|
// .rela.data only when a DATA line holds a symbol value.
|
||||||
|
hasRela := len(relas) > 0
|
||||||
|
hasDataRela := len(dataRelas) > 0
|
||||||
|
nSections := 6
|
||||||
|
if hasRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
secSymtab, secStrtab := 3, 4
|
||||||
|
secShstr := nSections - 1
|
||||||
|
|
||||||
// String tables.
|
// String tables.
|
||||||
stNames := newElfStrtab()
|
stNames := newElfStrtab()
|
||||||
for _, s := range syms {
|
for _, s := range syms {
|
||||||
@@ -142,18 +198,13 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
stSections.add(".rela.data")
|
||||||
|
}
|
||||||
for _, n := range dwarfSectionNames {
|
for _, n := range dwarfSectionNames {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRela := len(relas) > 0
|
|
||||||
nSections := 6
|
|
||||||
if hasRela {
|
|
||||||
nSections = 7
|
|
||||||
}
|
|
||||||
secSymtab, secStrtab := 3, 4
|
|
||||||
secShstr := nSections - 1
|
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
var out []byte
|
var out []byte
|
||||||
out = append(out, make([]byte, 64)...)
|
out = append(out, make([]byte, 64)...)
|
||||||
@@ -188,7 +239,7 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
strtabOff := len(out)
|
strtabOff := len(out)
|
||||||
out = append(out, stNames.bytes()...)
|
out = append(out, stNames.bytes()...)
|
||||||
|
|
||||||
var relaOff int
|
var relaOff, relaDataOff int
|
||||||
if hasRela {
|
if hasRela {
|
||||||
align(8)
|
align(8)
|
||||||
relaOff = len(out)
|
relaOff = len(out)
|
||||||
@@ -200,6 +251,17 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
out = append(out, b[:]...)
|
out = append(out, b[:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
align(8)
|
||||||
|
relaDataOff = len(out)
|
||||||
|
for _, r := range dataRelas {
|
||||||
|
var b [24]byte
|
||||||
|
le.PutUint64(b[0:], r.off)
|
||||||
|
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
|
||||||
|
le.PutUint64(b[16:], uint64(r.addend))
|
||||||
|
out = append(out, b[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
@@ -257,6 +319,9 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
if hasRela {
|
if hasRela {
|
||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
|
||||||
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
// DWARF section headers; their indices follow the write order.
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
|||||||
+116
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
||||||
@@ -197,3 +197,118 @@ TEXT ·add(SB), NOSPLIT, $0-24
|
|||||||
t.Error("unexpected .rela.text section when there are no relocations")
|
t.Error("unexpected .rela.text section when there are no relocations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestELFAARCH64ObjectDataRelocation checks that a symbol-valued DATA field
|
||||||
|
// ("DATA s+0(SB)/8, $other(SB)") reaches the AArch64 ELF object as a
|
||||||
|
// .rela.data entry: an R_AARCH64_ABS64 (ABS32 for a width-4 field) at the
|
||||||
|
// field's offset within .data, against the named symbol, external targets
|
||||||
|
// included.
|
||||||
|
func TestELFAARCH64ObjectDataRelocation(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("t_arm64.s", `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-0
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $32
|
||||||
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
||||||
|
DATA holder+8(SB)/8, $holder(SB)
|
||||||
|
DATA holder+16(SB)/8, $extvar(SB)
|
||||||
|
DATA holder+24(SB)/4, $Keep(SB)
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFAARCH64Object()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFAARCH64Object: %v", err)
|
||||||
|
}
|
||||||
|
checkELFSectionAccounting(t, obj)
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
relaData := ef.Section(".rela.data")
|
||||||
|
if relaData == nil {
|
||||||
|
t.Fatal("missing .rela.data section")
|
||||||
|
}
|
||||||
|
if relaData.Type != elf.SHT_RELA {
|
||||||
|
t.Errorf(".rela.data type = %v, want SHT_RELA", relaData.Type)
|
||||||
|
}
|
||||||
|
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
|
||||||
|
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
|
||||||
|
}
|
||||||
|
if ef.Sections[relaData.Info].Name != ".data" {
|
||||||
|
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
|
||||||
|
}
|
||||||
|
relas, err := relaData.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var got []struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}
|
||||||
|
for i := 0; i+24 <= len(relas); i += 24 {
|
||||||
|
got = append(got, struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}{
|
||||||
|
off: binary.LittleEndian.Uint64(relas[i:]),
|
||||||
|
// r_info packs the type in the low dword and the symbol index
|
||||||
|
// in the high dword.
|
||||||
|
typ: binary.LittleEndian.Uint32(relas[i+8:]),
|
||||||
|
sym: binary.LittleEndian.Uint32(relas[i+12:]),
|
||||||
|
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := func(idx uint32) string {
|
||||||
|
if idx >= 1 && int(idx) <= len(syms) {
|
||||||
|
return syms[idx-1].Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// The offsets are data-section-relative: the field's DATA offset plus
|
||||||
|
// the symbol's position in .data (the layout aligns each symbol to 16).
|
||||||
|
base := uint64(0)
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
if d.Name == "holder" {
|
||||||
|
base = uint64(d.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := []struct {
|
||||||
|
off uint64
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{off: base + 0, typ: uint32(elf.R_AARCH64_ABS64), addend: 5, target: "Keep"},
|
||||||
|
{off: base + 8, typ: uint32(elf.R_AARCH64_ABS64), addend: 0, target: "holder"},
|
||||||
|
{off: base + 16, typ: uint32(elf.R_AARCH64_ABS64), addend: 0, target: "extvar"},
|
||||||
|
{off: base + 24, typ: uint32(elf.R_AARCH64_ABS32), addend: 0, target: "Keep"},
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
|
||||||
|
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
|
||||||
|
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
|
||||||
|
}
|
||||||
|
if n := name(g.sym); n != w.target {
|
||||||
|
t.Errorf("entry %d names %q, want %q", i, n, w.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
-11
@@ -23,12 +23,16 @@ const (
|
|||||||
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
||||||
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
||||||
rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping)
|
rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping)
|
||||||
|
// R_LARCH_32 (debug/elf 1): the absolute 32-bit address of a symbol,
|
||||||
|
// the R_ADDR shape a 4-byte DATA field carries. R_LARCH_64 (2) lives
|
||||||
|
// with the DWARF fixup constants as rLarchAbs64.
|
||||||
|
rLarchAbs32 = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// ELFLOONG64Object returns the image as an ELF64 relocatable object file for
|
// ELFLOONG64Object returns the image as an ELF64 relocatable object file for
|
||||||
// LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the
|
// LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the
|
||||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an
|
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an
|
||||||
// optional .rela.text.
|
// optional .rela.text and an optional .rela.data.
|
||||||
func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||||
le := binary.LittleEndian
|
le := binary.LittleEndian
|
||||||
|
|
||||||
@@ -117,6 +121,50 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8,
|
||||||
|
// $other(SB)") become .rela.data entries: an absolute relocation of the
|
||||||
|
// DATA line's width at the field's data-section offset, S + A with no
|
||||||
|
// PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields
|
||||||
|
// cannot hold an address, so they are refused rather than truncated.
|
||||||
|
var dataRelas []elfRela
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
idx, ok := symIdx[r.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
var typ uint32
|
||||||
|
switch r.Siz {
|
||||||
|
case 8:
|
||||||
|
typ = rLarchAbs64
|
||||||
|
case 4:
|
||||||
|
typ = rLarchAbs32
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz)
|
||||||
|
}
|
||||||
|
dataRelas = append(dataRelas, elfRela{
|
||||||
|
off: uint64(d.Offset + r.Off),
|
||||||
|
sym: idx,
|
||||||
|
typ: typ,
|
||||||
|
addend: r.Addend,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section presence: .rela.text only when there are code relocations,
|
||||||
|
// .rela.data only when a DATA line holds a symbol value.
|
||||||
|
hasRela := len(relas) > 0
|
||||||
|
hasDataRela := len(dataRelas) > 0
|
||||||
|
nSections := 6
|
||||||
|
if hasRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
secSymtab, secStrtab := 3, 4
|
||||||
|
secShstr := nSections - 1
|
||||||
|
|
||||||
// String tables.
|
// String tables.
|
||||||
stNames := newElfStrtab()
|
stNames := newElfStrtab()
|
||||||
for _, s := range syms {
|
for _, s := range syms {
|
||||||
@@ -126,18 +174,13 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
stSections.add(".rela.data")
|
||||||
|
}
|
||||||
for _, n := range dwarfSectionNames {
|
for _, n := range dwarfSectionNames {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRela := len(relas) > 0
|
|
||||||
nSections := 6
|
|
||||||
if hasRela {
|
|
||||||
nSections = 7
|
|
||||||
}
|
|
||||||
secSymtab, secStrtab := 3, 4
|
|
||||||
secShstr := nSections - 1
|
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
var out []byte
|
var out []byte
|
||||||
out = append(out, make([]byte, 64)...)
|
out = append(out, make([]byte, 64)...)
|
||||||
@@ -172,7 +215,7 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
strtabOff := len(out)
|
strtabOff := len(out)
|
||||||
out = append(out, stNames.bytes()...)
|
out = append(out, stNames.bytes()...)
|
||||||
|
|
||||||
var relaOff int
|
var relaOff, relaDataOff int
|
||||||
if hasRela {
|
if hasRela {
|
||||||
align(8)
|
align(8)
|
||||||
relaOff = len(out)
|
relaOff = len(out)
|
||||||
@@ -184,6 +227,17 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
out = append(out, b[:]...)
|
out = append(out, b[:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
align(8)
|
||||||
|
relaDataOff = len(out)
|
||||||
|
for _, r := range dataRelas {
|
||||||
|
var b [24]byte
|
||||||
|
le.PutUint64(b[0:], r.off)
|
||||||
|
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
|
||||||
|
le.PutUint64(b[16:], uint64(r.addend))
|
||||||
|
out = append(out, b[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
@@ -239,6 +293,9 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
if hasRela {
|
if hasRela {
|
||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
|
||||||
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
// DWARF section headers; their indices follow the write order.
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
|||||||
+116
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestELFLOONG64Object checks the structure of the emitted LoongArch ELF64
|
// TestELFLOONG64Object checks the structure of the emitted LoongArch ELF64
|
||||||
@@ -245,3 +245,118 @@ func TestELFLOONG64BranchRelocation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestELFLOONG64ObjectDataRelocation checks that a symbol-valued DATA field
|
||||||
|
// ("DATA s+0(SB)/8, $other(SB)") reaches the LoongArch ELF object as a
|
||||||
|
// .rela.data entry: an R_LARCH_64 (R_LARCH_32 for a width-4 field) at the
|
||||||
|
// field's offset within .data, against the named symbol, external targets
|
||||||
|
// included.
|
||||||
|
func TestELFLOONG64ObjectDataRelocation(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("t_loong64.s", `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-0
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $32
|
||||||
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
||||||
|
DATA holder+8(SB)/8, $holder(SB)
|
||||||
|
DATA holder+16(SB)/8, $extvar(SB)
|
||||||
|
DATA holder+24(SB)/4, $Keep(SB)
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileLOONG64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileLOONG64: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFLOONG64Object()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFLOONG64Object: %v", err)
|
||||||
|
}
|
||||||
|
checkELFSectionAccounting(t, obj)
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
relaData := ef.Section(".rela.data")
|
||||||
|
if relaData == nil {
|
||||||
|
t.Fatal("missing .rela.data section")
|
||||||
|
}
|
||||||
|
if relaData.Type != elf.SHT_RELA {
|
||||||
|
t.Errorf(".rela.data type = %v, want SHT_RELA", relaData.Type)
|
||||||
|
}
|
||||||
|
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
|
||||||
|
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
|
||||||
|
}
|
||||||
|
if ef.Sections[relaData.Info].Name != ".data" {
|
||||||
|
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
|
||||||
|
}
|
||||||
|
relas, err := relaData.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var got []struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}
|
||||||
|
for i := 0; i+24 <= len(relas); i += 24 {
|
||||||
|
got = append(got, struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}{
|
||||||
|
off: binary.LittleEndian.Uint64(relas[i:]),
|
||||||
|
// r_info packs the type in the low dword and the symbol index
|
||||||
|
// in the high dword.
|
||||||
|
typ: binary.LittleEndian.Uint32(relas[i+8:]),
|
||||||
|
sym: binary.LittleEndian.Uint32(relas[i+12:]),
|
||||||
|
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := func(idx uint32) string {
|
||||||
|
if idx >= 1 && int(idx) <= len(syms) {
|
||||||
|
return syms[idx-1].Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// The offsets are data-section-relative: the field's DATA offset plus
|
||||||
|
// the symbol's position in .data (the layout aligns each symbol to 16).
|
||||||
|
base := uint64(0)
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
if d.Name == "holder" {
|
||||||
|
base = uint64(d.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := []struct {
|
||||||
|
off uint64
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{off: base + 0, typ: uint32(elf.R_LARCH_64), addend: 5, target: "Keep"},
|
||||||
|
{off: base + 8, typ: uint32(elf.R_LARCH_64), addend: 0, target: "holder"},
|
||||||
|
{off: base + 16, typ: uint32(elf.R_LARCH_64), addend: 0, target: "extvar"},
|
||||||
|
{off: base + 24, typ: uint32(elf.R_LARCH_32), addend: 0, target: "Keep"},
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
|
||||||
|
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
|
||||||
|
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
|
||||||
|
}
|
||||||
|
if n := name(g.sym); n != w.target {
|
||||||
|
t.Errorf("entry %d names %q, want %q", i, n, w.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
-10
@@ -24,11 +24,18 @@ const (
|
|||||||
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
||||||
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
||||||
rRISCVPCRELLO12S = 25 // R_RISCV_PCREL_LO12_S
|
rRISCVPCRELLO12S = 25 // R_RISCV_PCREL_LO12_S
|
||||||
|
rRISCVTPRELHI20 = 29 // R_RISCV_TPREL_HI20
|
||||||
|
rRISCVTPRELLO12I = 30 // R_RISCV_TPREL_LO12_I
|
||||||
|
// R_RISCV_32 (debug/elf 1): the absolute 32-bit address of a symbol,
|
||||||
|
// the R_ADDR shape a 4-byte DATA field carries. R_RISCV_64 (2) lives
|
||||||
|
// with the DWARF fixup constants as rRISCVAbs64.
|
||||||
|
rRISVCAbs32 = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// ELFRISCVObject returns the image as an ELF64 relocatable object file for
|
// ELFRISCVObject returns the image as an ELF64 relocatable object file for
|
||||||
// RISC-V (EM_RISCV, 64-bit, little-endian). The structure mirrors the amd64
|
// RISC-V (EM_RISCV, 64-bit, little-endian). The structure mirrors the amd64
|
||||||
// ELF emission: .text, .data, .symtab, .strtab and optional .rela.text.
|
// ELF emission: .text, .data, .symtab, .strtab, an optional .rela.text and
|
||||||
|
// an optional .rela.data.
|
||||||
func (img *Image) ELFRISCVObject() ([]byte, error) {
|
func (img *Image) ELFRISCVObject() ([]byte, error) {
|
||||||
le := binary.LittleEndian
|
le := binary.LittleEndian
|
||||||
|
|
||||||
@@ -123,12 +130,65 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
)
|
)
|
||||||
case RelRISCVJal:
|
case RelRISCVJal:
|
||||||
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
||||||
|
case RelRISCVTLSLE:
|
||||||
|
// The local-exec pair splits into the TPREL HI20 on the LUI
|
||||||
|
// and the TPREL LO12_I on the ADDIW, both against the symbol
|
||||||
|
// (a thread offset, not PC-relative, so the LO12 needs no
|
||||||
|
// label indirection).
|
||||||
|
relas = append(relas,
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVTPRELHI20, sym: idx, addend: r.Addend},
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVTPRELLO12I, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8,
|
||||||
|
// $other(SB)") become .rela.data entries: an absolute relocation of the
|
||||||
|
// DATA line's width at the field's data-section offset, S + A with no
|
||||||
|
// PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields
|
||||||
|
// cannot hold an address, so they are refused rather than truncated.
|
||||||
|
var dataRelas []elfRela
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
idx, ok := symIdx[r.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
var typ uint32
|
||||||
|
switch r.Siz {
|
||||||
|
case 8:
|
||||||
|
typ = rRISCVAbs64
|
||||||
|
case 4:
|
||||||
|
typ = rRISVCAbs32
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz)
|
||||||
|
}
|
||||||
|
dataRelas = append(dataRelas, elfRela{
|
||||||
|
off: uint64(d.Offset + r.Off),
|
||||||
|
sym: idx,
|
||||||
|
typ: typ,
|
||||||
|
addend: r.Addend,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section presence: .rela.text only when there are code relocations,
|
||||||
|
// .rela.data only when a DATA line holds a symbol value.
|
||||||
|
hasRela := len(relas) > 0
|
||||||
|
hasDataRela := len(dataRelas) > 0
|
||||||
|
nSections := 6
|
||||||
|
if hasRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
secSymtab, secStrtab := 3, 4
|
||||||
|
secShstr := nSections - 1
|
||||||
|
|
||||||
// String tables.
|
// String tables.
|
||||||
stNames := newElfStrtab()
|
stNames := newElfStrtab()
|
||||||
for _, s := range syms {
|
for _, s := range syms {
|
||||||
@@ -138,18 +198,13 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
stSections.add(".rela.data")
|
||||||
|
}
|
||||||
for _, n := range dwarfSectionNames {
|
for _, n := range dwarfSectionNames {
|
||||||
stSections.add(n)
|
stSections.add(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRela := len(relas) > 0
|
|
||||||
nSections := 6
|
|
||||||
if hasRela {
|
|
||||||
nSections = 7
|
|
||||||
}
|
|
||||||
secSymtab, secStrtab := 3, 4
|
|
||||||
secShstr := nSections - 1
|
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
var out []byte
|
var out []byte
|
||||||
out = append(out, make([]byte, 64)...)
|
out = append(out, make([]byte, 64)...)
|
||||||
@@ -184,7 +239,7 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
strtabOff := len(out)
|
strtabOff := len(out)
|
||||||
out = append(out, stNames.bytes()...)
|
out = append(out, stNames.bytes()...)
|
||||||
|
|
||||||
var relaOff int
|
var relaOff, relaDataOff int
|
||||||
if hasRela {
|
if hasRela {
|
||||||
align(8)
|
align(8)
|
||||||
relaOff = len(out)
|
relaOff = len(out)
|
||||||
@@ -196,6 +251,17 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
out = append(out, b[:]...)
|
out = append(out, b[:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
align(8)
|
||||||
|
relaDataOff = len(out)
|
||||||
|
for _, r := range dataRelas {
|
||||||
|
var b [24]byte
|
||||||
|
le.PutUint64(b[0:], r.off)
|
||||||
|
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
|
||||||
|
le.PutUint64(b[16:], uint64(r.addend))
|
||||||
|
out = append(out, b[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
@@ -251,6 +317,9 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
if hasRela {
|
if hasRela {
|
||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
|
if hasDataRela {
|
||||||
|
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
|
||||||
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
// DWARF section headers; their indices follow the write order.
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestELFRISCVObjectDataRelocation checks that a symbol-valued DATA field
|
||||||
|
// ("DATA s+0(SB)/8, $other(SB)") reaches the RISC-V ELF object as a
|
||||||
|
// .rela.data entry: an R_RISCV_64 (R_RISCV_32 for a width-4 field) at the
|
||||||
|
// field's offset within .data, against the named symbol, external targets
|
||||||
|
// included.
|
||||||
|
func TestELFRISCVObjectDataRelocation(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("t_riscv64.s", `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-0
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $32
|
||||||
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
||||||
|
DATA holder+8(SB)/8, $holder(SB)
|
||||||
|
DATA holder+16(SB)/8, $extvar(SB)
|
||||||
|
DATA holder+24(SB)/4, $Keep(SB)
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileRISCV(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFRISCVObject()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFRISCVObject: %v", err)
|
||||||
|
}
|
||||||
|
checkELFSectionAccounting(t, obj)
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
relaData := ef.Section(".rela.data")
|
||||||
|
if relaData == nil {
|
||||||
|
t.Fatal("missing .rela.data section")
|
||||||
|
}
|
||||||
|
if relaData.Type != elf.SHT_RELA {
|
||||||
|
t.Errorf(".rela.data type = %v, want SHT_RELA", relaData.Type)
|
||||||
|
}
|
||||||
|
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
|
||||||
|
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
|
||||||
|
}
|
||||||
|
if ef.Sections[relaData.Info].Name != ".data" {
|
||||||
|
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
|
||||||
|
}
|
||||||
|
relas, err := relaData.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var got []struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}
|
||||||
|
for i := 0; i+24 <= len(relas); i += 24 {
|
||||||
|
got = append(got, struct {
|
||||||
|
off uint64
|
||||||
|
sym uint32
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
}{
|
||||||
|
off: binary.LittleEndian.Uint64(relas[i:]),
|
||||||
|
// r_info packs the type in the low dword and the symbol index
|
||||||
|
// in the high dword.
|
||||||
|
typ: binary.LittleEndian.Uint32(relas[i+8:]),
|
||||||
|
sym: binary.LittleEndian.Uint32(relas[i+12:]),
|
||||||
|
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := func(idx uint32) string {
|
||||||
|
if idx >= 1 && int(idx) <= len(syms) {
|
||||||
|
return syms[idx-1].Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// The offsets are data-section-relative: the field's DATA offset plus
|
||||||
|
// the symbol's position in .data (the layout aligns each symbol to 16).
|
||||||
|
base := uint64(0)
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
if d.Name == "holder" {
|
||||||
|
base = uint64(d.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := []struct {
|
||||||
|
off uint64
|
||||||
|
typ uint32
|
||||||
|
addend int64
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{off: base + 0, typ: uint32(elf.R_RISCV_64), addend: 5, target: "Keep"},
|
||||||
|
{off: base + 8, typ: uint32(elf.R_RISCV_64), addend: 0, target: "holder"},
|
||||||
|
{off: base + 16, typ: uint32(elf.R_RISCV_64), addend: 0, target: "extvar"},
|
||||||
|
{off: base + 24, typ: uint32(elf.R_RISCV_32), addend: 0, target: "Keep"},
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
|
||||||
|
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
|
||||||
|
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
|
||||||
|
}
|
||||||
|
if n := name(g.sym); n != w.target {
|
||||||
|
t.Errorf("entry %d names %q, want %q", i, n, w.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
-10
@@ -16,10 +16,33 @@ import "strings"
|
|||||||
func Encodable(mnemonic string) bool {
|
func Encodable(mnemonic string) bool {
|
||||||
upper := strings.ToUpper(mnemonic)
|
upper := strings.ToUpper(mnemonic)
|
||||||
|
|
||||||
|
// The corpus families first, mirroring encode()'s dispatch order: a
|
||||||
|
// family owning the name decides encodability whatever the suffix split
|
||||||
|
// would make of it.
|
||||||
|
if amd64FamilyEncodable(upper) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Fixed-name instructions (no size suffix).
|
// Fixed-name instructions (no size suffix).
|
||||||
switch upper {
|
switch upper {
|
||||||
case "RET", "NOP", "CALL", "JMP",
|
case "RET", "NOP", "CALL", "JMP",
|
||||||
"POPFQ", "PUSHFQ", "INT", "LDMXCSR", "STMXCSR", "CMPSD", "SHA256RNDS2":
|
"POPFQ", "PUSHFQ", "INT", "LDMXCSR", "STMXCSR", "CMPSD", "SHA256RNDS2",
|
||||||
|
// The SSE compare family sharing CMPSD's predicate-last shape, the
|
||||||
|
// far return with its stack pop, the loop family, the bank-crossing
|
||||||
|
// MMX moves and the one-operand system controls.
|
||||||
|
"CMPSS", "CMPPS", "CMPPD", "RETFL",
|
||||||
|
"LOOP", "LOOPE", "LOOPNE",
|
||||||
|
"MOVDQ2Q", "MOVQ2DQ",
|
||||||
|
"ENDBR64", "CLWB", "TPAUSE", "UMONITOR", "UMWAIT", "RDPID", "CLDEMOTE",
|
||||||
|
// The literal-data pseudo-ops, the accepted-and-ignored END and
|
||||||
|
// bookkeeping statements, and the SP adjust.
|
||||||
|
"BYTE", "WORD", "LONG", "QUAD", "END", "ADJSP", "FUNCDATA", "PCDATA":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := sysUnaryTable[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := sseStoreOnly[upper]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := noOperandTable[upper]; ok {
|
if _, ok := noOperandTable[upper]; ok {
|
||||||
@@ -39,18 +62,16 @@ func Encodable(mnemonic string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// CMOV carries size then condition (CMOVLGT); SET carries the condition
|
// CMOV carries size then condition (CMOVLGT), or the renderer's
|
||||||
// alone (SETNE). The size letter is checked exactly as encodeCmov does,
|
// condition alone (CMOVLE) with the width from the operand; SET carries
|
||||||
// so a spelling like CMOVBGT is not reported encodable when Encode
|
// the condition alone (SETNE). cmovCondition checks the suffix exactly
|
||||||
// would reject it.
|
// as encodeCmov does, so a spelling like CMOVBGT is not reported
|
||||||
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 {
|
// encodable when Encode would reject it.
|
||||||
switch rest[0] {
|
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok {
|
||||||
case 'W', 'L', 'Q':
|
if _, _, ok := cmovCondition(rest); ok {
|
||||||
if _, ok := jccMap[rest[1:]]; ok {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
||||||
if _, ok := jccMap[rest]; ok {
|
if _, ok := jccMap[rest]; ok {
|
||||||
return true
|
return true
|
||||||
|
|||||||
+413
-6
@@ -5,6 +5,8 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +23,39 @@ func Encode(mnemonic string, ops ...Operand) ([]byte, error) {
|
|||||||
type enc struct {
|
type enc struct {
|
||||||
out []byte
|
out []byte
|
||||||
patches []encPatch // disp32 fields awaiting static-symbol resolution
|
patches []encPatch // disp32 fields awaiting static-symbol resolution
|
||||||
|
|
||||||
|
// FloatPool collects the pooled constants the floating-point
|
||||||
|
// immediates reference, in first-use order.
|
||||||
|
floatPool []floatPoolEntry
|
||||||
|
floatPoolSeen map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// floatPoolEntry is one pooled floating-point constant: the symbol name
|
||||||
|
// the emitted RIP-relative load refers to and its IEEE-754 bytes.
|
||||||
|
type floatPoolEntry struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFloatPool records a pooled constant, deduplicated by symbol name.
|
||||||
|
func (e *enc) addFloatPool(name string, bits uint64, width int) {
|
||||||
|
if e.floatPoolSeen == nil {
|
||||||
|
e.floatPoolSeen = map[string]bool{}
|
||||||
|
}
|
||||||
|
if e.floatPoolSeen[name] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.floatPoolSeen[name] = true
|
||||||
|
data := make([]byte, width)
|
||||||
|
for i := range width {
|
||||||
|
data[i] = byte(bits >> (8 * i))
|
||||||
|
}
|
||||||
|
e.floatPool = append(e.floatPool, floatPoolEntry{name: name, data: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
// floatPoolList returns the pooled constants in first-use order.
|
||||||
|
func (e *enc) floatPoolList() []floatPoolEntry {
|
||||||
|
return e.floatPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// encPatch marks a 4-byte displacement field in enc.out that must receive the
|
// encPatch marks a 4-byte displacement field in enc.out that must receive the
|
||||||
@@ -29,17 +64,39 @@ type encPatch struct {
|
|||||||
off int
|
off int
|
||||||
name string
|
name string
|
||||||
addend int64
|
addend int64
|
||||||
|
tls bool // a TLS slot offset: the patch is R_TLSLE with no symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *enc) encode(mnem string, ops []Operand) error {
|
func (e *enc) encode(mnem string, ops []Operand) error {
|
||||||
upper := strings.ToUpper(mnem)
|
upper := strings.ToUpper(mnem)
|
||||||
|
|
||||||
|
// The corpus families (x87, the system and string controls, XSAVE)
|
||||||
|
// dispatch on the full name from the amd64 family files before the
|
||||||
|
// fixed-name switch, the way their tables spell the mnemonics out.
|
||||||
|
if handled, err := e.encodeAmd64Family(upper, ops); handled {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Fixed-name instructions (no size suffix).
|
// Fixed-name instructions (no size suffix).
|
||||||
switch {
|
switch {
|
||||||
case upper == "RET":
|
case upper == "RET":
|
||||||
|
// RET sym(SB), the absolute return: the toolchain encodes it as a
|
||||||
|
// tail jump, E9 rel32 with a call relocation against the symbol.
|
||||||
|
if len(ops) == 1 {
|
||||||
|
if m, ok := ops[0].(sbMem); ok {
|
||||||
|
return e.emit(&instr{opcode: []byte{0xE9}, modrm: -1, sib: -1, disp: le32(0), sb: &sbRef{name: m.name, addend: m.addend}})
|
||||||
|
}
|
||||||
|
return fmt.Errorf("RET: unsupported operand")
|
||||||
|
}
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return fmt.Errorf("RET expects no operands, got %d", len(ops))
|
||||||
|
}
|
||||||
return e.encodeRet()
|
return e.encodeRet()
|
||||||
case upper == "NOP":
|
case upper == "NOP":
|
||||||
return e.emit(&instr{opcode: []byte{0x90}, modrm: -1, sib: -1})
|
// The toolchain consumes every NOP statement as a pseudo and emits
|
||||||
|
// nothing for it, operands included (a bare NOP, NOP AX and
|
||||||
|
// NOP sym(SB) all vanish from the object).
|
||||||
|
return nil
|
||||||
case upper == "CALL" || upper == "JMP":
|
case upper == "CALL" || upper == "JMP":
|
||||||
// Through a register or memory: FF /2 (CALL) or FF /4 (JMP).
|
// Through a register or memory: FF /2 (CALL) or FF /4 (JMP).
|
||||||
// Anything else is a rel32 against a label resolved by the assembler.
|
// Anything else is a rel32 against a label resolved by the assembler.
|
||||||
@@ -66,6 +123,15 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
return e.emit(&instr{opcode: op, modrm: -1, sib: -1})
|
return e.emit(&instr{opcode: op, modrm: -1, sib: -1})
|
||||||
}
|
}
|
||||||
|
// One-operand system instructions whose reg field is a fixed digit:
|
||||||
|
// the cache and wait controls under 0F AE/0F 1C and the RDPID read.
|
||||||
|
if m, ok := sysUnaryTable[upper]; ok {
|
||||||
|
return e.encodeSysUnary(upper, m, ops)
|
||||||
|
}
|
||||||
|
// The store-only SSE moves (the non-temporal store).
|
||||||
|
if m, ok := sseStoreOnly[upper]; ok {
|
||||||
|
return e.encodeSSEStoreOnly(upper, m, ops)
|
||||||
|
}
|
||||||
// POPFQ/PUSHFQ are exact names: the bare POPF/PUSHF and the L spellings
|
// POPFQ/PUSHFQ are exact names: the bare POPF/PUSHF and the L spellings
|
||||||
// are rejected by go tool asm in 64-bit mode, so they stay unsupported.
|
// are rejected by go tool asm in 64-bit mode, so they stay unsupported.
|
||||||
switch upper {
|
switch upper {
|
||||||
@@ -81,17 +147,88 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
return e.emit(&instr{opcode: []byte{0x9C}, modrm: -1, sib: -1})
|
return e.emit(&instr{opcode: []byte{0x9C}, modrm: -1, sib: -1})
|
||||||
case "INT":
|
case "INT":
|
||||||
return e.encodeInt(ops)
|
return e.encodeInt(ops)
|
||||||
|
// The LOOP family outside the assembler's label settlement: the operand
|
||||||
|
// is the already-computed rel8 (E0-E2).
|
||||||
|
case "LOOP", "LOOPE", "LOOPNE":
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok || !fits8(int64(imm)) {
|
||||||
|
return fmt.Errorf("%s: relative offset must be a signed byte", upper)
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{loopOpcode(upper)}, modrm: -1, sib: -1, imm: []byte{byte(int8(imm))}})
|
||||||
case "LDMXCSR":
|
case "LDMXCSR":
|
||||||
return e.encodeMxcsr(2, ops)
|
return e.encodeMxcsr(2, ops)
|
||||||
case "STMXCSR":
|
case "STMXCSR":
|
||||||
return e.encodeMxcsr(3, ops)
|
return e.encodeMxcsr(3, ops)
|
||||||
// CMPSD is the scalar double compare, whose predicate immediate comes
|
// CMPSD is the scalar double compare, whose predicate immediate comes
|
||||||
// LAST in Plan 9 order (src, dst, $imm).
|
// LAST in Plan 9 order (src, dst, $imm); the family shares the shape.
|
||||||
case "CMPSD":
|
case "CMPSD":
|
||||||
return e.encodeCmpsd(ops)
|
return e.encodeSSECmp("CMPSD", 0xF2, ops)
|
||||||
|
case "CMPSS":
|
||||||
|
return e.encodeSSECmp("CMPSS", 0xF3, ops)
|
||||||
|
case "CMPPS":
|
||||||
|
return e.encodeSSECmp("CMPPS", 0x00, ops)
|
||||||
|
case "CMPPD":
|
||||||
|
return e.encodeSSECmp("CMPPD", 0x66, ops)
|
||||||
|
// RETFL pops the immediate's worth of bytes after the far return
|
||||||
|
// (LRET iw: CA imm16), the toolchain's RETF spelling with a stack
|
||||||
|
// adjustment.
|
||||||
|
case "RETFL":
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("RETFL expects 1 operand, got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("RETFL expects an immediate")
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0xCA}, modrm: -1, sib: -1, imm: le16(int64(imm))})
|
||||||
|
// MOVDQ2Q/MOVQ2DQ cross the MMX and XMM banks, and each direction
|
||||||
|
// carries its own mandatory prefix: the toolchain renders F3 0F D6 as
|
||||||
|
// MOVQ2DQ (the MMX source, XMM destination) and F2 0F D6 as MOVDQ2Q
|
||||||
|
// (the XMM source, MMX destination), the register in the reg field, the
|
||||||
|
// other bank's in r/m.
|
||||||
|
case "MOVDQ2Q", "MOVQ2DQ":
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, ok1 := ops[0].(Reg)
|
||||||
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return fmt.Errorf("%s takes register operands alone", upper)
|
||||||
|
}
|
||||||
|
if upper == "MOVDQ2Q" && (!srcReg.isVec() || !dstReg.mmx) ||
|
||||||
|
upper == "MOVQ2DQ" && (!srcReg.mmx || !dstReg.isVec()) {
|
||||||
|
return fmt.Errorf("%s crosses the XMM and MMX banks in that order", upper)
|
||||||
|
}
|
||||||
|
prefix := byte(0xF2)
|
||||||
|
if upper == "MOVQ2DQ" {
|
||||||
|
prefix = 0xF3
|
||||||
|
}
|
||||||
|
i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, srcReg, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
// SHA256RNDS2 carries the round constant in a literal X0 first operand.
|
// SHA256RNDS2 carries the round constant in a literal X0 first operand.
|
||||||
case "SHA256RNDS2":
|
case "SHA256RNDS2":
|
||||||
return e.encodeSha256rnds2(ops)
|
return e.encodeSha256rnds2(ops)
|
||||||
|
// BYTE, WORD, LONG and QUAD write the immediate into the text stream
|
||||||
|
// itself: 1, 2, 4 or 8 literal bytes, little-endian. END is accepted
|
||||||
|
// and ignored. ADJSP adjusts SP by the immediate, sign-chosen between
|
||||||
|
// the SUBQ and ADDQ forms.
|
||||||
|
case "BYTE", "WORD", "LONG", "QUAD":
|
||||||
|
return e.encodeData(upper, ops)
|
||||||
|
case "END":
|
||||||
|
return e.encodeEnd(ops)
|
||||||
|
case "ADJSP":
|
||||||
|
return e.encodeAdjsp(ops)
|
||||||
|
// The runtime's bookkeeping statements carry no text bytes: go tool asm
|
||||||
|
// records FUNCDATA and PCDATA in the program list only, so the encoded
|
||||||
|
// body shows nothing, on every architecture.
|
||||||
|
case "FUNCDATA", "PCDATA":
|
||||||
|
return e.encodeFuncdata(upper, ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VEX (AVX/AVX2) and EVEX (AVX-512) instructions: the trailing
|
// VEX (AVX/AVX2) and EVEX (AVX-512) instructions: the trailing
|
||||||
@@ -103,6 +240,7 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) ||
|
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) ||
|
||||||
|
isEvexPrefGather(base) ||
|
||||||
base == "KMOVW" || base == "KMOVQ" || base == "KMOVB" || base == "KMOVD" {
|
base == "KMOVW" || base == "KMOVQ" || base == "KMOVB" || base == "KMOVD" {
|
||||||
return e.encodeVec(base, ops, sfx)
|
return e.encodeVec(base, ops, sfx)
|
||||||
}
|
}
|
||||||
@@ -119,6 +257,19 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
base, size := splitSize(upper)
|
base, size := splitSize(upper)
|
||||||
|
// The scalar families that own byte forms settle the width against the
|
||||||
|
// register operands here, before the unsuffixed 64-bit default applies,
|
||||||
|
// so a literal Q suffix stays distinguishable from no suffix at all
|
||||||
|
// (CRC32 DL, R11 is the byte form; CRC32Q DL, R11 is a conflict).
|
||||||
|
if byteFormBase[base] {
|
||||||
|
w, err := operandWidth(mnem, size, widthOperands(base, ops))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w != 0 {
|
||||||
|
size = w
|
||||||
|
}
|
||||||
|
}
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
||||||
}
|
}
|
||||||
@@ -130,11 +281,18 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
// Legacy SSE packed binaries dispatch on the full name: the packed
|
// Legacy SSE packed binaries dispatch on the full name: the packed
|
||||||
// integer mnemonics carry real width suffixes (PADDB/PCMPGTW/...),
|
// integer mnemonics carry real width suffixes (PADDB/PCMPGTW/...),
|
||||||
// which the size split must not eat.
|
// which the size split must not eat. A floating-point immediate
|
||||||
|
// rewrites into a pooled-constant read on the scalar members.
|
||||||
if m, ok := sseBinTable[upper]; ok {
|
if m, ok := sseBinTable[upper]; ok {
|
||||||
|
if f, isFloat := floatImmOperand(ops); isFloat {
|
||||||
|
return e.encodeSSEFloatBin(upper, m, f, ops)
|
||||||
|
}
|
||||||
return e.encodeSSEBin(m, ops)
|
return e.encodeSSEBin(m, ops)
|
||||||
}
|
}
|
||||||
if m, ok := sseBinTable[base]; ok {
|
if m, ok := sseBinTable[base]; ok {
|
||||||
|
if f, isFloat := floatImmOperand(ops); isFloat {
|
||||||
|
return e.encodeSSEFloatBin(upper, m, f, ops)
|
||||||
|
}
|
||||||
return e.encodeSSEBin(m, ops)
|
return e.encodeSSEBin(m, ops)
|
||||||
}
|
}
|
||||||
// The imm8-controlled legacy instructions, the lane extracts and inserts
|
// The imm8-controlled legacy instructions, the lane extracts and inserts
|
||||||
@@ -161,8 +319,12 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
case "MOV":
|
case "MOV":
|
||||||
return e.encodeMov(ops, size)
|
return e.encodeMov(ops, size)
|
||||||
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
|
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
|
||||||
// REX.W and all.
|
// REX.W and all. The alias takes no byte register either, the same
|
||||||
|
// conflict rule the Q-suffixed spelling answers to.
|
||||||
case "MOVD":
|
case "MOVD":
|
||||||
|
if _, err := operandWidth(mnem, 8, ops); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return e.encodeMov(ops, 8)
|
return e.encodeMov(ops, 8)
|
||||||
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
|
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
|
||||||
return e.encodeALU(aluOp[base], ops, size)
|
return e.encodeALU(aluOp[base], ops, size)
|
||||||
@@ -211,7 +373,12 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
return e.encodeCvtInt(base, ops, size)
|
return e.encodeCvtInt(base, ops, size)
|
||||||
case "FMOVD":
|
case "FMOVD":
|
||||||
return e.encodeFmov(ops)
|
return e.encodeFmov(ops)
|
||||||
case "MOVOU", "MOVO", "MOVOA", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD", "MOVSD", "MOVSS":
|
case "MOVSD", "MOVSS":
|
||||||
|
if f, isFloat := floatImmOperand(ops); isFloat {
|
||||||
|
return e.encodeSSEFloatMove(upper, f, ops)
|
||||||
|
}
|
||||||
|
return e.encodeSSEMove(sseMoveTable[base], ops)
|
||||||
|
case "MOVOU", "MOVO", "MOVOA", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD":
|
||||||
return e.encodeSSEMove(sseMoveTable[base], ops)
|
return e.encodeSSEMove(sseMoveTable[base], ops)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unsupported instruction %q", mnem)
|
return fmt.Errorf("unsupported instruction %q", mnem)
|
||||||
@@ -241,6 +408,214 @@ var prefetchVariant = map[string]int{
|
|||||||
"PREFETCHT2": 3,
|
"PREFETCHT2": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dataWidth is the literal byte count of each data-emission pseudo-op.
|
||||||
|
var dataWidth = map[string]int{
|
||||||
|
"BYTE": 1,
|
||||||
|
"WORD": 2,
|
||||||
|
"LONG": 4,
|
||||||
|
"QUAD": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeData emits the literal-data pseudo-ops: BYTE, WORD, LONG and QUAD
|
||||||
|
// write the immediate into the text stream as 1, 2, 4 or 8 bytes,
|
||||||
|
// little-endian, with no opcode lookup. The value is truncated to the
|
||||||
|
// width rather than range-checked, exactly as go tool asm behaves (BYTE
|
||||||
|
// $0x1FF emits FF, WORD $0x12345 emits 45 23, both without an error), and
|
||||||
|
// exactly one immediate is accepted: the toolchain rejects a list such as
|
||||||
|
// BYTE $1, $2, $3.
|
||||||
|
func (e *enc) encodeData(mnem string, ops []Operand) error {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 immediate operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s requires an integer immediate", mnem)
|
||||||
|
}
|
||||||
|
width := dataWidth[mnem]
|
||||||
|
out := make([]byte, width)
|
||||||
|
u := uint64(imm)
|
||||||
|
for i := range width {
|
||||||
|
out[i] = byte(u >> (8 * i))
|
||||||
|
}
|
||||||
|
e.out = append(e.out, out...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeFuncdata accepts-and-ignores the runtime bookkeeping statements:
|
||||||
|
// FUNCDATA $n, sym(SB) and PCDATA $n, $m. go tool asm emits no text bytes
|
||||||
|
// for either (the entries live in the object's ancillary tables, not the
|
||||||
|
// function body), and the operand shapes it takes are exactly these: an
|
||||||
|
// integer count first, then a symbol reference for FUNCDATA and an integer
|
||||||
|
// value for PCDATA. The other architectures accept-and-ignore the same
|
||||||
|
// statements; amd64 now matches.
|
||||||
|
func (e *enc) encodeFuncdata(upper string, ops []Operand) error {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
if _, ok := ops[0].(Imm); !ok {
|
||||||
|
return fmt.Errorf("%s: first operand must be an integer immediate", upper)
|
||||||
|
}
|
||||||
|
switch upper {
|
||||||
|
case "FUNCDATA":
|
||||||
|
if _, ok := ops[1].(sbMem); !ok {
|
||||||
|
return fmt.Errorf("FUNCDATA: second operand must be a symbol reference")
|
||||||
|
}
|
||||||
|
case "PCDATA":
|
||||||
|
if _, ok := ops[1].(Imm); !ok {
|
||||||
|
return fmt.Errorf("PCDATA: second operand must be an integer immediate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeEnd accepts-and-ignores END. go tool asm drops the statement
|
||||||
|
// entirely: the AEND Prog is skipped when the program list is flushed, so
|
||||||
|
// the statements after an END still belong to the same function and the
|
||||||
|
// encoded body carries no trace of it, whatever operands follow the name
|
||||||
|
// (the toolchain takes END $0 and END AX alike). Zero bytes, no effect.
|
||||||
|
func (e *enc) encodeEnd(ops []Operand) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeAdjsp emits ADJSP $imm: a positive value is SUBQ $imm, SP, a
|
||||||
|
// negative one ADDQ $-imm, SP, in the imm8 or imm32 form the magnitude
|
||||||
|
// picks (the same selection subSP and addSP make for the frame). go tool
|
||||||
|
// asm refuses ADJSP $0 outright, so a zero value is an error here too; the
|
||||||
|
// statement's effect on the SP balance is checked by the function-level
|
||||||
|
// assembly (checkAdjspBalance), as the toolchain's push/pop walk does.
|
||||||
|
func (e *enc) encodeAdjsp(ops []Operand) error {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("ADJSP expects 1 immediate operand, got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("ADJSP requires an integer immediate")
|
||||||
|
}
|
||||||
|
switch v := int(imm); {
|
||||||
|
case v > 0:
|
||||||
|
e.out = append(e.out, subSP(v)...)
|
||||||
|
case v < 0:
|
||||||
|
e.out = append(e.out, addSP(-v)...)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("ADJSP $0 has no encoding")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- floating-point immediates ----------------------------------------------
|
||||||
|
|
||||||
|
// sseFloatImm lists the mnemonics whose first operand may be a floating-point
|
||||||
|
// immediate, the set go tool asm rewrites into a pooled-constant read: the
|
||||||
|
// scalar moves, the four scalar arithmetic pairs and the scalar compares.
|
||||||
|
// The packed members and the uniform forms (MAXSD, MINSD, SQRTSD, CMPSD)
|
||||||
|
// reject the immediate in the toolchain and are absent here on purpose.
|
||||||
|
var sseFloatImm = map[string]bool{
|
||||||
|
"MOVSD": true, "MOVSS": true,
|
||||||
|
"ADDSD": true, "ADDSS": true,
|
||||||
|
"SUBSD": true, "SUBSS": true,
|
||||||
|
"MULSD": true, "MULSS": true,
|
||||||
|
"DIVSD": true, "DIVSS": true,
|
||||||
|
"COMISD": true, "COMISS": true,
|
||||||
|
"UCOMISD": true, "UCOMISS": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// floatImmOperand reports whether the operand list opens with a
|
||||||
|
// floating-point immediate in the two-operand spelling (imm, dst).
|
||||||
|
func floatImmOperand(ops []Operand) (FloatImm, bool) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return FloatImm{}, false
|
||||||
|
}
|
||||||
|
f, ok := ops[0].(FloatImm)
|
||||||
|
return f, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// floatPoolValue evaluates a floating-point immediate at the width its
|
||||||
|
// mnemonic encodes and names the pool constant the toolchain synthesises:
|
||||||
|
// $f64.<16 hex> for the doubles, $f32.<8 hex> for the singles (the float32
|
||||||
|
// rounding of the parsed value). The name carries the IEEE-754 bits; the
|
||||||
|
// section holds them little-endian.
|
||||||
|
func floatPoolValue(mnem string, f FloatImm) (bits uint64, name string, err error) {
|
||||||
|
v, err := strconv.ParseFloat(f.Text, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("invalid floating-point immediate %q", f.Text)
|
||||||
|
}
|
||||||
|
if f.Neg {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(mnem, "D") {
|
||||||
|
bits = math.Float64bits(v)
|
||||||
|
return bits, fmt.Sprintf("$f64.%016x", bits), nil
|
||||||
|
}
|
||||||
|
bits = uint64(math.Float32bits(float32(v)))
|
||||||
|
return bits, fmt.Sprintf("$f32.%08x", bits), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEFloatMove encodes MOVSD/MOVSS with a floating-point immediate
|
||||||
|
// source. A positive zero needs no memory read: the toolchain emits
|
||||||
|
// XORPS dst, dst. Anything else loads the pooled constant RIP-relative
|
||||||
|
// ($f64.<hex>(SB) / $f32.<hex>(SB)), the displacement a patch site the
|
||||||
|
// file-level layout or the linker resolves.
|
||||||
|
func (e *enc) encodeSSEFloatMove(mnem string, f FloatImm, ops []Operand) error {
|
||||||
|
if !sseFloatImm[mnem] {
|
||||||
|
return fmt.Errorf("%s does not take a floating-point immediate", mnem)
|
||||||
|
}
|
||||||
|
dst, ok := ops[1].(Reg)
|
||||||
|
if !ok || !dst.isVec() {
|
||||||
|
return fmt.Errorf("%s: destination must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
bits, name, err := floatPoolValue(mnem, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.addFloatPool(name, bits, mwidth(mnem))
|
||||||
|
if bits == 0 {
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x57}, modrm: -1, sib: -1} // XORPS
|
||||||
|
if err := setRM(i, dst, dst, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
m := sseMoveTable[mnem]
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.load}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dst, sbMem{size: mwidth(mnem), name: name}, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEFloatBin encodes the scalar arithmetic and compare mnemonics with
|
||||||
|
// a floating-point immediate source: the constant is read from the pool into
|
||||||
|
// the instruction's r/m side (reg = destination), the rewrite go tool asm
|
||||||
|
// performs at the source level.
|
||||||
|
func (e *enc) encodeSSEFloatBin(mnem string, m sseBin, f FloatImm, ops []Operand) error {
|
||||||
|
if !sseFloatImm[mnem] {
|
||||||
|
return fmt.Errorf("%s does not take a floating-point immediate", mnem)
|
||||||
|
}
|
||||||
|
dst, ok := ops[1].(Reg)
|
||||||
|
if !ok || !dst.isVec() {
|
||||||
|
return fmt.Errorf("%s: destination must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
bits, name, err := floatPoolValue(mnem, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.addFloatPool(name, bits, mwidth(mnem))
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dst, sbMem{size: mwidth(mnem), name: name}, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mwidth returns the operand width a scalar SSE mnemonic encodes: the double
|
||||||
|
// spellings end in D, the single spellings in S.
|
||||||
|
func mwidth(mnem string) int {
|
||||||
|
if strings.HasSuffix(mnem, "D") {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
// splitSize separates a trailing B/W/L/Q size suffix from the mnemonic.
|
// splitSize separates a trailing B/W/L/Q size suffix from the mnemonic.
|
||||||
func splitSize(upper string) (base string, size int) {
|
func splitSize(upper string) (base string, size int) {
|
||||||
if upper == "" {
|
if upper == "" {
|
||||||
@@ -307,6 +682,7 @@ type instr struct {
|
|||||||
disp []byte
|
disp []byte
|
||||||
imm []byte
|
imm []byte
|
||||||
sb *sbRef // static-symbol displacement in disp, awaiting resolution
|
sb *sbRef // static-symbol displacement in disp, awaiting resolution
|
||||||
|
tls bool // the displacement is a TLS slot offset, patched R_TLSLE
|
||||||
}
|
}
|
||||||
|
|
||||||
// sbRef records that an instruction's displacement refers to a static symbol
|
// sbRef records that an instruction's displacement refers to a static symbol
|
||||||
@@ -349,6 +725,9 @@ func (e *enc) emit(i *instr) error {
|
|||||||
if i.sb != nil {
|
if i.sb != nil {
|
||||||
e.patches = append(e.patches, encPatch{off: len(e.out), name: i.sb.name, addend: i.sb.addend})
|
e.patches = append(e.patches, encPatch{off: len(e.out), name: i.sb.name, addend: i.sb.addend})
|
||||||
}
|
}
|
||||||
|
if i.tls {
|
||||||
|
e.patches = append(e.patches, encPatch{off: len(e.out), tls: true})
|
||||||
|
}
|
||||||
e.out = append(e.out, i.disp...)
|
e.out = append(e.out, i.disp...)
|
||||||
e.out = append(e.out, i.imm...)
|
e.out = append(e.out, i.imm...)
|
||||||
return nil
|
return nil
|
||||||
@@ -403,12 +782,30 @@ func setRMReg(i *instr, regField int, rexR, regForced bool, rm Operand, opSize i
|
|||||||
i.disp = le32(0)
|
i.disp = le32(0)
|
||||||
i.sb = &sbRef{name: r.name, addend: r.addend}
|
i.sb = &sbRef{name: r.name, addend: r.addend}
|
||||||
return nil
|
return nil
|
||||||
|
case TLSMem:
|
||||||
|
// off(TLS): the segment-prefixed absolute access, mod=00 with the
|
||||||
|
// SIB escape's disp32 absolute form. The displacement is the TLS
|
||||||
|
// slot offset, patched by the linker's TLS relocation.
|
||||||
|
i.prefix = r.Seg
|
||||||
|
i.modrm = 0x04 | regField<<3
|
||||||
|
i.sib = 0x25
|
||||||
|
i.disp = le32(r.Disp)
|
||||||
|
i.tls = true
|
||||||
|
return nil
|
||||||
|
case SegAbs:
|
||||||
|
// 0x30(GS): the segment override with the SIB escape's disp32
|
||||||
|
// absolute form, no relocation.
|
||||||
|
setSegAbs(i, regField, r)
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid r/m operand %T", rm)
|
return fmt.Errorf("invalid r/m operand %T", rm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMem(i *instr, regField int, m Mem) error {
|
func setMem(i *instr, regField int, m Mem) error {
|
||||||
|
if m.Seg != 0 {
|
||||||
|
i.prefix = m.Seg
|
||||||
|
}
|
||||||
modrm, sib, disp, xBit, bBit, err := memComponents(regField, m)
|
modrm, sib, disp, xBit, bBit, err := memComponents(regField, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -421,6 +818,16 @@ func setMem(i *instr, regField int, m Mem) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setSegAbs assembles a segment-absolute operand, 0x30(GS): the segment
|
||||||
|
// override with the mod=00 SIB escape's disp32 absolute form and no
|
||||||
|
// relocation.
|
||||||
|
func setSegAbs(i *instr, regField int, m SegAbs) {
|
||||||
|
i.prefix = m.Seg
|
||||||
|
i.modrm = 0x04 | regField<<3
|
||||||
|
i.sib = 0x25
|
||||||
|
i.disp = le32(m.Disp)
|
||||||
|
}
|
||||||
|
|
||||||
// memComponents computes the ModR/M byte (with the given reg field), the SIB
|
// memComponents computes the ModR/M byte (with the given reg field), the SIB
|
||||||
// byte (-1 if none), the displacement bytes, and the high index/base bits, for
|
// byte (-1 if none), the displacement bytes, and the high index/base bits, for
|
||||||
// a memory operand. It is shared by the REX (scalar) and VEX (vector) paths.
|
// a memory operand. It is shared by the REX (scalar) and VEX (vector) paths.
|
||||||
|
|||||||
+465
-4
@@ -9,6 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// decode encodes an instruction and decodes it back, returning the decoded
|
// decode encodes an instruction and decodes it back, returning the decoded
|
||||||
@@ -262,7 +265,11 @@ func TestImul(t *testing.T) {
|
|||||||
|
|
||||||
func TestControl(t *testing.T) {
|
func TestControl(t *testing.T) {
|
||||||
checkSyntax(t, "ret", "RET")
|
checkSyntax(t, "ret", "RET")
|
||||||
checkSyntax(t, "nop", "NOP")
|
// NOP contributes nothing on amd64, consumed whole by the toolchain as
|
||||||
|
// a pseudo; only the bytes pin it (no decodable instruction remains).
|
||||||
|
if code, err := Encode("NOP"); err != nil || len(code) != 0 {
|
||||||
|
t.Errorf("NOP: bytes %x (err %v), want empty", code, err)
|
||||||
|
}
|
||||||
checkOp(t, x86asm.JMP, "JMP", Imm(0))
|
checkOp(t, x86asm.JMP, "JMP", Imm(0))
|
||||||
checkOp(t, x86asm.CALL, "CALL", Imm(0))
|
checkOp(t, x86asm.CALL, "CALL", Imm(0))
|
||||||
checkOp(t, x86asm.JGE, "JGE", Imm(0))
|
checkOp(t, x86asm.JGE, "JGE", Imm(0))
|
||||||
@@ -404,6 +411,16 @@ func TestScalarGroundTruth(t *testing.T) {
|
|||||||
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
||||||
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
||||||
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
||||||
|
// The renderer's condition spellings carry no size letter; the width
|
||||||
|
// rides the operand registers and the bytes match the toolchain's own
|
||||||
|
// size-prefixed spellings (CMOVQLE/CMOVLLE/CMOVWLE pinned from go tool
|
||||||
|
// asm). CMOVLE with the 16-bit registers reproduces the disasm
|
||||||
|
// fixture's 660f4e13 row byte for byte.
|
||||||
|
{"CMOVLE (BX),DX", "CMOVLE", []Operand{Ptr(BX, 0, 2), DX}, "660f4e13", "CMOVLE"},
|
||||||
|
{"CMOVQLE AX,BX", "CMOVQLE", []Operand{Reg{idx: 0, size: 8}, Reg{idx: 3, size: 8}}, "480f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVLLE AX,BX", "CMOVLLE", []Operand{Reg{idx: 0, size: 4}, Reg{idx: 3, size: 4}}, "0f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVWLE AX,BX", "CMOVWLE", []Operand{AX, BX}, "660f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVB AL,CL", "CMOVB", []Operand{AL, CL}, "0f42c8", "CMOVB"},
|
||||||
{"SETNE AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
{"SETNE AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
||||||
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
||||||
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
||||||
@@ -588,14 +605,15 @@ func TestImmediateTruncation(t *testing.T) {
|
|||||||
|
|
||||||
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
||||||
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
||||||
// letter) is not reported as encodable.
|
// letter) is not reported as encodable. The condition-name spellings the
|
||||||
|
// renderer prints (CMOVB, CMOVLE) encode with the width from the operands.
|
||||||
func TestEncodableCmovSize(t *testing.T) {
|
func TestEncodableCmovSize(t *testing.T) {
|
||||||
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOVB", "CMOV", "CMOVWXX"} {
|
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOV", "CMOVWXX"} {
|
||||||
if Encodable(m) {
|
if Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = true, want false", m)
|
t.Errorf("Encodable(%q) = true, want false", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ"} {
|
for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ", "CMOVB", "CMOVLE"} {
|
||||||
if !Encodable(m) {
|
if !Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = false, want true", m)
|
t.Errorf("Encodable(%q) = false, want true", m)
|
||||||
}
|
}
|
||||||
@@ -925,3 +943,446 @@ func TestMOVQXMMGroundTruth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPrefixStatements pins LOCK, REP and REPN. go tool asm encodes each as
|
||||||
|
// a standalone one-byte instruction with a PC of its own (F0, F3, F2), not a
|
||||||
|
// prefix field merged into the following instruction, and it validates
|
||||||
|
// nothing about the pairing (LOCK before NOP assembles). The prefixed
|
||||||
|
// atomic and string shapes are the bytes the runtime's own kernels need.
|
||||||
|
func TestPrefixStatements(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"LOCK", "LOCK", nil, "f0"},
|
||||||
|
{"REP", "REP", nil, "f3"},
|
||||||
|
{"REPN", "REPN", nil, "f2"},
|
||||||
|
// LOCK; CMPXCHGQ AX, (BX)
|
||||||
|
{"LOCK CMPXCHGQ", "CMPXCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fb103"},
|
||||||
|
// REP; MOVSQ
|
||||||
|
{"REP MOVSQ", "MOVSQ", nil, "48a5"},
|
||||||
|
// REPN; MOVSB
|
||||||
|
{"REPN MOVSB", "MOVSB", nil, "a4"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||||
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The prefix statements take no operands, as the toolchain reports for
|
||||||
|
// LOCK AX.
|
||||||
|
if _, err := Encode("LOCK", AX); err == nil {
|
||||||
|
t.Error("LOCK AX assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := Encode("REP", Imm(1)); err == nil {
|
||||||
|
t.Error("REP $1 assembled, want an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDataEmission pins BYTE, WORD, LONG and QUAD: the immediate lands in
|
||||||
|
// the text stream as 1, 2, 4 or 8 little-endian bytes with no opcode
|
||||||
|
// lookup, truncated to the width rather than range-checked (go tool asm
|
||||||
|
// emits FF for BYTE $0x1FF and 45 23 for WORD $0x12345, both silently).
|
||||||
|
func TestDataEmission(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
imm Imm
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"BYTE", "BYTE", 0x0f, "0f"},
|
||||||
|
{"BYTE negative", "BYTE", -1, "ff"},
|
||||||
|
{"BYTE truncated", "BYTE", 0x1ff, "ff"},
|
||||||
|
{"WORD", "WORD", 0x1234, "3412"},
|
||||||
|
{"WORD negative", "WORD", -1, "ffff"},
|
||||||
|
{"WORD truncated", "WORD", 0x12345, "4523"},
|
||||||
|
{"LONG", "LONG", 0x11223344, "44332211"},
|
||||||
|
{"LONG negative", "LONG", -1, "ffffffff"},
|
||||||
|
{"QUAD", "QUAD", 0x1122334455667788, "8877665544332211"},
|
||||||
|
{"QUAD negative", "QUAD", -2, "feffffffffffffff"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.imm)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||||
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exactly one immediate: the toolchain rejects BYTE $1, $2, $3, and a
|
||||||
|
// register or a missing operand is no immediate at all.
|
||||||
|
if _, err := Encode("BYTE"); err == nil {
|
||||||
|
t.Error("BYTE with no operand assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := Encode("BYTE", Imm(1), Imm(2)); err == nil {
|
||||||
|
t.Error("BYTE $1, $2 assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := Encode("WORD", AX); err == nil {
|
||||||
|
t.Error("WORD AX assembled, want an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEndIgnored pins END: go tool asm drops the statement entirely, so it
|
||||||
|
// encodes to zero bytes and takes any operands without complaint (the
|
||||||
|
// toolchain accepts END $0 and END AX alike).
|
||||||
|
func TestEndIgnored(t *testing.T) {
|
||||||
|
for _, ops := range [][]Operand{nil, {Imm(0)}, {AX}} {
|
||||||
|
code, err := Encode("END", ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("END: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(code) != 0 {
|
||||||
|
t.Errorf("END = %x, want no bytes", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdjsp pins ADJSP: a positive immediate is SUBQ $imm, SP, a negative
|
||||||
|
// one ADDQ $-imm, SP, in the imm8 or imm32 form the magnitude picks; $0
|
||||||
|
// has no encoding (go tool asm refuses ADJSP $0 outright).
|
||||||
|
func TestAdjsp(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
imm Imm
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"imm8", 112, "4883ec70"},
|
||||||
|
{"imm8 negative", -112, "4883c470"},
|
||||||
|
{"imm32", 200, "4881ecc8000000"},
|
||||||
|
{"imm32 negative", -200, "4881c4c8000000"},
|
||||||
|
{"small", 8, "4883ec08"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode("ADJSP", c.imm)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||||
|
t.Errorf("ADJSP %d = %s, want %s", int64(c.imm), got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := Encode("ADJSP", Imm(0)); err == nil {
|
||||||
|
t.Error("ADJSP $0 assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := Encode("ADJSP"); err == nil {
|
||||||
|
t.Error("ADJSP with no operand assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := Encode("ADJSP", AX); err == nil {
|
||||||
|
t.Error("ADJSP AX assembled, want an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFloatImmediateGroundTruth pins the floating-point immediate rewrite
|
||||||
|
// byte for byte against go tool asm: the scalar moves and the scalar
|
||||||
|
// arithmetic read the constant from a synthesised read-only pool symbol
|
||||||
|
// ($f64.<hex>, $f32.<hex>) RIP-relative with the displacement left to the
|
||||||
|
// relocation, and a positive zero on the moves collapses to XORPS dst, dst.
|
||||||
|
func TestFloatImmediateGroundTruth(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"MOVSD -1.0", "MOVSD", []Operand{FloatImm{Text: "1.0", Neg: true}, vreg(t, "X2")}, "f20f101500000000"},
|
||||||
|
{"MOVSD 1.5", "MOVSD", []Operand{FloatImm{Text: "1.5"}, vreg(t, "X3")}, "f20f101d00000000"},
|
||||||
|
{"MOVSS 2.5", "MOVSS", []Operand{FloatImm{Text: "2.5"}, vreg(t, "X4")}, "f30f102500000000"},
|
||||||
|
{"MOVSS -0.5", "MOVSS", []Operand{FloatImm{Text: "0.5", Neg: true}, vreg(t, "X5")}, "f30f102d00000000"},
|
||||||
|
{"MOVSS +0.0 is XORPS", "MOVSS", []Operand{FloatImm{Text: "0.0"}, vreg(t, "X10")}, "450f57d2"},
|
||||||
|
{"MOVSD +0.0 is XORPS", "MOVSD", []Operand{FloatImm{Text: "0.0"}, vreg(t, "X6")}, "0f57f6"},
|
||||||
|
{"ADDSD 1.0", "ADDSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "f20f580500000000"},
|
||||||
|
{"ADDSS 0.5", "ADDSS", []Operand{FloatImm{Text: "0.5"}, vreg(t, "X1")}, "f30f580d00000000"},
|
||||||
|
{"SUBSD 2.0", "SUBSD", []Operand{FloatImm{Text: "2.0"}, vreg(t, "X3")}, "f20f5c1d00000000"},
|
||||||
|
{"MULSD -2.5", "MULSD", []Operand{FloatImm{Text: "2.5", Neg: true}, vreg(t, "X3")}, "f20f591d00000000"},
|
||||||
|
{"DIVSD 1.0", "DIVSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "f20f5e0500000000"},
|
||||||
|
{"COMISD 1.0", "COMISD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "660f2f0500000000"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pool names carry the IEEE-754 bits, the float32 narrowing for the
|
||||||
|
// single spellings; negative zero keeps its sign bit and never takes the
|
||||||
|
// XORPS shortcut.
|
||||||
|
for _, c := range []struct {
|
||||||
|
mnem string
|
||||||
|
imm FloatImm
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"MOVSD", FloatImm{Text: "1.0", Neg: true}, "$f64.bff0000000000000"},
|
||||||
|
{"MOVSD", FloatImm{Text: "0.5"}, "$f64.3fe0000000000000"},
|
||||||
|
{"MOVSS", FloatImm{Text: "2.5"}, "$f32.40200000"},
|
||||||
|
{"MOVSS", FloatImm{Text: "0.5", Neg: true}, "$f32.bf000000"},
|
||||||
|
{"MOVSD", FloatImm{Text: "0.0", Neg: true}, "$f64.8000000000000000"},
|
||||||
|
} {
|
||||||
|
_, name, err := floatPoolValue(c.mnem, c.imm)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s %s: %v", c.mnem, c.imm.Text, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name != c.want {
|
||||||
|
t.Errorf("%s $%s: pool name %s, want %s", c.mnem, c.imm.Text, name, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shapes the toolchain's parser rejects: the packed and uniform
|
||||||
|
// forms, a non-vector destination, and the integer spellings.
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
}{
|
||||||
|
{"MAXSD rejects the immediate", "MAXSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
||||||
|
{"MINSD rejects the immediate", "MINSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
||||||
|
{"SQRTSD rejects the immediate", "SQRTSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
||||||
|
{"integer destination", "MOVSD", []Operand{FloatImm{Text: "1.0"}, AX}},
|
||||||
|
} {
|
||||||
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||||||
|
t.Errorf("%s: expected an error, got none", c.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBookkeepingGroundTruth pins FUNCDATA and PCDATA as accept-and-ignore:
|
||||||
|
// go tool asm emits no text bytes for either, on every architecture.
|
||||||
|
func TestBookkeepingGroundTruth(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
}{
|
||||||
|
{"FUNCDATA", "FUNCDATA", []Operand{Imm(3), sbMem{name: "\u00b7f.arginfo0"}}},
|
||||||
|
{"PCDATA", "PCDATA", []Operand{Imm(1), Imm(-1)}},
|
||||||
|
} {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(code) != 0 {
|
||||||
|
t.Errorf("%s: emitted %x, want no bytes", c.name, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
}{
|
||||||
|
{"FUNCDATA arity", "FUNCDATA", []Operand{Imm(3)}},
|
||||||
|
{"FUNCDATA missing the count", "FUNCDATA", []Operand{sbMem{name: "x"}}},
|
||||||
|
{"FUNCDATA integer value", "FUNCDATA", []Operand{Imm(3), Imm(4)}},
|
||||||
|
{"PCDATA arity", "PCDATA", []Operand{Imm(1)}},
|
||||||
|
{"PCDATA register value", "PCDATA", []Operand{Imm(1), AX}},
|
||||||
|
} {
|
||||||
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||||||
|
t.Errorf("%s: expected an error, got none", c.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At the statement level the bookkeeping lines sit between real
|
||||||
|
// instructions and contribute nothing to the body, symbol reference
|
||||||
|
// included: the FUNCDATA operand never needs file-level resolution.
|
||||||
|
f, errs := parser.Parse("t_amd64.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tNOP\n\tFUNCDATA $3, \u00b7f.arginfo0(SB)\n\tPCDATA $1, $-1\n\tFUNCDATA $0, x<>(SB)\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
want := "c3"
|
||||||
|
if got := hexCompact(img.Code); got != want {
|
||||||
|
t.Errorf("body %s, want %s (the bookkeeping lines contribute nothing)", got, want)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFile(mustParse(t, "TEXT \u00b7f(SB), NOSPLIT, $0\n\tFUNCDATA $1, X0\n\tRET\n")); err == nil {
|
||||||
|
t.Error("FUNCDATA $1, X0 assembled, want an error")
|
||||||
|
}
|
||||||
|
if _, err := AssembleFile(mustParse(t, "TEXT \u00b7f(SB), NOSPLIT, $0\n\tPCDATA $1, X0\n\tRET\n")); err == nil {
|
||||||
|
t.Error("PCDATA $1, X0 assembled, want an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodable mirrors Encode for the names this work touched.
|
||||||
|
for _, mnem := range []string{"FUNCDATA", "PCDATA", "V4FMADDPS", "V4FMADDSS", "V4FNMADDPS", "V4FNMADDSS", "VP4DPWSSD", "VP4DPWSSDS"} {
|
||||||
|
if !Encodable(mnem) {
|
||||||
|
t.Errorf("Encodable(%s) = false, want true", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustParse parses src or fails the test.
|
||||||
|
func mustParse(t *testing.T, src string) *ast.File {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("t_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCorpusTailSystem pins the system and control forms the toolchain's own
|
||||||
|
// amd64 testdata carries, byte for byte: the one-operand IMUL, the compare
|
||||||
|
// family, the far return, the loop, the MMX moves, the CR/DR and segment
|
||||||
|
// register moves, the TLS pseudo-base and the 0F AE/1C/C7 controls.
|
||||||
|
func TestCorpusTailSystem(t *testing.T) {
|
||||||
|
regBPT := Reg{idx: 5, size: 8}
|
||||||
|
X0, X1, X2 := vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")
|
||||||
|
Y1, Y2, Y7 := vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y7")
|
||||||
|
X5, X20 := vreg(t, "X5"), vreg(t, "X20")
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"IMUL one-op byte", "IMULB", []Operand{DX}, "f6ea"},
|
||||||
|
{"IMUL one-op long", "IMULL", []Operand{AX}, "f7e8"},
|
||||||
|
{"CMPPD", "CMPPD", []Operand{X1, X2, Imm(4)}, "660fc2d104"},
|
||||||
|
{"CMPSS", "CMPSS", []Operand{X1, X2, Imm(4)}, "f30fc2d104"},
|
||||||
|
{"CMPPS", "CMPPS", []Operand{X1, X2, Imm(4)}, "0fc2d104"},
|
||||||
|
{"RETFL", "RETFL", []Operand{Imm(4)}, "ca0400"},
|
||||||
|
{"LOOP", "LOOP", []Operand{Imm(-2)}, "e2fe"},
|
||||||
|
{"LOOPE", "LOOPE", []Operand{Imm(-2)}, "e1fe"},
|
||||||
|
{"LOOPNE", "LOOPNE", []Operand{Imm(-2)}, "e0fe"},
|
||||||
|
{"PADDD MMX", "PADDD", []Operand{Reg{idx: 2, size: 8, mmx: true}, Reg{idx: 1, size: 8, mmx: true}}, "0ffeca"},
|
||||||
|
{"MOVDQ2Q", "MOVDQ2Q", []Operand{X1, Reg{idx: 1, size: 8, mmx: true}}, "f20fd6c9"},
|
||||||
|
{"MOVNTDQ", "MOVNTDQ", []Operand{X1, Ptr(AX, 0, 16)}, "660fe708"},
|
||||||
|
{"MOVQ mmx load", "MOVQ", []Operand{Ptr(AX, 0, 8), Reg{idx: 0, size: 8, mmx: true}}, "0f6f00"},
|
||||||
|
{"MOVQ mmx store", "MOVQ", []Operand{Reg{idx: 0, size: 8, mmx: true}, Ptr(SI, 0, 8)}, "0f7f06"},
|
||||||
|
{"MOVQ CR0 load", "MOVQ", []Operand{Reg{idx: 0, size: 8, ctl: 1}, AX}, "0f20c0"},
|
||||||
|
{"MOVQ CR4 load", "MOVQ", []Operand{Reg{idx: 4, size: 8, ctl: 1}, DI}, "0f20e7"},
|
||||||
|
{"MOVQ CR0 store", "MOVQ", []Operand{AX, Reg{idx: 0, size: 8, ctl: 1}}, "0f22c0"},
|
||||||
|
{"MOVQ DR0 load", "MOVQ", []Operand{Reg{idx: 0, size: 8, ctl: 2}, AX}, "0f21c0"},
|
||||||
|
{"MOVQ DR7 load", "MOVQ", []Operand{Reg{idx: 7, size: 8, ctl: 2}, SI}, "0f21fe"},
|
||||||
|
{"PUSHQ FS", "PUSHQ", []Operand{Reg{idx: 4, size: 2, seg: 5}}, "0fa0"},
|
||||||
|
{"PUSHQ GS", "PUSHQ", []Operand{Reg{idx: 5, size: 2, seg: 6}}, "0fa8"},
|
||||||
|
{"POPQ FS", "POPQ", []Operand{Reg{idx: 4, size: 2, seg: 5}}, "0fa1"},
|
||||||
|
{"POPQ GS", "POPQ", []Operand{Reg{idx: 5, size: 2, seg: 6}}, "0fa9"},
|
||||||
|
{"ENDBR64", "ENDBR64", nil, "f30f1efa"},
|
||||||
|
{"CLWB", "CLWB", []Operand{Ptr(BX, 0, 8)}, "660fae33"},
|
||||||
|
{"CLDEMOTE", "CLDEMOTE", []Operand{Ptr(BX, 0, 8)}, "0f1c03"},
|
||||||
|
{"TPAUSE", "TPAUSE", []Operand{BX}, "660faef3"},
|
||||||
|
{"UMONITOR", "UMONITOR", []Operand{BX}, "f30faef3"},
|
||||||
|
{"UMWAIT", "UMWAIT", []Operand{BX}, "f20faef3"},
|
||||||
|
{"RDPID", "RDPID", []Operand{DX}, "f30fc7fa"},
|
||||||
|
{"RDPID r11", "RDPID", []Operand{Reg{idx: 11, size: 8}}, "f3410fc7fb"},
|
||||||
|
{"LEAL wide disp", "LEAL", []Operand{Idx(regBPT, Reg{idx: 10, size: 8}, 1, 0x8f1bbcdc, 8), regBPT}, "428dac15dcbc1b8f"},
|
||||||
|
{"VPERMPD", "VPERMPD", []Operand{Imm(0xd8), Y7, Y7}, "c4e3fd01ffd8"},
|
||||||
|
{"VPERMILPD", "VPERMILPD", []Operand{Imm(0xff), X1, X2}, "c4e37905d1ff"},
|
||||||
|
{"VPERMILPS", "VPERMILPS", []Operand{Imm(0xff), X1, X2}, "c4e37904d1ff"},
|
||||||
|
{"VROUNDPD", "VROUNDPD", []Operand{Imm(-1), X1, X2}, "c4e37909d1ff"},
|
||||||
|
{"VROUNDPS", "VROUNDPS", []Operand{Imm(-1), Y1, Y2}, "c4e37d08d1ff"},
|
||||||
|
{"VAESKEYGENASSIST", "VAESKEYGENASSIST", []Operand{Imm(-1), X1, X2}, "c4e379dfd1ff"},
|
||||||
|
{"VPCMPESTRI", "VPCMPESTRI", []Operand{Imm(-1), X1, X2}, "c4e37961d1ff"},
|
||||||
|
{"VPCMPESTRM", "VPCMPESTRM", []Operand{Imm(-1), X1, X2}, "c4e37960d1ff"},
|
||||||
|
{"VPCMPISTRI", "VPCMPISTRI", []Operand{Imm(-1), X1, X2}, "c4e37963d1ff"},
|
||||||
|
{"VPCMPISTRM", "VPCMPISTRM", []Operand{Imm(-1), X1, X2}, "c4e37962d1ff"},
|
||||||
|
{"VEXTRACTPS", "VEXTRACTPS", []Operand{Imm(-1), X1, AX}, "c4e37917c8ff"},
|
||||||
|
{"VPEXTRW", "VPEXTRW", []Operand{Imm(0xff), X1, AX}, "c4e37915c8ff"},
|
||||||
|
{"VPBLENDVB", "VPBLENDVB", []Operand{X0, Ptr(BX, 0, 16), X1, X2}, "c4e3714c1300"},
|
||||||
|
{"VMOVHPD load", "VMOVHPD", []Operand{Ptr(AX, 0, 8), X5, X5}, "c5d11628"},
|
||||||
|
{"VMOVHPD load disp", "VMOVHPD", []Operand{Ptr(DX, 7, 8), X5, X5}, "c5d1166a07"},
|
||||||
|
{"VMOVHPD store", "VMOVHPD", []Operand{X5, Ptr(AX, 0, 8)}, "c5f91728"},
|
||||||
|
{"VMOVLPD load", "VMOVLPD", []Operand{Ptr(AX, 0, 8), X5, X5}, "c5d11228"},
|
||||||
|
{"VMOVLPD store", "VMOVLPD", []Operand{X5, Ptr(AX, 0, 8)}, "c5f91328"},
|
||||||
|
{"VMOVQ EVEX gpr load", "VMOVQ", []Operand{Reg{idx: 4, size: 8}, X20}, "62e1fd086ee4"},
|
||||||
|
{"VMOVQ EVEX mem store", "VMOVQ", []Operand{X20, Ptr(AX, 0, 8)}, "62e1fd087e20"},
|
||||||
|
{"VMOVQ EVEX mem load", "VMOVQ", []Operand{Ptr(AX, 0, 8), X20}, "62e1fd086e20"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCorpusTailFileForms pins the file-level forms the toolchain's amd64
|
||||||
|
// testdata carries: the star-marked indirect jumps, the TLS pseudo-base, the
|
||||||
|
// paired-register shift spelling, the absolute RET and the jump to an
|
||||||
|
// undefined static symbol (its displacement and the TLS slot offsets are
|
||||||
|
// relocation sites, zeroed here as the kernel parity suites do).
|
||||||
|
func TestCorpusTailFileForms(t *testing.T) {
|
||||||
|
mask32 := func(b []byte, at int) { b[at], b[at+1], b[at+2], b[at+3] = 0, 0, 0, 0 }
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string // hex, with X marking a masked 32-bit relocation site
|
||||||
|
}{
|
||||||
|
{"star reg jump", "\tJMP *(R12)\n\tRET\n", "41ff2424c3"},
|
||||||
|
{"star sp jump", "\tJMP *4(SP)\n\tRET\n", "ff642404c3"},
|
||||||
|
{"star indexed jump", "\tJMP *(R12)(R13*4)\n\tRET\n", "43ff24acc3"},
|
||||||
|
{"TLS load", "\tMOVQ (TLS), AX\n\tRET\n", "64488b0425XXXXXXXXc3"},
|
||||||
|
{"TLS load offset", "\tMOVQ 8(TLS), DX\n\tRET\n", "64488b1425XXXXXXXXc3"},
|
||||||
|
{"colon shift", "\tSHLL CX, R11:AX\n\tRET\n", "410fa5c3c3"},
|
||||||
|
{"SP indexed local", "\tMOVQ foo(SP)(AX*1), BX\n\tRET\n", "488b1c04c3"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
f, errs := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n"+c.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Errorf("%s: parse: %v", c.name, errs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: assemble: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
||||||
|
if at := strings.Index(c.want, "XXXXXXXX"); at >= 0 {
|
||||||
|
mask32(code, at/2) // the masked relocation site
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != strings.ReplaceAll(c.want, "X", "0") {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JMP to an undefined static symbol and the absolute RET: their rel32
|
||||||
|
// carries a call relocation against the symbol, masked to zero here.
|
||||||
|
for _, c := range []struct{ name, src string }{
|
||||||
|
{"static external jump", "\tJMP bar<>+4(SB)\n\tRET\n"},
|
||||||
|
{"static external indexed jump", "\tJMP bar<>+4(SB)(R11*4)\n\tRET\n"},
|
||||||
|
{"absolute ret", "\tRET\n\tRET foo(SB)\n"},
|
||||||
|
} {
|
||||||
|
f, errs := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n"+c.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Errorf("%s: parse: %v", c.name, errs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: assemble: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
want := "e900000000c3"
|
||||||
|
if c.name == "absolute ret" {
|
||||||
|
want = "c3e900000000"
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != want {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+645
-27
@@ -5,6 +5,7 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ var evexTable = map[string]evexSpec{
|
|||||||
"VPSRAD": {1, 0x72, 0, 1, 4, vexShiftImm, [3]int{16, 32, 64}},
|
"VPSRAD": {1, 0x72, 0, 1, 4, vexShiftImm, [3]int{16, 32, 64}},
|
||||||
// EVEX.128/256/512.66.0F.W1, variable shift with an XMM count (VPSRAQ;
|
// EVEX.128/256/512.66.0F.W1, variable shift with an XMM count (VPSRAQ;
|
||||||
// the W bit distinguishes it from VPSRAD's E2 form).
|
// the W bit distinguishes it from VPSRAD's E2 form).
|
||||||
"VPSRAQ": {1, 0xE2, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPSRAQ": {1, 0x72, 1, 1, 4, vexShiftImm, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
// EVEX.128/256/512.F3.0F.W1, signed qword to packed double (reg=dst,
|
// EVEX.128/256/512.F3.0F.W1, signed qword to packed double (reg=dst,
|
||||||
// rm=src, no vvvv).
|
// rm=src, no vvvv).
|
||||||
@@ -138,7 +139,7 @@ var evexTable = map[string]evexSpec{
|
|||||||
|
|
||||||
// EVEX.66.0F, the EVEX forms of the VEX two-source shuffle.
|
// EVEX.66.0F, the EVEX forms of the VEX two-source shuffle.
|
||||||
"VSHUFPD": {1, 0xC6, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
"VSHUFPD": {1, 0xC6, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
"VSHUFPS": {1, 0xC6, 0, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
"VSHUFPS": {1, 0xC6, 0, 0, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
// EVEX.66.0F3A, lane insert ($imm, xsrc, zsrc1, zdst).
|
// EVEX.66.0F3A, lane insert ($imm, xsrc, zsrc1, zdst).
|
||||||
"VINSERTF32X4": {3, 0x18, 0, 1, -1, vexNDS3Imm, [3]int{0, 16, 32}},
|
"VINSERTF32X4": {3, 0x18, 0, 1, -1, vexNDS3Imm, [3]int{0, 16, 32}},
|
||||||
@@ -202,7 +203,7 @@ var evexTable = map[string]evexSpec{
|
|||||||
"VPMULHUW": {1, 0xE4, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPMULHUW": {1, 0xE4, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPMADDUBSW": {2, 0x04, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPMADDUBSW": {2, 0x04, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPSLLVW": {2, 0x12, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPSLLVW": {2, 0x12, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPSRLVW": {2, 0x11, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPSRLVW": {2, 0x10, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPACKSSWB": {1, 0x63, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPACKSSWB": {1, 0x63, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPACKUSWB": {1, 0x67, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPACKUSWB": {1, 0x67, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
"VPACKSSDW": {1, 0x6B, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
"VPACKSSDW": {1, 0x6B, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
@@ -324,7 +325,7 @@ var evexTable = map[string]evexSpec{
|
|||||||
"VCVTPD2UQQ": {1, 0x79, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
"VCVTPD2UQQ": {1, 0x79, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
"VCVTPS2QQ": {1, 0x7B, 0, 1, -1, vexRM, [3]int{8, 16, 32}},
|
"VCVTPS2QQ": {1, 0x7B, 0, 1, -1, vexRM, [3]int{8, 16, 32}},
|
||||||
"VCVTUDQ2PD": {1, 0x7A, 0, 2, -1, vexRM, [3]int{8, 16, 32}},
|
"VCVTUDQ2PD": {1, 0x7A, 0, 2, -1, vexRM, [3]int{8, 16, 32}},
|
||||||
"VCVTUDQ2PS": {1, 0x7A, 0, 0, -1, vexRM, [3]int{8, 16, 32}},
|
"VCVTUDQ2PS": {1, 0x7A, 0, 3, -1, vexRM, [3]int{8, 16, 32}},
|
||||||
// EVEX.66.0F38, half-precision convert (half-width source).
|
// EVEX.66.0F38, half-precision convert (half-width source).
|
||||||
"VCVTPH2PS": {2, 0x13, 0, 1, -1, vexRM, [3]int{8, 16, 32}},
|
"VCVTPH2PS": {2, 0x13, 0, 1, -1, vexRM, [3]int{8, 16, 32}},
|
||||||
// EVEX.66.0F3A, half-precision convert back ($imm, src, dst: reg=src,
|
// EVEX.66.0F3A, half-precision convert back ($imm, src, dst: reg=src,
|
||||||
@@ -494,6 +495,331 @@ var evexTable = map[string]evexSpec{
|
|||||||
// destination (VPMOVDW dword→word, VPMOVQD qword→dword).
|
// destination (VPMOVDW dword→word, VPMOVQD qword→dword).
|
||||||
"VPMOVDW": {2, 0x33, 0, 2, -1, vexRMRev, [3]int{8, 16, 32}},
|
"VPMOVDW": {2, 0x33, 0, 2, -1, vexRMRev, [3]int{8, 16, 32}},
|
||||||
"VPMOVQD": {2, 0x35, 0, 2, -1, vexRMRev, [3]int{8, 16, 32}},
|
"VPMOVQD": {2, 0x35, 0, 2, -1, vexRMRev, [3]int{8, 16, 32}},
|
||||||
|
|
||||||
|
// --- the AVX-512 families the avx512enc corpus exercises, read off
|
||||||
|
// the toolchain opcodetables ---
|
||||||
|
"VAESDEC": {2, 0xDE, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VAESDECLAST": {2, 0xDF, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VAESENC": {2, 0xDC, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VAESENCLAST": {2, 0xDD, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VALIGNQ": {3, 0x03, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VANDNPD": {1, 0x55, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VANDPD": {1, 0x54, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VBLENDMPD": {2, 0x65, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VBLENDMPS": {2, 0x65, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VBROADCASTF32X2": {2, 0x19, 0, 1, -1, vexRM, [3]int{0, 8, 8}},
|
||||||
|
"VBROADCASTF32X4": {2, 0x1A, 0, 1, -1, vexRM, [3]int{0, 16, 16}},
|
||||||
|
"VBROADCASTF32X8": {2, 0x1B, 0, 1, -1, vexRM, [3]int{0, 0, 32}},
|
||||||
|
"VBROADCASTF64X2": {2, 0x1A, 1, 1, -1, vexRM, [3]int{0, 16, 16}},
|
||||||
|
"VBROADCASTF64X4": {2, 0x1B, 1, 1, -1, vexRM, [3]int{0, 0, 32}},
|
||||||
|
"VBROADCASTI32X2": {2, 0x59, 0, 1, -1, vexRM, [3]int{8, 8, 8}},
|
||||||
|
"VBROADCASTI32X4": {2, 0x5A, 0, 1, -1, vexRM, [3]int{0, 16, 16}},
|
||||||
|
"VBROADCASTI32X8": {2, 0x5B, 0, 1, -1, vexRM, [3]int{0, 0, 32}},
|
||||||
|
"VBROADCASTI64X2": {2, 0x5A, 1, 1, -1, vexRM, [3]int{0, 16, 16}},
|
||||||
|
"VBROADCASTI64X4": {2, 0x5B, 1, 1, -1, vexRM, [3]int{0, 0, 32}},
|
||||||
|
"VCOMISD": {1, 0x2F, 1, 1, -1, vexRM, [3]int{8, 0, 0}},
|
||||||
|
"VCVTSD2SS": {1, 0x5A, 1, 3, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VCVTSS2SD": {1, 0x5A, 0, 2, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VDBPSADBW": {3, 0x42, 0, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VEXP2PD": {2, 0xC8, 1, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VEXP2PS": {2, 0xC8, 0, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VFMADD132PD": {2, 0x98, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADD132PS": {2, 0x98, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADD132SD": {2, 0x99, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMADD132SS": {2, 0x99, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMADD213PD": {2, 0xA8, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADD213PS": {2, 0xA8, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADD213SD": {2, 0xA9, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMADD213SS": {2, 0xA9, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMADD231PS": {2, 0xB8, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADD231SD": {2, 0xB9, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMADD231SS": {2, 0xB9, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMADDSUB132PD": {2, 0x96, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADDSUB132PS": {2, 0x96, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADDSUB213PD": {2, 0xA6, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADDSUB213PS": {2, 0xA6, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADDSUB231PD": {2, 0xB6, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMADDSUB231PS": {2, 0xB6, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB132PD": {2, 0x9A, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB132PS": {2, 0x9A, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB132SD": {2, 0x9B, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMSUB132SS": {2, 0x9B, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMSUB213PD": {2, 0xAA, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB213PS": {2, 0xAA, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB213SD": {2, 0xAB, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMSUB213SS": {2, 0xAB, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMSUB231PD": {2, 0xBA, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB231PS": {2, 0xBA, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUB231SD": {2, 0xBB, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFMSUB231SS": {2, 0xBB, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFMSUBADD132PD": {2, 0x97, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUBADD132PS": {2, 0x97, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUBADD213PD": {2, 0xA7, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUBADD213PS": {2, 0xA7, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUBADD231PD": {2, 0xB7, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFMSUBADD231PS": {2, 0xB7, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD132PD": {2, 0x9C, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD132PS": {2, 0x9C, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD132SD": {2, 0x9D, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMADD132SS": {2, 0x9D, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFNMADD213PD": {2, 0xAC, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD213PS": {2, 0xAC, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD213SD": {2, 0xAD, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMADD213SS": {2, 0xAD, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFNMADD231PD": {2, 0xBC, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD231PS": {2, 0xBC, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMADD231SD": {2, 0xBD, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMADD231SS": {2, 0xBD, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFNMSUB132PD": {2, 0x9E, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB132PS": {2, 0x9E, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB132SD": {2, 0x9F, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMSUB132SS": {2, 0x9F, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFNMSUB213PD": {2, 0xAE, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB213PS": {2, 0xAE, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB213SD": {2, 0xAF, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMSUB213SS": {2, 0xAF, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VFNMSUB231PD": {2, 0xBE, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB231PS": {2, 0xBE, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VFNMSUB231SD": {2, 0xBF, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VFNMSUB231SS": {2, 0xBF, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VGF2P8AFFINEINVQB": {3, 0xCF, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VGF2P8AFFINEQB": {3, 0xCE, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VGF2P8MULB": {2, 0xCF, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VMOVNTDQ": {1, 0xE7, 0, 1, -1, vexRMRev, [3]int{16, 32, 64}},
|
||||||
|
"VMOVNTDQA": {2, 0x2A, 0, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VMOVNTPD": {1, 0x2B, 1, 1, -1, vexRMRev, [3]int{16, 32, 64}},
|
||||||
|
"VORPD": {1, 0x56, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPADDSB": {1, 0xEC, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPADDSW": {1, 0xED, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPADDUSB": {1, 0xDC, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPADDUSW": {1, 0xDD, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPBLENDMB": {2, 0x66, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPBLENDMD": {2, 0x64, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPBLENDMQ": {2, 0x64, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPBLENDMW": {2, 0x66, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPBROADCASTMB2Q": {2, 0x2A, 1, 2, -1, vexRM, [3]int{0, 0, 0}},
|
||||||
|
"VPBROADCASTMW2D": {2, 0x3A, 0, 2, -1, vexRM, [3]int{0, 0, 0}},
|
||||||
|
"VPCLMULQDQ": {3, 0x44, 0, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPEQB": {1, 0x74, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPEQQ": {2, 0x29, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPEQW": {1, 0x75, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPGTB": {1, 0x64, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPGTD": {1, 0x66, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPGTQ": {2, 0x37, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCMPGTW": {1, 0x65, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPCOMPRESSB": {2, 0x63, 0, 1, -1, vexRMRev, [3]int{1, 1, 1}},
|
||||||
|
"VPCOMPRESSW": {2, 0x63, 1, 1, -1, vexRMRev, [3]int{2, 2, 2}},
|
||||||
|
"VPCONFLICTD": {2, 0xC4, 0, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VPCONFLICTQ": {2, 0xC4, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VPDPBUSD": {2, 0x50, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPDPBUSDS": {2, 0x51, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPDPWSSD": {2, 0x52, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPDPWSSDS": {2, 0x53, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMI2PD": {2, 0x77, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMI2PS": {2, 0x77, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMI2W": {2, 0x75, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMPS": {2, 0x16, 0, 1, -1, vexNDS3, [3]int{0, 32, 64}},
|
||||||
|
"VPERMT2B": {2, 0x7D, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMT2PS": {2, 0x7F, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMT2W": {2, 0x7D, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPEXPANDB": {2, 0x62, 0, 1, -1, vexRM, [3]int{1, 1, 1}},
|
||||||
|
"VPEXPANDW": {2, 0x62, 1, 1, -1, vexRM, [3]int{2, 2, 2}},
|
||||||
|
"VPINSRD": {3, 0x22, 0, 1, -1, vexNDS3Imm, [3]int{4, 0, 0}},
|
||||||
|
"VPINSRQ": {3, 0x22, 1, 1, -1, vexNDS3Imm, [3]int{8, 0, 0}},
|
||||||
|
"VPLZCNTD": {2, 0x44, 0, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VPLZCNTQ": {2, 0x44, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VPMADD52HUQ": {2, 0xB5, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMADD52LUQ": {2, 0xB4, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMULDQ": {2, 0x28, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMULHRSW": {2, 0x0B, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMULHW": {1, 0xE5, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMULTISHIFTQB": {2, 0x83, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPMULUDQ": {1, 0xF4, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPOPCNTW": {2, 0x54, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VPORD": {1, 0xEB, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPROLVD": {2, 0x15, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPROLVQ": {2, 0x15, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPRORVD": {2, 0x14, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPRORVQ": {2, 0x14, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSADBW": {1, 0xF6, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDD": {3, 0x71, 0, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDQ": {3, 0x71, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDVD": {2, 0x71, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDVQ": {2, 0x71, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDVW": {2, 0x70, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHLDW": {3, 0x70, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDD": {3, 0x73, 0, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDQ": {3, 0x73, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDVD": {2, 0x73, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDVQ": {2, 0x73, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDVW": {2, 0x72, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSHRDW": {3, 0x72, 1, 1, -1, vexNDS3Imm, [3]int{16, 32, 64}},
|
||||||
|
"VPSHUFBITQMB": {2, 0x8F, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSRAVW": {2, 0x11, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSRLD": {1, 0x72, 0, 1, 2, vexShiftImm, [3]int{16, 32, 64}},
|
||||||
|
"VPSRLDQ": {1, 0x73, 0, 1, 3, vexShiftImm, [3]int{16, 32, 64}},
|
||||||
|
// EVEX.66.0F73 /7, the byte-quad shift left (the count is always an
|
||||||
|
// immediate; there is no register-count twin).
|
||||||
|
"VPSLLDQ": {1, 0x73, 0, 1, 7, vexShiftImm, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
|
// EVEX.128/256/512.0F.W0, the plain-prefix (no 66) packed spellings
|
||||||
|
// whose EVEX form drops the legacy prefix entirely.
|
||||||
|
"VANDNPS": {1, 0x55, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VANDPS": {1, 0x54, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VORPS": {1, 0x56, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VXORPS": {1, 0x57, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VUNPCKLPS": {1, 0x14, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VUNPCKHPS": {1, 0x15, 0, 0, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VSQRTPS": {1, 0x51, 0, 0, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VCOMISS": {1, 0x2F, 0, 0, -1, vexRM, [3]int{4, 0, 0}},
|
||||||
|
"VUCOMISS": {1, 0x2E, 0, 0, -1, vexRM, [3]int{4, 0, 0}},
|
||||||
|
"VMOVNTPS": {1, 0x2B, 0, 0, -1, vexRMRev, [3]int{16, 32, 64}},
|
||||||
|
"VPSUBSB": {1, 0xE8, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSUBSW": {1, 0xE9, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSUBUSB": {1, 0xD8, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPSUBUSW": {1, 0xD9, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTMB": {2, 0x26, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTMD": {2, 0x27, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTMQ": {2, 0x27, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTMW": {2, 0x26, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTNMB": {2, 0x26, 0, 2, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTNMD": {2, 0x27, 0, 2, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTNMQ": {2, 0x27, 1, 2, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPTESTNMW": {2, 0x26, 1, 2, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKHBW": {1, 0x68, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKHQDQ": {1, 0x6D, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKHWD": {1, 0x69, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKLBW": {1, 0x60, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKLQDQ": {1, 0x6C, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPUNPCKLWD": {1, 0x61, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VRCP28PD": {2, 0xCA, 1, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VRCP28PS": {2, 0xCA, 0, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VRCP28SD": {2, 0xCB, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VRCP28SS": {2, 0xCB, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VRSQRT28PD": {2, 0xCC, 1, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VRSQRT28PS": {2, 0xCC, 0, 1, -1, vexRM, [3]int{0, 0, 64}},
|
||||||
|
"VRSQRT28SD": {2, 0xCD, 1, 1, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VRSQRT28SS": {2, 0xCD, 0, 1, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VSQRTPD": {1, 0x51, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||||
|
"VSQRTSD": {1, 0x51, 1, 3, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VSQRTSS": {1, 0x51, 0, 2, -1, vexNDS3, [3]int{4, 0, 0}},
|
||||||
|
"VUCOMISD": {1, 0x2E, 1, 1, -1, vexRM, [3]int{8, 0, 0}},
|
||||||
|
"VXORPD": {1, 0x57, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
|
// EVEX.128/256/512.0F.F3/F2.W0, word shuffles with an immediate
|
||||||
|
// ($imm, src, dst: reg = dst, rm = src, imm8). The F3/F2 prefixes
|
||||||
|
// split the high/low lane spellings.
|
||||||
|
"VPSHUFHW": {1, 0x70, 0, 2, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
"VPSHUFLW": {1, 0x70, 0, 3, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
|
// EVEX.128.66.0F3A, lane extract to a general-purpose register or
|
||||||
|
// memory ($imm, xsrc, GPR/mem dst: reg = source, rm = destination).
|
||||||
|
"VPEXTRB": {3, 0x14, 0, 1, -1, vexExtractGPR, [3]int{1, 1, 1}},
|
||||||
|
"VPEXTRW": {3, 0x15, 0, 1, -1, vexExtractGPR, [3]int{2, 2, 2}},
|
||||||
|
"VPEXTRD": {3, 0x16, 0, 1, -1, vexExtractGPR, [3]int{4, 4, 4}},
|
||||||
|
"VPEXTRQ": {3, 0x16, 1, 1, -1, vexExtractGPR, [3]int{8, 8, 8}},
|
||||||
|
|
||||||
|
// EVEX.66.0F3A.W1, the qword permutes with an immediate control
|
||||||
|
// ($imm, src, dst: reg = dst, rm = src, imm8); the register-count
|
||||||
|
// forms live in evexRegFormTable.
|
||||||
|
"VPERMQ": {3, 0x00, 1, 1, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
"VPERMPD": {3, 0x01, 1, 1, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
// EVEX.66.0F3A, the packed permute shuffles with an immediate control.
|
||||||
|
"VPERMILPS": {3, 0x04, 0, 1, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
"VPERMILPD": {3, 0x05, 1, 1, -1, vexImmRM, [3]int{16, 32, 64}},
|
||||||
|
|
||||||
|
// EVEX.128.0F.W0, high/low half moves. VMOVHPS carries the
|
||||||
|
// three-operand insert form (rm = m64 source, vvvv = preserved,
|
||||||
|
// reg = dst) and the two-operand store (reg = source, rm = m64);
|
||||||
|
// the encoder splits on the operand count. VMOVLHPS is the
|
||||||
|
// three-operand form alone.
|
||||||
|
"VMOVHPS": {1, 0x16, 0, 0, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
"VMOVLHPS": {1, 0x16, 0, 0, -1, vexNDS3, [3]int{8, 0, 0}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// evexQuad describes one quad-register instruction: the opcode under
|
||||||
|
// EVEX.0F38.W0 with the F2 mandatory prefix, and the width of the vector
|
||||||
|
// registers the bracketed list and the destination take (512-bit ZMM for
|
||||||
|
// the packed forms, 128-bit XMM for the scalar ones).
|
||||||
|
type evexQuad struct {
|
||||||
|
opcode byte
|
||||||
|
width int // register width in bytes: 64 (ZMM) or 16 (XMM)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evexQuadTable maps the quad-register instructions (the 4FMAPS and 4VNNIW
|
||||||
|
// families) to their encoding. The operand shape is fixed: a single memory
|
||||||
|
// source in r/m, the bracketed register list whose LOW register travels the
|
||||||
|
// inverted 5-bit V'VVVV field, an optional opmask in aaa and the vector
|
||||||
|
// destination in reg. The vector length follows the destination (512-bit
|
||||||
|
// for the ZMM list forms, 128-bit for the scalar ones) while the disp8×N
|
||||||
|
// multiplier stays 16 for every member, the toolchain's own tuple choice.
|
||||||
|
var evexQuadTable = map[string]evexQuad{
|
||||||
|
"V4FMADDPS": {0x9A, 64},
|
||||||
|
"V4FMADDSS": {0x9B, 16},
|
||||||
|
"V4FNMADDPS": {0xAA, 64},
|
||||||
|
"V4FNMADDSS": {0xAB, 16},
|
||||||
|
"VP4DPWSSD": {0x52, 64},
|
||||||
|
"VP4DPWSSDS": {0x53, 64},
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEvexQuad reports whether the mnemonic is a quad-register instruction.
|
||||||
|
func isEvexQuad(upper string) bool {
|
||||||
|
_, ok := evexQuadTable[upper]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeEvexQuad encodes the quad-register form: OP mem, [Zn-Zn+3], (K), dst.
|
||||||
|
// The register list is the VVVV-side source: its low register fills the
|
||||||
|
// inverted V'VVVV bits, which is why an indexed memory source above Z15 (no
|
||||||
|
// spare EVEX.X bit once V' is taken) is refused. Masking rides the standard
|
||||||
|
// aaa field, zeroing keeps the usual requires-a-mask rule, and no other
|
||||||
|
// suffix applies.
|
||||||
|
func (e *enc) encodeEvexQuad(mnem string, q evexQuad, ops []Operand, sfx evexSuffix) error {
|
||||||
|
if len(ops) != 3 && len(ops) != 4 {
|
||||||
|
return fmt.Errorf("%s expects 3 or 4 operands (mem, [Zn-Zn+3], (K), dst), got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
mem, lst := ops[0], ops[1]
|
||||||
|
dst := ops[len(ops)-1]
|
||||||
|
mask := 0
|
||||||
|
if len(ops) == 4 {
|
||||||
|
k, ok := ops[2].(Reg)
|
||||||
|
if !ok || !k.mask {
|
||||||
|
return fmt.Errorf("%s: third operand must be an opmask register", mnem)
|
||||||
|
}
|
||||||
|
if k.idx == 0 {
|
||||||
|
return fmt.Errorf("k0 is not a usable mask register")
|
||||||
|
}
|
||||||
|
mask = k.idx
|
||||||
|
}
|
||||||
|
list, ok := lst.(RegList)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: second operand must be a four-register list", mnem)
|
||||||
|
}
|
||||||
|
if list.Lo.size != q.width {
|
||||||
|
return fmt.Errorf("%s: the register list must hold %d-bit vector registers", mnem, q.width*8)
|
||||||
|
}
|
||||||
|
dstReg, ok := dst.(Reg)
|
||||||
|
if !ok || !dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s: destination must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
if dstReg.size != q.width {
|
||||||
|
return fmt.Errorf("%s: the destination must be a %d-bit vector register", mnem, q.width*8)
|
||||||
|
}
|
||||||
|
if !memOperand(mem) {
|
||||||
|
return fmt.Errorf("%s: the source must be a memory operand", mnem)
|
||||||
|
}
|
||||||
|
// The list owns V'VVVV; a scaled index in the EVEX-only half would fold
|
||||||
|
// its fifth bit into the same field the list's low register occupies.
|
||||||
|
if m, ok := mem.(Mem); ok && m.HasIndex && m.Index.idx >= 16 {
|
||||||
|
return fmt.Errorf("%s: an index register above Z15 has no EVEX bit free", mnem)
|
||||||
|
}
|
||||||
|
if sfx.zeroing && mask == 0 {
|
||||||
|
return fmt.Errorf("%s: zeroing (.Z) requires a mask register", mnem)
|
||||||
|
}
|
||||||
|
spec := evexSpec{mapSel: 2, opcode: q.opcode, w: 0, pp: 3, opdigit: -1, n: [3]int{16, 16, 16}}
|
||||||
|
// The vector length follows the destination (512-bit for the ZMM forms,
|
||||||
|
// 128-bit for the scalar ones), exactly as the oracle encodes it.
|
||||||
|
return e.emitEvexFields(spec, dstReg.vecLenBit(), dstReg.idx, list.Lo.idx, mem, mask, sfx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexBcastSpec describes an EVEX broadcast (VPBROADCASTD/Q): the opcode
|
// evexBcastSpec describes an EVEX broadcast (VPBROADCASTD/Q): the opcode
|
||||||
@@ -529,32 +855,42 @@ type evexMoveSpec struct {
|
|||||||
n [3]int
|
n [3]int
|
||||||
vecOK bool // the non-memory operand may be a vector register
|
vecOK bool // the non-memory operand may be a vector register
|
||||||
xmmOnly bool // wider than XMM registers are rejected
|
xmmOnly bool // wider than XMM registers are rejected
|
||||||
|
nds3 bool // a three-operand register form exists (VMOVSD/VMOVSS)
|
||||||
|
gprOK bool // the r/m side may be a general-purpose register (VMOVQ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexMoveTable maps an upper-case EVEX move mnemonic to its encoding.
|
// evexMoveTable maps an upper-case EVEX move mnemonic to its encoding.
|
||||||
var evexMoveTable = map[string]evexMoveSpec{
|
var evexMoveTable = map[string]evexMoveSpec{
|
||||||
// EVEX.128/256/512.F3.0F.W0, unaligned integer move.
|
// EVEX.128/256/512.F3.0F.W0, unaligned integer move.
|
||||||
"VMOVDQU32": {1, 2, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false},
|
"VMOVDQU32": {1, 2, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F3.0F.W1, unaligned qword move.
|
// EVEX.128/256/512.F3.0F.W1, unaligned qword move.
|
||||||
"VMOVDQU64": {1, 2, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false},
|
"VMOVDQU64": {1, 2, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F2.0F.W0, unaligned byte move (byte/word moves use the
|
// EVEX.128/256/512.F2.0F.W0, unaligned byte move (byte/word moves use the
|
||||||
// F2 prefix, dword/qword moves F3; the element size only changes the tuple
|
// F2 prefix, dword/qword moves F3; the element size only changes the tuple
|
||||||
// semantics).
|
// semantics).
|
||||||
"VMOVDQU8": {1, 3, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false},
|
"VMOVDQU8": {1, 3, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F2.0F.W1, unaligned word move (shares the qword
|
// EVEX.128/256/512.F2.0F.W1, unaligned word move (shares the qword
|
||||||
// encoding).
|
// encoding).
|
||||||
"VMOVDQU16": {1, 3, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false},
|
"VMOVDQU16": {1, 3, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.66.0F.W1, unaligned packed double move.
|
// EVEX.128/256/512.66.0F.W1, unaligned packed double move.
|
||||||
"VMOVUPD": {1, 1, 0x10, 0x11, 1, [3]int{16, 32, 64}, true, false},
|
"VMOVUPD": {1, 1, 0x10, 0x11, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512, aligned packed moves.
|
// EVEX.128/256/512, aligned packed moves.
|
||||||
"VMOVAPS": {1, 0, 0x28, 0x29, 0, [3]int{16, 32, 64}, true, false},
|
"VMOVAPS": {1, 0, 0x28, 0x29, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
"VMOVAPD": {1, 1, 0x28, 0x29, 1, [3]int{16, 32, 64}, true, false},
|
"VMOVAPD": {1, 1, 0x28, 0x29, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.66.0F, aligned integer moves.
|
// EVEX.128/256/512.66.0F, aligned integer moves.
|
||||||
"VMOVDQA32": {1, 1, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false},
|
"VMOVDQA32": {1, 1, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
"VMOVDQA64": {1, 1, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false},
|
"VMOVDQA64": {1, 1, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128.F3.0F.W0, scalar single move, memory operands (the
|
// EVEX.128.F3.0F.W0, scalar single move, memory operands (the
|
||||||
// three-operand register form is not supported).
|
// three-operand register form is not supported).
|
||||||
"VMOVSS": {1, 2, 0x10, 0x11, 0, [3]int{4, 4, 4}, false, true},
|
"VMOVSS": {1, 2, 0x10, 0x11, 0, [3]int{4, 4, 4}, false, true, true, false},
|
||||||
|
// EVEX.128.F2.0F.W1, scalar double move: memory operands and the
|
||||||
|
// three-operand register form (VMOVSD dst, src1, src2).
|
||||||
|
"VMOVSD": {1, 3, 0x10, 0x11, 1, [3]int{8, 8, 8}, false, true, true, false},
|
||||||
|
// EVEX.128/256/512.0F.W0, unaligned packed single move.
|
||||||
|
"VMOVUPS": {1, 0, 0x10, 0x11, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
|
// EVEX.128.66.0F.W1, the 64-bit GPR/memory ↔ XMM move (VMOVQ RSP, X20
|
||||||
|
// and friends, the EVEX spelling the high registers demand).
|
||||||
|
"VMOVQ": {1, 1, 0x6E, 0x7E, 1, [3]int{8, 8, 8}, true, true, false, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// isEvex reports whether the mnemonic has an EVEX encoding we handle.
|
// isEvex reports whether the mnemonic has an EVEX encoding we handle.
|
||||||
@@ -565,8 +901,13 @@ func isEvex(mnemUpper string) bool {
|
|||||||
if _, ok := evexBcastTable[mnemUpper]; ok {
|
if _, ok := evexBcastTable[mnemUpper]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, ok := evexMoveTable[mnemUpper]
|
if _, ok := evexMoveTable[mnemUpper]; ok {
|
||||||
return ok
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := evexHptrTable[mnemUpper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return isEvexQuad(mnemUpper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexRequired reports whether the operands force the EVEX encoding of a
|
// evexRequired reports whether the operands force the EVEX encoding of a
|
||||||
@@ -577,8 +918,21 @@ func evexRequired(upper string, ops []Operand) bool {
|
|||||||
_, inVex := vexTable[upper]
|
_, inVex := vexTable[upper]
|
||||||
_, inVexMove := vexMoveTable[upper]
|
_, inVexMove := vexMoveTable[upper]
|
||||||
if !inVex && !inVexMove {
|
if !inVex && !inVexMove {
|
||||||
|
// The dual-shape moves pick their VEX form by operand count, so
|
||||||
|
// they are not EVEX-only either.
|
||||||
|
switch upper {
|
||||||
|
case "VMOVHPD", "VMOVLPD", "VMOVHPS", "VMOVLPS":
|
||||||
|
default:
|
||||||
return true // EVEX-only mnemonic
|
return true // EVEX-only mnemonic
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// The byte-quad shifts have VEX register forms but EVEX-only memory
|
||||||
|
// forms: a memory count source forces the EVEX encoding.
|
||||||
|
if upper == "VPSLLDQ" || upper == "VPSRLDQ" {
|
||||||
|
if slices.ContainsFunc(ops, memOperand) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
if r, ok := op.(Reg); ok && (r.size == 64 || r.mask || (r.isVec() && r.idx >= 16)) {
|
if r, ok := op.(Reg); ok && (r.size == 64 || r.mask || (r.isVec() && r.idx >= 16)) {
|
||||||
return true
|
return true
|
||||||
@@ -677,6 +1031,8 @@ var evexRound = map[string]bool{
|
|||||||
"VCVTTSD2USIL": true, "VCVTTSD2USIQ": true, "VCVTTSS2USIL": true, "VCVTTSS2USIQ": true,
|
"VCVTTSD2USIL": true, "VCVTTSD2USIQ": true, "VCVTTSS2USIL": true, "VCVTTSS2USIQ": true,
|
||||||
"VCVTSI2SDQ": true, "VCVTSI2SSL": true, "VCVTSI2SSQ": true,
|
"VCVTSI2SDQ": true, "VCVTSI2SSL": true, "VCVTSI2SSQ": true,
|
||||||
"VCVTUSI2SDQ": true, "VCVTUSI2SSL": true, "VCVTUSI2SSQ": true,
|
"VCVTUSI2SDQ": true, "VCVTUSI2SSL": true, "VCVTUSI2SSQ": true,
|
||||||
|
// The scalar compares suppress exceptions on their LIG encoding.
|
||||||
|
"VCMPSD": true, "VCMPSS": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexBcstN maps an instruction accepting .BCST to the broadcast element
|
// evexBcstN maps an instruction accepting .BCST to the broadcast element
|
||||||
@@ -741,6 +1097,22 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
|||||||
return e.encodeEvexRM(spec, ops, 0, sfx)
|
return e.encodeEvexRM(spec, ops, 0, sfx)
|
||||||
}
|
}
|
||||||
spec, inTable := evexTable[mnemUpper]
|
spec, inTable := evexTable[mnemUpper]
|
||||||
|
// A high/low half move that lives in the hptr table alone (the packed
|
||||||
|
// double twins) reaches the same inTable block below, which completes
|
||||||
|
// its spec from the hptr entry.
|
||||||
|
if !inTable {
|
||||||
|
if _, ok := evexHptrTable[mnemUpper]; ok {
|
||||||
|
inTable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if q, ok := evexQuadTable[mnemUpper]; ok {
|
||||||
|
// The quad-register family carries no rounding, SAE or broadcast;
|
||||||
|
// only masking and zeroing apply.
|
||||||
|
if sfx.sae || sfx.bcst || sfx.rounding >= 0 {
|
||||||
|
return fmt.Errorf("%s takes no rounding/SAE/broadcast suffix", mnemUpper)
|
||||||
|
}
|
||||||
|
return e.encodeEvexQuad(mnemUpper, q, ops, sfx)
|
||||||
|
}
|
||||||
if inTable {
|
if inTable {
|
||||||
if (sfx.rounding >= 0 || sfx.sae) && !evexRound[mnemUpper] {
|
if (sfx.rounding >= 0 || sfx.sae) && !evexRound[mnemUpper] {
|
||||||
return fmt.Errorf("%s: rounding/SAE is not supported for this instruction", mnemUpper)
|
return fmt.Errorf("%s: rounding/SAE is not supported for this instruction", mnemUpper)
|
||||||
@@ -752,6 +1124,27 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
|||||||
}
|
}
|
||||||
spec.n = [3]int{n, n, n}
|
spec.n = [3]int{n, n, n}
|
||||||
}
|
}
|
||||||
|
// A mnemonic with an immediate and a register spelling (the
|
||||||
|
// variable-count shifts, the permutes) encodes the register one
|
||||||
|
// when the first operand is not an immediate.
|
||||||
|
if len(ops) > 0 {
|
||||||
|
if _, isImm := ops[0].(Imm); !isImm {
|
||||||
|
if alt, ok := evexRegFormTable[mnemUpper]; ok {
|
||||||
|
spec, inTable = alt, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The high/low half moves split by operand count: three operands
|
||||||
|
// insert, two store (VMOVHPS m64, X1).
|
||||||
|
if hs, ok := evexHptrTable[mnemUpper]; ok {
|
||||||
|
if len(ops) == 2 {
|
||||||
|
if hs.store.opcode == 0 {
|
||||||
|
return fmt.Errorf("%s has no two-operand form", mnemUpper)
|
||||||
|
}
|
||||||
|
return e.encodeEvexRMRev(hs.store, ops, 0, sfx)
|
||||||
|
}
|
||||||
|
spec = hs.insert
|
||||||
|
}
|
||||||
} else if sfx.evexOnly() {
|
} else if sfx.evexOnly() {
|
||||||
return fmt.Errorf("%s: the instruction does not take rounding/SAE/broadcast suffixes", mnemUpper)
|
return fmt.Errorf("%s: the instruction does not take rounding/SAE/broadcast suffixes", mnemUpper)
|
||||||
}
|
}
|
||||||
@@ -811,6 +1204,12 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
|||||||
}
|
}
|
||||||
return e.encodeEvexMove(mnemUpper, ms, ops, mask, sfx)
|
return e.encodeEvexMove(mnemUpper, ms, ops, mask, sfx)
|
||||||
}
|
}
|
||||||
|
if ps, ok := evexPrefGatherTable[mnemUpper]; ok {
|
||||||
|
if sfx.any() {
|
||||||
|
return fmt.Errorf("%s takes no EVEX suffixes", mnemUpper)
|
||||||
|
}
|
||||||
|
return e.encodeEvexPrefGather(mnemUpper, ps, ops, mask, sfx)
|
||||||
|
}
|
||||||
if !inTable {
|
if !inTable {
|
||||||
return fmt.Errorf("unsupported instruction %q for ZMM/K operands", mnemUpper)
|
return fmt.Errorf("unsupported instruction %q for ZMM/K operands", mnemUpper)
|
||||||
}
|
}
|
||||||
@@ -829,6 +1228,8 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
|||||||
return e.encodeEvexNDS3Imm(spec, ops, mask, sfx)
|
return e.encodeEvexNDS3Imm(spec, ops, mask, sfx)
|
||||||
case vexExtract:
|
case vexExtract:
|
||||||
return e.encodeEvexExtract(spec, ops, mask, sfx)
|
return e.encodeEvexExtract(spec, ops, mask, sfx)
|
||||||
|
case vexExtractGPR:
|
||||||
|
return e.encodeEvexExtractGPR(spec, ops, mask, sfx)
|
||||||
case vexRMSrcLen:
|
case vexRMSrcLen:
|
||||||
return e.encodeEvexRMSrcLen(spec, ops, mask, sfx)
|
return e.encodeEvexRMSrcLen(spec, ops, mask, sfx)
|
||||||
}
|
}
|
||||||
@@ -908,6 +1309,11 @@ func (e *enc) encodeEvexImmRM(spec evexSpec, ops []Operand, mask int, sfx evexSu
|
|||||||
if dstReg.mask {
|
if dstReg.mask {
|
||||||
if r, ok := src.(Reg); ok && r.isVec() {
|
if r, ok := src.(Reg); ok && r.isVec() {
|
||||||
ll = r.vecLenBit()
|
ll = r.vecLenBit()
|
||||||
|
} else if l, err := soleLen(spec.n); err == nil {
|
||||||
|
// A memory source with a length-fixed mnemonic
|
||||||
|
// (VFPCLASSPDX/Y/Z): the length comes from the table's
|
||||||
|
// single valid slot, not from the operand.
|
||||||
|
ll = l
|
||||||
}
|
}
|
||||||
} else if r, ok := src.(Reg); ok && r.isVec() {
|
} else if r, ok := src.(Reg); ok && r.isVec() {
|
||||||
ll = r.vecLenBit()
|
ll = r.vecLenBit()
|
||||||
@@ -934,9 +1340,11 @@ func (e *enc) encodeEvexShiftImm(spec evexSpec, ops []Operand, mask int, sfx eve
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("shift count must be an immediate")
|
return fmt.Errorf("shift count must be an immediate")
|
||||||
}
|
}
|
||||||
srcReg, ok := src.(Reg)
|
// The count source is a vector register or memory; the length the L'L
|
||||||
if !ok || !srcReg.isVec() {
|
// field and the disp8×N multiplier follow is the destination's either
|
||||||
return fmt.Errorf("shift source must be a vector register")
|
// way.
|
||||||
|
if !vecOrMem(src) {
|
||||||
|
return fmt.Errorf("shift source must be a vector register or memory")
|
||||||
}
|
}
|
||||||
dstReg, ok := dst.(Reg)
|
dstReg, ok := dst.(Reg)
|
||||||
if !ok || !dstReg.isVec() {
|
if !ok || !dstReg.isVec() {
|
||||||
@@ -946,7 +1354,7 @@ func (e *enc) encodeEvexShiftImm(spec evexSpec, ops []Operand, mask int, sfx eve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := e.emitEvexFields(spec, dstReg.vecLenBit(), spec.opdigit, dstReg.idx, srcReg, mask, sfx); err != nil {
|
if err := e.emitEvexFields(spec, dstReg.vecLenBit(), spec.opdigit, dstReg.idx, src, mask, sfx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e.out = append(e.out, immByte)
|
e.out = append(e.out, immByte)
|
||||||
@@ -1018,10 +1426,90 @@ func (e *enc) encodeEvexExtract(spec evexSpec, ops []Operand, mask int, sfx evex
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeEvexExtractGPR encodes the lane extract to a general-purpose
|
||||||
|
// register or memory: OP $imm, xsrc, dst (reg = the XMM source, rm = the
|
||||||
|
// destination, imm8). The encoding is 128-bit regardless of register
|
||||||
|
// numbers, so L'L is fixed at 0 and the disp8×N multiplier is the extracted
|
||||||
|
// element size the table carries.
|
||||||
|
func (e *enc) encodeEvexExtractGPR(spec evexSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return fmt.Errorf("extract expects 3 operands ($imm, xsrc, dst), got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, src, dst := ops[0], ops[1], ops[2]
|
||||||
|
immVal, ok := imm.(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("extract lane must be an immediate")
|
||||||
|
}
|
||||||
|
srcReg, ok := src.(Reg)
|
||||||
|
if !ok || !srcReg.isVec() {
|
||||||
|
return fmt.Errorf("extract source must be a vector register")
|
||||||
|
}
|
||||||
|
switch dst.(type) {
|
||||||
|
case Reg:
|
||||||
|
if dst.(Reg).isVec() {
|
||||||
|
return fmt.Errorf("extract destination must be a general-purpose register or memory")
|
||||||
|
}
|
||||||
|
case Mem, sbMem:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("extract destination must be a general-purpose register or memory")
|
||||||
|
}
|
||||||
|
immByte, err := imm8(int64(immVal))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := e.emitEvexFields(spec, 0, srcReg.idx, -1, dst, mask, sfx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.out = append(e.out, immByte)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// encodeEvexMove encodes a two-operand EVEX move; a vector→vector move uses
|
// encodeEvexMove encodes a two-operand EVEX move; a vector→vector move uses
|
||||||
// the store-form opcode (reg = source, rm = destination), matching the Go
|
// the store-form opcode (reg = source, rm = destination), matching the Go
|
||||||
// assembler.
|
// assembler. The scalar moves also carry a three-operand register form
|
||||||
|
// (VMOVSD dst, src1, src2: the load opcode with vvvv = src1), which ms.nds3
|
||||||
|
// opens.
|
||||||
|
// validEvexMoveOther reports whether the non-vector side of an EVEX move may
|
||||||
|
// take the operand: memory always, a general-purpose register when gprOK.
|
||||||
|
func validEvexMoveOther(ms evexMoveSpec, op Operand) bool {
|
||||||
|
if memOperand(op) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !ms.gprOK {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r, ok := op.(Reg)
|
||||||
|
return ok && !r.isVec() && !r.mask && r.ctl == 0 && !r.mmx && !r.fp
|
||||||
|
}
|
||||||
|
|
||||||
func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
||||||
|
if len(ops) == 3 {
|
||||||
|
if !ms.nds3 {
|
||||||
|
return fmt.Errorf("EVEX move expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
// The masked scalar register form keeps the Go assembler's own
|
||||||
|
// layout: the store opcode with reg = op0, vvvv = op1 and the
|
||||||
|
// destination in r/m (op2), the bytes go tool asm emits, not
|
||||||
|
// the manual's NDS reading.
|
||||||
|
src, src1, dst := ops[0], ops[1], ops[2]
|
||||||
|
reg, ok := src.(Reg)
|
||||||
|
if !ok || !reg.isVec() {
|
||||||
|
return fmt.Errorf("%s: first operand must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
vvvvReg, ok := src1.(Reg)
|
||||||
|
if !ok || !vvvvReg.isVec() {
|
||||||
|
return fmt.Errorf("%s: second operand must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
dstReg, ok := dst.(Reg)
|
||||||
|
if !ok || !dstReg.isVec() {
|
||||||
|
return fmt.Errorf("%s: destination must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
if ms.xmmOnly && (reg.size != 16 || vvvvReg.size != 16 || dstReg.size != 16) {
|
||||||
|
return fmt.Errorf("%s operates on XMM registers only", mnem)
|
||||||
|
}
|
||||||
|
spec := evexSpec{mapSel: ms.mapSel, opcode: ms.store, w: ms.w, pp: ms.pp, opdigit: -1, n: ms.n}
|
||||||
|
return e.emitEvexFields(spec, dstReg.vecLenBit(), reg.idx, vvvvReg.idx, dst, mask, sfx)
|
||||||
|
}
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return fmt.Errorf("EVEX move expects 2 operands, got %d", len(ops))
|
return fmt.Errorf("EVEX move expects 2 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
@@ -1042,12 +1530,12 @@ func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask i
|
|||||||
}
|
}
|
||||||
reg, rm = srcReg, dst
|
reg, rm = srcReg, dst
|
||||||
case srcIsVec:
|
case srcIsVec:
|
||||||
if !memOperand(dst) {
|
if !validEvexMoveOther(ms, dst) {
|
||||||
return fmt.Errorf("%s: invalid destination operand", mnem)
|
return fmt.Errorf("%s: invalid destination operand", mnem)
|
||||||
}
|
}
|
||||||
reg, rm = srcReg, dst
|
reg, rm = srcReg, dst
|
||||||
case dstIsVec:
|
case dstIsVec:
|
||||||
if !memOperand(src) {
|
if !validEvexMoveOther(ms, src) {
|
||||||
return fmt.Errorf("%s: invalid source operand", mnem)
|
return fmt.Errorf("%s: invalid source operand", mnem)
|
||||||
}
|
}
|
||||||
op = ms.load
|
op = ms.load
|
||||||
@@ -1141,12 +1629,20 @@ func (e *enc) encodeEvexBcast(bs evexBcastSpec, ops []Operand, mask int, sfx eve
|
|||||||
return fmt.Errorf("broadcast destination must be a vector register")
|
return fmt.Errorf("broadcast destination must be a vector register")
|
||||||
}
|
}
|
||||||
spec := evexSpec{mapSel: bs.mapSel, w: bs.w, pp: 1, opdigit: -1}
|
spec := evexSpec{mapSel: bs.mapSel, w: bs.w, pp: 1, opdigit: -1}
|
||||||
switch src.(type) {
|
switch r := src.(type) {
|
||||||
case Mem, sbMem:
|
case Mem, sbMem:
|
||||||
spec.opcode = bs.opMem
|
spec.opcode = bs.opMem
|
||||||
spec.n = [3]int{bs.n, bs.n, bs.n}
|
spec.n = [3]int{bs.n, bs.n, bs.n}
|
||||||
case Reg:
|
case Reg:
|
||||||
|
// A GPR source uses the register broadcast opcode; a vector
|
||||||
|
// source shares the xmm/mem one (the low byte is copied from
|
||||||
|
// the lane or from the memory operand).
|
||||||
|
if r.isVec() {
|
||||||
|
spec.opcode = bs.opMem
|
||||||
|
spec.n = [3]int{bs.n, bs.n, bs.n}
|
||||||
|
} else {
|
||||||
spec.opcode = bs.opReg
|
spec.opcode = bs.opReg
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("broadcast source must be a register or memory")
|
return fmt.Errorf("broadcast source must be a register or memory")
|
||||||
}
|
}
|
||||||
@@ -1349,6 +1845,111 @@ func isScatter(upper string) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isEvexPrefGather reports whether the mnemonic is a gather/scatter
|
||||||
|
// prefetch hint.
|
||||||
|
func isEvexPrefGather(upper string) bool {
|
||||||
|
_, ok := evexPrefGatherTable[upper]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// evexRegFormTable holds the register-count twin of the immediate-form
|
||||||
|
// entries in evexTable. Several mnemonics name two encodings: an immediate
|
||||||
|
// count or control ($imm, src, dst …) and a register-count one whose second
|
||||||
|
// operand is a vector register or memory (count, src2, src1, dst). The
|
||||||
|
// immediate spelling lives in evexTable, this table carries the register
|
||||||
|
// spelling, and encodeEvex picks by whether the first operand is an
|
||||||
|
// immediate, the way vexVarShift does on the VEX side.
|
||||||
|
var evexRegFormTable = map[string]evexSpec{
|
||||||
|
"VPSLLD": {1, 0xF2, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSLLQ": {1, 0xF3, 1, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSLLW": {1, 0xF1, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRAD": {1, 0xE2, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRAQ": {1, 0xE2, 1, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRAW": {1, 0xE1, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRLD": {1, 0xD2, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRLQ": {1, 0xD3, 1, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
"VPSRLW": {1, 0xD1, 0, 1, -1, vexNDS3, [3]int{16, 16, 16}},
|
||||||
|
// EVEX.NDS.0F38.W1, the register-count permutes (the immediate
|
||||||
|
// controls live in evexTable under 0F3A).
|
||||||
|
"VPERMQ": {2, 0x36, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMPD": {2, 0x16, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
// EVEX.NDS.0F38, the register-count permil shuffles.
|
||||||
|
"VPERMILPS": {2, 0x0C, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
"VPERMILPD": {2, 0x0D, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// evexPrefGatherSpec describes a gather/scatter prefetch hint: one memory
|
||||||
|
// operand with a VSIB index and an opmask register, no destination. The
|
||||||
|
// ModRM.reg field carries a fixed /digit, the L'L field is fixed at 512, and
|
||||||
|
// the mask register is the instruction's only register operand.
|
||||||
|
type evexPrefGatherSpec struct {
|
||||||
|
mapSel int
|
||||||
|
opcode byte
|
||||||
|
w int
|
||||||
|
pp int
|
||||||
|
opdigit int
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
var evexPrefGatherTable = map[string]evexPrefGatherSpec{
|
||||||
|
"VGATHERPF0DPD": {2, 0xC6, 1, 1, 1, 8},
|
||||||
|
"VGATHERPF0DPS": {2, 0xC6, 0, 1, 1, 4},
|
||||||
|
"VGATHERPF0QPD": {2, 0xC7, 1, 1, 1, 8},
|
||||||
|
"VGATHERPF0QPS": {2, 0xC7, 0, 1, 1, 4},
|
||||||
|
"VGATHERPF1DPD": {2, 0xC6, 1, 1, 2, 8},
|
||||||
|
"VGATHERPF1DPS": {2, 0xC6, 0, 1, 2, 4},
|
||||||
|
"VGATHERPF1QPD": {2, 0xC7, 1, 1, 2, 8},
|
||||||
|
"VGATHERPF1QPS": {2, 0xC7, 0, 1, 2, 4},
|
||||||
|
"VSCATTERPF0DPD": {2, 0xC6, 1, 1, 5, 8},
|
||||||
|
"VSCATTERPF0DPS": {2, 0xC6, 0, 1, 5, 4},
|
||||||
|
"VSCATTERPF0QPD": {2, 0xC7, 1, 1, 5, 8},
|
||||||
|
"VSCATTERPF0QPS": {2, 0xC7, 0, 1, 5, 4},
|
||||||
|
"VSCATTERPF1DPD": {2, 0xC6, 1, 1, 6, 8},
|
||||||
|
"VSCATTERPF1DPS": {2, 0xC6, 0, 1, 6, 4},
|
||||||
|
"VSCATTERPF1QPD": {2, 0xC7, 1, 1, 6, 8},
|
||||||
|
"VSCATTERPF1QPS": {2, 0xC7, 0, 1, 6, 4},
|
||||||
|
}
|
||||||
|
|
||||||
|
// evexHptrSpec describes the high/low half moves (VMOVHPS family): the
|
||||||
|
// three-operand insert shares an opcode with a two-operand store whose
|
||||||
|
// source is the vector register and whose destination is m64.
|
||||||
|
type evexHptrSpec struct {
|
||||||
|
insert evexSpec
|
||||||
|
store evexSpec // store.opcode == 0 when the mnemonic has no store form
|
||||||
|
}
|
||||||
|
|
||||||
|
var evexHptrTable = map[string]evexHptrSpec{
|
||||||
|
"VMOVHPS": {
|
||||||
|
insert: evexSpec{mapSel: 1, opcode: 0x16, w: 0, pp: 0, opdigit: -1, form: vexNDS3, n: [3]int{8, 0, 0}},
|
||||||
|
store: evexSpec{mapSel: 1, opcode: 0x17, w: 0, pp: 0, opdigit: -1, form: vexRMRev, n: [3]int{8, 0, 0}},
|
||||||
|
},
|
||||||
|
"VMOVLHPS": {
|
||||||
|
insert: evexSpec{mapSel: 1, opcode: 0x16, w: 0, pp: 0, opdigit: -1, form: vexNDS3, n: [3]int{8, 0, 0}},
|
||||||
|
},
|
||||||
|
// The packed-double twins, 66-prefixed.
|
||||||
|
"VMOVHPD": {
|
||||||
|
insert: evexSpec{mapSel: 1, opcode: 0x16, w: 1, pp: 1, opdigit: -1, form: vexNDS3, n: [3]int{8, 0, 0}},
|
||||||
|
store: evexSpec{mapSel: 1, opcode: 0x17, w: 1, pp: 1, opdigit: -1, form: vexRMRev, n: [3]int{8, 0, 0}},
|
||||||
|
},
|
||||||
|
"VMOVLPD": {
|
||||||
|
insert: evexSpec{mapSel: 1, opcode: 0x12, w: 1, pp: 1, opdigit: -1, form: vexNDS3, n: [3]int{8, 0, 0}},
|
||||||
|
store: evexSpec{mapSel: 1, opcode: 0x13, w: 1, pp: 1, opdigit: -1, form: vexRMRev, n: [3]int{8, 0, 0}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeEvexPrefGather encodes a gather/scatter prefetch hint: OP K, vsib.
|
||||||
|
func (e *enc) encodeEvexPrefGather(upper string, ps evexPrefGatherSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands (K, vsib memory), got %d", upper, len(ops)+1)
|
||||||
|
}
|
||||||
|
m, ok := ops[0].(Mem)
|
||||||
|
if !ok || !m.HasIndex || !m.Index.isVec() {
|
||||||
|
return fmt.Errorf("%s: operand must be a VSIB memory reference with a vector index", upper)
|
||||||
|
}
|
||||||
|
spec := evexSpec{mapSel: ps.mapSel, opcode: ps.opcode, w: ps.w, pp: ps.pp, opdigit: ps.opdigit, n: [3]int{ps.n, ps.n, ps.n}}
|
||||||
|
return e.emitEvexFields(spec, 2, ps.opdigit, -1, m, mask, sfx)
|
||||||
|
}
|
||||||
|
|
||||||
// vsibLen validates a VSIB memory operand (the index must be a vector
|
// vsibLen validates a VSIB memory operand (the index must be a vector
|
||||||
// register) and returns it with the vector length the index selects, the
|
// register) and returns it with the vector length the index selects, the
|
||||||
// EVEX L'L field follows the index register, not the data register.
|
// EVEX L'L field follows the index register, not the data register.
|
||||||
@@ -1370,7 +1971,9 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mask != 0 || sfx.any() {
|
if mask != 0 || sfx.any() {
|
||||||
// EVEX form: OP vsib, K, dst.
|
// EVEX form: OP vsib, K, dst. The L'L field is the wider of the
|
||||||
|
// index and the data register lengths (the Go assembler's
|
||||||
|
// layout); the disp8×N multiplier stays the index element size.
|
||||||
if len(rest) != 2 {
|
if len(rest) != 2 {
|
||||||
return fmt.Errorf("%s expects 3 operands (vsib, K, dst), got %d", upper, len(ops))
|
return fmt.Errorf("%s expects 3 operands (vsib, K, dst), got %d", upper, len(ops))
|
||||||
}
|
}
|
||||||
@@ -1382,6 +1985,9 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
if !ok || !dst.isVec() {
|
if !ok || !dst.isVec() {
|
||||||
return fmt.Errorf("%s: destination must be a vector register", upper)
|
return fmt.Errorf("%s: destination must be a vector register", upper)
|
||||||
}
|
}
|
||||||
|
if d := dst.vecLenBit(); d > ll {
|
||||||
|
ll = d
|
||||||
|
}
|
||||||
evex := evexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1, n: [3]int{gs.n, gs.n, gs.n}}
|
evex := evexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1, n: [3]int{gs.n, gs.n, gs.n}}
|
||||||
return e.emitEvexFields(evex, ll, dst.idx, -1, vsib, mask, sfx)
|
return e.emitEvexFields(evex, ll, dst.idx, -1, vsib, mask, sfx)
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1999,7 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
if !ok || !maskReg.isVec() {
|
if !ok || !maskReg.isVec() {
|
||||||
return fmt.Errorf("%s: mask must be a vector register", upper)
|
return fmt.Errorf("%s: mask must be a vector register", upper)
|
||||||
}
|
}
|
||||||
vsib, _, err := vsibLen(rest[1], upper)
|
vsib, idxLen, err := vsibLen(rest[1], upper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1401,12 +2007,16 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
if !ok || !dst.isVec() {
|
if !ok || !dst.isVec() {
|
||||||
return fmt.Errorf("%s: destination must be a vector register", upper)
|
return fmt.Errorf("%s: destination must be a vector register", upper)
|
||||||
}
|
}
|
||||||
|
// The L bit is the wider of the data register and the VSIB index
|
||||||
|
// lengths (a YMM index under an XMM destination selects 256-bit, the
|
||||||
|
// bytes go tool asm emits).
|
||||||
|
ll := max(idxLen, dst.vecLenBit())
|
||||||
spec := vexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1}
|
spec := vexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1}
|
||||||
rBit := 0
|
rBit := 0
|
||||||
if dst.idx >= 8 {
|
if dst.idx >= 8 {
|
||||||
rBit = 1
|
rBit = 1
|
||||||
}
|
}
|
||||||
return e.emitVexFields(spec, dst.vecLenBit(), dst.idx&7, rBit, 15-maskReg.idx, vsib)
|
return e.emitVexFields(spec, ll, dst.idx&7, rBit, 15-maskReg.idx, vsib)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeScatter encodes a scatter (EVEX only): OP src, K, vsib, reg = src,
|
// encodeScatter encodes a scatter (EVEX only): OP src, K, vsib, reg = src,
|
||||||
@@ -1431,6 +2041,11 @@ func (e *enc) encodeScatter(upper string, ss gatherSpec, ops []Operand, sfx evex
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// The L'L field is the wider of the data register and the VSIB index
|
||||||
|
// lengths, the bytes go tool asm emits.
|
||||||
|
if d := src.vecLenBit(); d > ll {
|
||||||
|
ll = d
|
||||||
|
}
|
||||||
evex := evexSpec{mapSel: 2, opcode: ss.opcode, w: ss.w, pp: 1, opdigit: -1, n: [3]int{ss.n, ss.n, ss.n}}
|
evex := evexSpec{mapSel: 2, opcode: ss.opcode, w: ss.w, pp: 1, opdigit: -1, n: [3]int{ss.n, ss.n, ss.n}}
|
||||||
return e.emitEvexFields(evex, ll, src.idx, -1, vsib, mask, sfx)
|
return e.emitEvexFields(evex, ll, src.idx, -1, vsib, mask, sfx)
|
||||||
}
|
}
|
||||||
@@ -1441,6 +2056,8 @@ func (e *enc) encodeScatter(upper string, ss gatherSpec, ops []Operand, sfx evex
|
|||||||
var evexKOperand = map[string]bool{
|
var evexKOperand = map[string]bool{
|
||||||
"VPMOVM2B": true, "VPMOVM2W": true, "VPMOVM2D": true, "VPMOVM2Q": true,
|
"VPMOVM2B": true, "VPMOVM2W": true, "VPMOVM2D": true, "VPMOVM2Q": true,
|
||||||
"VPMOVB2M": true, "VPMOVW2M": true, "VPMOVD2M": true, "VPMOVQ2M": true,
|
"VPMOVB2M": true, "VPMOVW2M": true, "VPMOVD2M": true, "VPMOVQ2M": true,
|
||||||
|
// The K-to-vector broadcast reads its opmask source from r/m.
|
||||||
|
"VPBROADCASTMB2Q": true, "VPBROADCASTMW2D": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// kmovSpec describes a KMOV width: the opcode depends on the operand
|
// kmovSpec describes a KMOV width: the opcode depends on the operand
|
||||||
@@ -1541,6 +2158,7 @@ var kOpsTable = map[string]kOpSpec{
|
|||||||
"KXORD": {1, 0x47, 1, 1, 1, vexNDS3},
|
"KXORD": {1, 0x47, 1, 1, 1, vexNDS3},
|
||||||
"KXORQ": {1, 0x47, 1, 0, 1, vexNDS3},
|
"KXORQ": {1, 0x47, 1, 0, 1, vexNDS3},
|
||||||
"KUNPCKBW": {1, 0x4B, 0, 1, 1, vexNDS3},
|
"KUNPCKBW": {1, 0x4B, 0, 1, 1, vexNDS3},
|
||||||
|
"KUNPCKWD": {1, 0x4B, 0, 0, 1, vexNDS3},
|
||||||
"KUNPCKDQ": {1, 0x4B, 1, 0, 1, vexNDS3},
|
"KUNPCKDQ": {1, 0x4B, 1, 0, 1, vexNDS3},
|
||||||
"KADDB": {1, 0x4A, 0, 1, 1, vexNDS3},
|
"KADDB": {1, 0x4A, 0, 1, 1, vexNDS3},
|
||||||
"KADDW": {1, 0x4A, 0, 0, 1, vexNDS3},
|
"KADDW": {1, 0x4A, 0, 0, 1, vexNDS3},
|
||||||
|
|||||||
@@ -721,3 +721,216 @@ func hexCompact(b []byte) string {
|
|||||||
}
|
}
|
||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAvx512CorpusFamilies pins representative encodings of the AVX-512
|
||||||
|
// families the toolchain's avx512enc corpus exercises: the bytes are the
|
||||||
|
// go tool asm output for exactly these operands, and the same families are
|
||||||
|
// covered end to end by the avx512_amd64.s differential kernel.
|
||||||
|
func TestAvx512CorpusFamilies(t *testing.T) {
|
||||||
|
vsib := func(base, idx string, scale int) Operand {
|
||||||
|
return Idx(vreg(t, base), vreg(t, idx), scale, 0, 0)
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// AES rounds (EVEX NDS, VEX twin routed by operand width).
|
||||||
|
{"VAESDEC Z", "VAESDEC", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d48ded9"},
|
||||||
|
// Integer VNNI and the bit algorithm group.
|
||||||
|
{"VPDPBUSD", "VPDPBUSD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K2"), vreg(t, "Z3")}, "62f26d4a50d9"},
|
||||||
|
{"VPOPCNTW", "VPOPCNTW", []Operand{vreg(t, "Z1"), vreg(t, "K3"), vreg(t, "Z2")}, "62f2fd4b54d1"},
|
||||||
|
{"VPCONFLICTD", "VPCONFLICTD", []Operand{vreg(t, "Z1"), vreg(t, "K1"), vreg(t, "Z2")}, "62f27d49c4d1"},
|
||||||
|
{"VPLZCNTQ masked", "VPLZCNTQ", []Operand{vreg(t, "Z7"), vreg(t, "K1"), vreg(t, "Z8")}, "6272fd4944c7"},
|
||||||
|
{"VPERMT2B", "VPERMT2B", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f26d497dd9"},
|
||||||
|
{"VPMULTISHIFTQB", "VPMULTISHIFTQB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K3"), vreg(t, "Z4")}, "62f2ed4b83e1"},
|
||||||
|
{"VDBPSADBW", "VDBPSADBW", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K3"), vreg(t, "Z3")}, "62f36d4b42d903"},
|
||||||
|
{"VPSHUFBITQMB", "VPSHUFBITQMB", []Operand{vreg(t, "Z9"), vreg(t, "Z10"), vreg(t, "K3")}, "62d22d488fd9"},
|
||||||
|
{"VPTESTNMQ", "VPTESTNMQ", []Operand{vreg(t, "Z13"), vreg(t, "Z14"), vreg(t, "K5")}, "62d28e4827ed"},
|
||||||
|
// Permutations: immediate and register counts.
|
||||||
|
{"VALIGNQ", "VALIGNQ", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f3ed4903d903"},
|
||||||
|
{"VPERMQ imm", "VPERMQ", []Operand{Imm(1), vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Z2")}, "62f3fd4a00d101"},
|
||||||
|
{"VPERMQ reg", "VPERMQ", []Operand{vreg(t, "Z3"), vreg(t, "Z4"), vreg(t, "K2"), vreg(t, "Z5")}, "62f2dd4a36eb"},
|
||||||
|
{"VPERMPD reg", "VPERMPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed4816d9"},
|
||||||
|
{"VPERMILPS imm", "VPERMILPS", []Operand{Imm(5), vreg(t, "Z9"), vreg(t, "K2"), vreg(t, "Z10")}, "62537d4a04d105"},
|
||||||
|
{"VPERMILPS reg", "VPERMILPS", []Operand{vreg(t, "Z11"), vreg(t, "Z12"), vreg(t, "K2"), vreg(t, "Z13")}, "62521d4a0ceb"},
|
||||||
|
// Shifts: immediate, register-count and memory-count forms; the
|
||||||
|
// count source carries its own XMM tuple width.
|
||||||
|
{"VPSLLW imm mask", "VPSLLW", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Z2")}, "62f16d4a71f103"},
|
||||||
|
{"VPSLLD reg count", "VPSLLD", []Operand{vreg(t, "X1"), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f16d49f2d9"},
|
||||||
|
{"VPSLLDQ", "VPSLLDQ", []Operand{Imm(9), vreg(t, "Z7"), vreg(t, "Z8")}, "62f13d4873ff09"},
|
||||||
|
{"VPSRLDQ mem", "VPSRLDQ", []Operand{Imm(11), Ptr(SI, 16, 16), vreg(t, "Z4")}, "62f15d48739e100000000b"},
|
||||||
|
{"VPSRLVW", "VPSRLVW", []Operand{vreg(t, "Z3"), vreg(t, "Z4"), vreg(t, "K1"), vreg(t, "Z5")}, "62f2dd4910eb"},
|
||||||
|
// Conversions and shuffles with the F2 prefix and no prefix.
|
||||||
|
{"VCVTUDQ2PS", "VCVTUDQ2PS", []Operand{vreg(t, "Z1"), vreg(t, "K1"), vreg(t, "Z2")}, "62f17f497ad1"},
|
||||||
|
{"VSHUFPS", "VSHUFPS", []Operand{Imm(2), vreg(t, "Z4"), vreg(t, "Z5"), vreg(t, "K1"), vreg(t, "Z6")}, "62f15449c6f402"},
|
||||||
|
// Gather and scatter prefetch hints (memory-only, /digit in reg).
|
||||||
|
{"VGATHERPF0DPD", "VGATHERPF0DPD", []Operand{vreg(t, "K5"), vsib("R10", "Y29", 8)}, "6292fd45c60cea"},
|
||||||
|
{"VSCATTERPF1DPS", "VSCATTERPF1DPS", []Operand{vreg(t, "K2"), vsib("R10", "Z28", 4)}, "62927d42c634a2"},
|
||||||
|
// Opmask broadcasts and the K logic.
|
||||||
|
{"VPBROADCASTMB2Q", "VPBROADCASTMB2Q", []Operand{vreg(t, "K1"), vreg(t, "Z2")}, "62f2fe482ad1"},
|
||||||
|
{"VPBROADCASTMW2D", "VPBROADCASTMW2D", []Operand{vreg(t, "K3"), vreg(t, "Z4")}, "62f27e483ae3"},
|
||||||
|
{"KUNPCKWD", "KUNPCKWD", []Operand{vreg(t, "K6"), vreg(t, "K4"), vreg(t, "K1")}, "c5dc4bce"},
|
||||||
|
{"KADDB", "KADDB", []Operand{vreg(t, "K2"), vreg(t, "K3"), vreg(t, "K5")}, "c5e54aea"},
|
||||||
|
// Lane extracts to general registers (EVEX and VEX routes).
|
||||||
|
{"VPEXTRB", "VPEXTRB", []Operand{Imm(3), vreg(t, "X26"), AX}, "62637d0814d003"},
|
||||||
|
{"VPEXTRD", "VPEXTRD", []Operand{Imm(1), vreg(t, "X26"), vreg(t, "R9")}, "62437d0816d101"},
|
||||||
|
{"VPEXTRD vex", "VPEXTRD", []Operand{Imm(1), vreg(t, "X2"), DI}, "c4e37916d701"},
|
||||||
|
{"VPINSRQ", "VPINSRQ", []Operand{Imm(1), DI, vreg(t, "X3"), vreg(t, "X4")}, "c4e3e122e701"},
|
||||||
|
// Moves: masked unaligned, masked scalar register form, half moves
|
||||||
|
// and non-temporal stores.
|
||||||
|
{"VMOVUPS mask", "VMOVUPS", []Operand{vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Z3")}, "62f17c4a11cb"},
|
||||||
|
{"VMOVSD 3op", "VMOVSD", []Operand{vreg(t, "X14"), vreg(t, "X5"), vreg(t, "K3"), vreg(t, "X22")}, "6231d70b11f6"},
|
||||||
|
{"VMOVSS 3op", "VMOVSS", []Operand{vreg(t, "X18"), vreg(t, "X3"), vreg(t, "K2"), vreg(t, "X25")}, "6281660a11d1"},
|
||||||
|
{"VMOVHPS insert", "VMOVHPS", []Operand{Ptr(SI, 0, 8), vreg(t, "X18"), vreg(t, "X19")}, "62e16c00161e"},
|
||||||
|
{"VMOVHPS store", "VMOVHPS", []Operand{vreg(t, "X20"), Ptr(SI, 8, 8)}, "62e17c08176601"},
|
||||||
|
{"VMOVLHPS", "VMOVLHPS", []Operand{vreg(t, "X16"), vreg(t, "X5"), vreg(t, "X17")}, "62a1540816c8"},
|
||||||
|
{"VMOVNTDQ", "VMOVNTDQ", []Operand{vreg(t, "Z7"), Ptr(SI, 0, 64)}, "62f17d48e73e"},
|
||||||
|
{"VMOVNTDQA", "VMOVNTDQA", []Operand{Ptr(SI, 64, 64), vreg(t, "Z8")}, "62727d482a4601"},
|
||||||
|
{"VMOVNTPS", "VMOVNTPS", []Operand{vreg(t, "Z9"), Ptr(SI, 0, 64)}, "62717c482b0e"},
|
||||||
|
// Scalar compares with and without the 66 prefix.
|
||||||
|
{"VCOMISD", "VCOMISD", []Operand{vreg(t, "X5"), vreg(t, "X6")}, "c5f92ff5"},
|
||||||
|
{"VUCOMISS", "VUCOMISS", []Operand{vreg(t, "X7"), vreg(t, "X8")}, "c5782ec7"},
|
||||||
|
// Floating point helpers.
|
||||||
|
{"VSQRTSD", "VSQRTSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "K1"), vreg(t, "X3")}, "62f1ef0951d9"},
|
||||||
|
{"VEXP2PD", "VEXP2PD", []Operand{vreg(t, "Z5"), vreg(t, "K1"), vreg(t, "Z6")}, "62f2fd49c8f5"},
|
||||||
|
{"VRCP28SD", "VRCP28SD", []Operand{vreg(t, "X9"), vreg(t, "X8"), vreg(t, "K1"), vreg(t, "X10")}, "6252bd09cbd1"},
|
||||||
|
{"VBROADCASTF32X2", "VBROADCASTF32X2", []Operand{vreg(t, "X1"), vreg(t, "K1"), vreg(t, "Z2")}, "62f27d4919d1"},
|
||||||
|
{"VPCOMPRESSB", "VPCOMPRESSB", []Operand{vreg(t, "Z1"), vreg(t, "K1"), Ptr(SI, 0, 64)}, "62f27d49630e"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEvexQuadRegisterGroundTruth pins the quad-register instructions (the
|
||||||
|
// 4FMAPS and 4VNNIW families) byte for byte against go tool asm: the memory
|
||||||
|
// source keeps r/m, the bracketed list's LOW register travels the inverted
|
||||||
|
// 5-bit V'VVVV field, the destination sits in reg, the opmask rides aaa and
|
||||||
|
// the vector length follows the destination (L'L=512 for the ZMM forms,
|
||||||
|
// 128 for the scalar ones) while the disp8×N multiplier stays 16 for every
|
||||||
|
// member. The x86 decoder has no view of these forms, so no decode check
|
||||||
|
// runs.
|
||||||
|
func TestEvexQuadRegisterGroundTruth(t *testing.T) {
|
||||||
|
sp := vreg(t, "RSP")
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"V4FMADDPS 17(SP) [Z0-Z3] K2 Z0", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f27f4a9a842411000000"},
|
||||||
|
{"V4FMADDPS [Z10-Z13]", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z10"), vreg(t, "Z13")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f22f4a9a842411000000"},
|
||||||
|
{"V4FMADDPS [Z20-Z23]", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z20"), vreg(t, "Z23")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f25f429a842411000000"},
|
||||||
|
{"V4FMADDPS Z8 dst", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "K2"), vreg(t, "Z8")},
|
||||||
|
"62727f4a9a842411000000"},
|
||||||
|
{"V4FMADDPS disp8x16", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 64, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f27f4a9a442404"},
|
||||||
|
{"V4FMADDPS unmasked", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "Z0")},
|
||||||
|
"62f27f489a842411000000"},
|
||||||
|
{"V4FMADDSS 7(AX) [X0-X3] K5 X22", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X0"), vreg(t, "X3")}, vreg(t, "K5"), vreg(t, "X22")},
|
||||||
|
"62e27f0d9bb007000000"},
|
||||||
|
{"V4FMADDSS (DI)", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(DI, 0, 8), RegList{vreg(t, "X0"), vreg(t, "X3")}, vreg(t, "K5"), vreg(t, "X22")},
|
||||||
|
"62e27f0d9b37"},
|
||||||
|
{"V4FMADDSS [X10-X13]", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X10"), vreg(t, "X13")}, vreg(t, "K5"), vreg(t, "X22")},
|
||||||
|
"62e22f0d9bb007000000"},
|
||||||
|
{"V4FMADDSS [X20-X23]", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X20"), vreg(t, "X23")}, vreg(t, "K5"), vreg(t, "X22")},
|
||||||
|
"62e25f059bb007000000"},
|
||||||
|
{"V4FMADDSS X30 dst", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X0"), vreg(t, "X3")}, vreg(t, "K5"), vreg(t, "X30")},
|
||||||
|
"62627f0d9bb007000000"},
|
||||||
|
{"V4FMADDSS X3 dst", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X0"), vreg(t, "X3")}, vreg(t, "K5"), vreg(t, "X3")},
|
||||||
|
"62f27f0d9b9807000000"},
|
||||||
|
{"V4FMADDSS disp8x16", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 16, 8), RegList{vreg(t, "X20"), vreg(t, "X23")}, vreg(t, "K5"), vreg(t, "X30")},
|
||||||
|
"62625f059b7001"},
|
||||||
|
{"V4FNMADDPS", "V4FNMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f27f4aaa842411000000"},
|
||||||
|
{"V4FNMADDSS", "V4FNMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 7, 8), RegList{vreg(t, "X0"), vreg(t, "X3")}, vreg(t, "K5"), vreg(t, "X22")},
|
||||||
|
"62e27f0dabb007000000"},
|
||||||
|
{"VP4DPWSSD", "VP4DPWSSD",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "K2"), vreg(t, "Z0")},
|
||||||
|
"62f27f4a52842411000000"},
|
||||||
|
{"VP4DPWSSDS unmasked", "VP4DPWSSDS",
|
||||||
|
[]Operand{Ptr(sp, 17, 8), RegList{vreg(t, "Z0"), vreg(t, "Z3")}, vreg(t, "Z0")},
|
||||||
|
"62f27f4853842411000000"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEvexQuadRegisterErrors pins the operand shapes the toolchain rejects:
|
||||||
|
// the register class the list and the destination take is fixed per
|
||||||
|
// instruction, the source is memory only, the opmask slot is positional and
|
||||||
|
// the list's low register owns V'VVVV.
|
||||||
|
func TestEvexQuadRegisterErrors(t *testing.T) {
|
||||||
|
sp := vreg(t, "RSP")
|
||||||
|
list := func(lo, hi string) RegList {
|
||||||
|
return RegList{vreg(t, lo), vreg(t, hi)}
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
}{
|
||||||
|
{"X list on the PS form", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("X0", "X3"), vreg(t, "K2"), vreg(t, "Z0")}},
|
||||||
|
{"Z list on the SS form", "V4FMADDSS",
|
||||||
|
[]Operand{Ptr(AX, 0, 8), list("Z0", "Z3"), vreg(t, "K5"), vreg(t, "X22")}},
|
||||||
|
{"Y destination", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "K2"), vreg(t, "Y0")}},
|
||||||
|
{"register source", "V4FMADDPS",
|
||||||
|
[]Operand{vreg(t, "Z1"), list("Z0", "Z3"), vreg(t, "K2"), vreg(t, "Z0")}},
|
||||||
|
{"non-mask third operand", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "Z4"), vreg(t, "Z0")}},
|
||||||
|
{"k0 mask", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "K0"), vreg(t, "Z0")}},
|
||||||
|
{"K after the destination", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "Z0"), vreg(t, "K2")}},
|
||||||
|
{"zeroing without a mask", "V4FMADDPS.Z",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "Z0")}},
|
||||||
|
{"SAE suffix", "V4FMADDPS.SAE",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3"), vreg(t, "K2"), vreg(t, "Z0")}},
|
||||||
|
{"high index source", "VP4DPWSSD",
|
||||||
|
[]Operand{Idx(DI, vreg(t, "X16"), 1, 0, 8), list("Z0", "Z3"), vreg(t, "K2"), vreg(t, "Z0")}},
|
||||||
|
{"short operand list", "V4FMADDPS",
|
||||||
|
[]Operand{Ptr(sp, 0, 8), list("Z0", "Z3")}},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||||||
|
t.Errorf("%s: expected an error, got none", c.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// The extended-instruction registry: the lookup over and above the generated
|
||||||
|
// architecture tables. The generated tables (arch/*_gen.go) list the
|
||||||
|
// mnemonics the Go toolchain knows; the extension layer carries the
|
||||||
|
// instructions it does not, and this file indexes them per architecture so
|
||||||
|
// the assembler and the linter can consult the layer without touching the
|
||||||
|
// generated lists or the main encoders. A later hook wires
|
||||||
|
// ExtensionEncodable into the Encodable mirror and EncodeExtension into the
|
||||||
|
// per-architecture assembly paths; nothing existing changes until then.
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// extensionIndex is the per-architecture index of the extension layer, keyed
|
||||||
|
// by upper-case mnemonic. One mnemonic registers several forms (the SVE ADD
|
||||||
|
// carries unpredicated, predicated and immediate shapes), so the value is the
|
||||||
|
// full candidate list in table order.
|
||||||
|
type extensionIndex struct {
|
||||||
|
byName map[string][]arch.ExtInstr
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionIndexes builds one index per known architecture. Architectures
|
||||||
|
// whose extension layer is not built yet get an empty index, which keeps the
|
||||||
|
// queries answering false rather than failing on a missing entry.
|
||||||
|
var extensionIndexes = buildExtensionIndexes()
|
||||||
|
|
||||||
|
func buildExtensionIndexes() map[arch.Arch]*extensionIndex {
|
||||||
|
m := make(map[arch.Arch]*extensionIndex)
|
||||||
|
for _, a := range []arch.Arch{arch.AMD64, arch.ARM64, arch.RISCV, arch.LOONG64} {
|
||||||
|
idx := &extensionIndex{byName: make(map[string][]arch.ExtInstr)}
|
||||||
|
for _, in := range arch.Extensions(a) {
|
||||||
|
key := strings.ToUpper(in.Name)
|
||||||
|
idx.byName[key] = append(idx.byName[key], in)
|
||||||
|
}
|
||||||
|
m[a] = idx
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupExtension returns the extended instructions registered for the
|
||||||
|
// mnemonic on a, outside the generated architecture table. It reports false
|
||||||
|
// when a carries no extended layer or the mnemonic is not in it; a mnemonic
|
||||||
|
// the base table knows is not thereby covered, the layers stay independent.
|
||||||
|
func LookupExtension(a arch.Arch, mnemonic string) ([]arch.ExtInstr, bool) {
|
||||||
|
idx, ok := extensionIndexes[a]
|
||||||
|
if !ok || idx == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
cands, ok := idx.byName[strings.ToUpper(mnemonic)]
|
||||||
|
return cands, ok && len(cands) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionNames returns the mnemonics the extension layer of a registers,
|
||||||
|
// in table order, without duplicates.
|
||||||
|
func ExtensionNames(a arch.Arch) []string {
|
||||||
|
var names []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, in := range arch.Extensions(a) {
|
||||||
|
key := strings.ToUpper(in.Name)
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
names = append(names, in.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtension encodes one extended instruction on a: it resolves the
|
||||||
|
// mnemonic through the extension registry, picks the registered form whose
|
||||||
|
// arity matches the operands and encodes against it. The first form that
|
||||||
|
// encodes wins. When every matching form rejects the operands, the error
|
||||||
|
// comes from the form whose operand kinds the list points at (the one with
|
||||||
|
// the most matching positions), so a mis-spelled predicate qualifier is
|
||||||
|
// diagnosed as one, not as the unpredicated form's register complaint.
|
||||||
|
func EncodeExtension(a arch.Arch, mnemonic string, ops ...arch.ExtOperand) ([]byte, error) {
|
||||||
|
cands, ok := LookupExtension(a, mnemonic)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s registers no extended instruction %q", a, mnemonic)
|
||||||
|
}
|
||||||
|
var bestErr error
|
||||||
|
var bestScore int
|
||||||
|
var tried int
|
||||||
|
for _, in := range cands {
|
||||||
|
if in.Form.Arity() != len(ops) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tried++
|
||||||
|
b, err := in.Encode(ops)
|
||||||
|
if err == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
if score := kindScore(in.Form, ops); bestErr == nil || score > bestScore {
|
||||||
|
bestErr, bestScore = err, score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tried == 0 {
|
||||||
|
return nil, fmt.Errorf("%s: extended %q takes %s, got %d operands",
|
||||||
|
a, mnemonic, extensionAritySummary(cands), len(ops))
|
||||||
|
}
|
||||||
|
return nil, bestErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// kindScore counts the positions whose operand kind matches what the form
|
||||||
|
// wants, the tie-break that picks the most specific rejection.
|
||||||
|
func kindScore(form arch.ExtForm, ops []arch.ExtOperand) int {
|
||||||
|
kinds := form.Kinds()
|
||||||
|
score := 0
|
||||||
|
for i, op := range ops {
|
||||||
|
if i < len(kinds) && op.Kind == kinds[i] {
|
||||||
|
score++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionEncodable reports whether the extension layer of a encodes the
|
||||||
|
// mnemonic with these operands. It mirrors asm.Encodable for the extension
|
||||||
|
// layer: the predicate the linter consults once the hook wires it in.
|
||||||
|
func ExtensionEncodable(a arch.Arch, mnemonic string, ops ...arch.ExtOperand) bool {
|
||||||
|
_, err := EncodeExtension(a, mnemonic, ops...)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionAritySummary describes the operand counts the candidate forms
|
||||||
|
// take, "2 or 3" style, for the arity error.
|
||||||
|
func extensionAritySummary(cands []arch.ExtInstr) string {
|
||||||
|
counts := make([]int, 0, len(cands))
|
||||||
|
seen := make(map[int]bool)
|
||||||
|
for _, in := range cands {
|
||||||
|
n := in.Form.Arity()
|
||||||
|
if !seen[n] {
|
||||||
|
seen[n] = true
|
||||||
|
counts = append(counts, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(counts)
|
||||||
|
var b strings.Builder
|
||||||
|
for i, n := range counts {
|
||||||
|
if i > 0 {
|
||||||
|
if i == len(counts)-1 {
|
||||||
|
b.WriteString(" or ")
|
||||||
|
} else {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%d", n)
|
||||||
|
}
|
||||||
|
b.WriteString(" operands")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAmd64ExtensionRegistry checks the mnemonic lookup for the amd64 layer:
|
||||||
|
// one mnemonic across several vector lengths or W bits resolves to every
|
||||||
|
// entry, the lookup is case-insensitive, and the counts match the registered
|
||||||
|
// families.
|
||||||
|
func TestAmd64ExtensionRegistry(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
mnem string
|
||||||
|
forms int
|
||||||
|
}{
|
||||||
|
{"VCVTNE2PS2BF16", 3},
|
||||||
|
{"VCVTNEPS2BF16", 3},
|
||||||
|
{"VDPBF16PS", 3},
|
||||||
|
{"VP2INTERSECTD", 3},
|
||||||
|
{"VP2INTERSECTQ", 3},
|
||||||
|
{"VMOVSH", 3},
|
||||||
|
{"VMOVW", 4},
|
||||||
|
{"VADDSH", 1},
|
||||||
|
{"VSQRTSH", 1},
|
||||||
|
{"VCOMISH", 1},
|
||||||
|
{"VCVTSH2SS", 1},
|
||||||
|
{"VCVTSI2SH", 2},
|
||||||
|
{"VCVTSH2SI", 2},
|
||||||
|
{"VADDPH", 3},
|
||||||
|
{"VSQRTPH", 3},
|
||||||
|
{"VSCALEFSH", 1},
|
||||||
|
{"VGETEXPSH", 1},
|
||||||
|
{"VCMPSH", 1},
|
||||||
|
{"VGETMANTSH", 1},
|
||||||
|
{"VREDUCESH", 1},
|
||||||
|
{"VRNDSCALESH", 1},
|
||||||
|
{"VCVTPH2W", 3},
|
||||||
|
{"VCVTPH2UW", 3},
|
||||||
|
{"VCVTW2PH", 3},
|
||||||
|
{"VCVTUW2PH", 3},
|
||||||
|
{"VCVTPH2DQ", 3},
|
||||||
|
{"VCVTPH2UDQ", 3},
|
||||||
|
{"VCVTDQ2PH", 3},
|
||||||
|
{"VCVTUDQ2PH", 3},
|
||||||
|
{"VCVTPH2QQ", 3},
|
||||||
|
{"VCVTPH2UQQ", 3},
|
||||||
|
{"VCVTQQ2PH", 3},
|
||||||
|
{"VCVTUQQ2PH", 3},
|
||||||
|
{"VCVTPH2PD", 3},
|
||||||
|
{"VCVTPD2PH", 3},
|
||||||
|
{"VRNDSCALEPH", 3},
|
||||||
|
{"VREDUCEPH", 3},
|
||||||
|
{"VGETMANTPH", 3},
|
||||||
|
{"VFMADD132PH", 3},
|
||||||
|
{"VFMADD213PH", 3},
|
||||||
|
{"VFMADD231PH", 3},
|
||||||
|
{"VFMSUB132PH", 3},
|
||||||
|
{"VFMSUB213PH", 3},
|
||||||
|
{"VFMSUB231PH", 3},
|
||||||
|
{"VFMADDSUB132PH", 3},
|
||||||
|
{"VFMADDSUB213PH", 3},
|
||||||
|
{"VFMADDSUB231PH", 3},
|
||||||
|
{"VFMSUBADD132PH", 3},
|
||||||
|
{"VFMSUBADD213PH", 3},
|
||||||
|
{"VFMSUBADD231PH", 3},
|
||||||
|
{"VFMADD132SH", 1},
|
||||||
|
{"VFMADD213SH", 1},
|
||||||
|
{"VFMADD231SH", 1},
|
||||||
|
{"VFMSUB132SH", 1},
|
||||||
|
{"VFMSUB213SH", 1},
|
||||||
|
{"VFMSUB231SH", 1},
|
||||||
|
{"VFMULCPH", 3},
|
||||||
|
{"VFCMULCPH", 3},
|
||||||
|
{"VFMULCSH", 1},
|
||||||
|
{"VFCMULCSH", 1},
|
||||||
|
{"VFMADDCPH", 3},
|
||||||
|
{"VFCMADDCPH", 3},
|
||||||
|
{"VFMADDCSH", 1},
|
||||||
|
{"VFCMADDCSH", 1},
|
||||||
|
{"VMINMAXPH", 3},
|
||||||
|
{"VMINMAXSH", 1},
|
||||||
|
{"VPDPWSUD", 2},
|
||||||
|
{"VPDPWSUDS", 2},
|
||||||
|
{"VPDPWUSD", 2},
|
||||||
|
{"VPDPWUSDS", 2},
|
||||||
|
} {
|
||||||
|
cands, ok := LookupExtension(arch.AMD64, tt.mnem)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("LookupExtension(AMD64, %s) found nothing", tt.mnem)
|
||||||
|
}
|
||||||
|
if len(cands) != tt.forms {
|
||||||
|
t.Errorf("%s registers %d forms, want %d", tt.mnem, len(cands), tt.forms)
|
||||||
|
}
|
||||||
|
lower, ok := LookupExtension(arch.AMD64, strings.ToLower(tt.mnem))
|
||||||
|
if !ok || len(lower) != tt.forms {
|
||||||
|
t.Errorf("the %s lookup is not case-insensitive", tt.mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(arch.AMD64); len(got) != 191 {
|
||||||
|
t.Errorf("the amd64 layer registers %d instructions, want 191", len(got))
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.AMD64, "NOSUCHINSTR"); ok {
|
||||||
|
t.Error("a non-extended mnemonic resolved")
|
||||||
|
}
|
||||||
|
// VPOPCNTD and VPOPCNTQ are toolchain instructions today: they stay in
|
||||||
|
// the generated table and out of the extension layer.
|
||||||
|
if _, ok := LookupExtension(arch.AMD64, "VPOPCNTD"); ok {
|
||||||
|
t.Error("VPOPCNTD is an extension, want it in the generated table alone")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtensionAboveGeneratedTable pins the layering twice over: no
|
||||||
|
// registered mnemonic sits in the generated amd64 table, and the encoder
|
||||||
|
// mirror asm.Encodable answers false for every one of them, so the layer
|
||||||
|
// stays out of the main encoders by test and not by promise.
|
||||||
|
func TestAmd64ExtensionAboveGeneratedTable(t *testing.T) {
|
||||||
|
for _, mnem := range ExtensionNames(arch.AMD64) {
|
||||||
|
if _, found := arch.ForArch(arch.AMD64).Lookup(mnem); found {
|
||||||
|
t.Errorf("%s leaked into the generated amd64 table", mnem)
|
||||||
|
}
|
||||||
|
if Encodable(mnem) {
|
||||||
|
t.Errorf("%s is encodable through the main encoder, the layer is not sealed", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionAmd64 encodes through the registry and pins the same
|
||||||
|
// golden words the arch table tests pin, proving the registry resolves to the
|
||||||
|
// right encoding.
|
||||||
|
func TestEncodeExtensionAmd64(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []arch.ExtOperand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"bf16 convert", "VCVTNE2PS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtZmm(6)},
|
||||||
|
"62f2574872f4"},
|
||||||
|
{"bf16 narrow convert", "VCVTNEPS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtYmm(6)},
|
||||||
|
"62f27e4872f5"},
|
||||||
|
{"dot product", "VDPBF16PS",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtXmm(6)},
|
||||||
|
"62f2560852f4"},
|
||||||
|
{"intersect into a mask", "VP2INTERSECTD",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(2), arch.ExtYmm(1), arch.ExtMask(2)},
|
||||||
|
"62f26f2868d1"},
|
||||||
|
{"scalar fp16 add, high registers", "VADDSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
||||||
|
"6205160058f4"},
|
||||||
|
{"scalar compare", "VCOMISH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(30)},
|
||||||
|
"62057c082ff5"},
|
||||||
|
{"integer into a scalar fp16", "VCVTSI2SH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtGpr64(12), arch.ExtXmm(30)},
|
||||||
|
"624596002af4"},
|
||||||
|
{"scalar fp16 into an integer", "VCVTSH2SI",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(30), arch.ExtGpr32(2)},
|
||||||
|
"62957e082dd6"},
|
||||||
|
{"word move into an xmm", "VMOVW",
|
||||||
|
[]arch.ExtOperand{arch.ExtGpr64(12), arch.ExtXmm(30)},
|
||||||
|
"62457d086ef4"},
|
||||||
|
{"scalar compare into a mask", "VCMPSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtMask(5)},
|
||||||
|
"62931600c2ec7b"},
|
||||||
|
{"mantissa extract with a control byte", "VGETMANTSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x0b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
||||||
|
"6203140027f40b"},
|
||||||
|
{"packed fp16 add under embedded rounding", "VADDPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f5547858f4"},
|
||||||
|
{"scalar fp16 minimum with exceptions suppressed", "VMINSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundSAE)},
|
||||||
|
"62f556185df4"},
|
||||||
|
{"fp16 to signed words", "VCVTPH2W",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f57d487df5"},
|
||||||
|
{"dwords to fp16 over a broadcast source", "VCVTDQ2PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtBroadcast(9, 0), arch.ExtYmm(30)},
|
||||||
|
"62457c585b31"},
|
||||||
|
{"fp16 to signed qwords, rounded", "VCVTPH2QQ",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f57d787bf5"},
|
||||||
|
{"fp16 scalar out of memory into an integer", "VCVTSH2SI",
|
||||||
|
[]arch.ExtOperand{arch.ExtMemory(9, 0), arch.ExtGpr64(12)},
|
||||||
|
"6255fe082d21"},
|
||||||
|
{"fp16 to double-precision, widened", "VCVTPH2PD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f57c485af5"},
|
||||||
|
{"packed fp16 rounding to fraction bits", "VRNDSCALEPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtZmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f37c4808f57b"},
|
||||||
|
{"packed fused multiply-add, high registers", "VFMADD132PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"6206154098f4"},
|
||||||
|
{"fused multiply-add out of a broadcast source", "VFMADD231PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(5), arch.ExtBroadcast(1, 0), arch.ExtYmm(6)},
|
||||||
|
"62f65538b831"},
|
||||||
|
{"scalar multiply-subtract under embedded rounding", "VFMSUB231SH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f65578bbf4"},
|
||||||
|
{"complex multiply, conjugating the first source", "VFCMULCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"62061740d6f4"},
|
||||||
|
{"complex multiply-add, conjugating the second source", "VFMADDCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"6206164056f4"},
|
||||||
|
{"complex multiply-add, conjugated first source, rounded", "VFCMADDCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundNearest)},
|
||||||
|
"62f6571856f4"},
|
||||||
|
{"scalar complex multiply-add out of memory", "VFMADDCSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtMemory(9, 0), arch.ExtXmm(30)},
|
||||||
|
"624616005731"},
|
||||||
|
{"minimum or maximum under a control byte", "VMINMAXPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtZmm(29), arch.ExtMemory(9, 0), arch.ExtZmm(30)},
|
||||||
|
"62431440523188"},
|
||||||
|
{"scalar minimum or maximum out of memory", "VMINMAXSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtXmm(28), arch.ExtMemory(9, 0), arch.ExtXmm(29)},
|
||||||
|
"62431c00532988"},
|
||||||
|
{"vnni dot product through the VEX word", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtXmm(1), arch.ExtXmm(3)},
|
||||||
|
"c4e26ad2d9"},
|
||||||
|
{"saturating dot product, high registers", "VPDPWUSDS",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(10), arch.ExtYmm(15), arch.ExtYmm(8)},
|
||||||
|
"c4422dd3c7"},
|
||||||
|
{"dot product out of memory", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtMemory(1, 127), arch.ExtXmm(1)},
|
||||||
|
"c4e26ad2497f"},
|
||||||
|
} {
|
||||||
|
got, err := EncodeExtension(arch.AMD64, tt.mnem, tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: encode: %v", tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionAmd64Errors checks the registry's diagnostics on the
|
||||||
|
// amd64 side: a wrong arity names the form's count and a mis-classed operand
|
||||||
|
// surfaces the entry's own message.
|
||||||
|
func TestEncodeExtensionAmd64Errors(t *testing.T) {
|
||||||
|
if _, err := EncodeExtension(arch.AMD64, "VP2INTERSECTD", arch.ExtZmm(1)); err == nil {
|
||||||
|
t.Error("one operand encoded, want an arity error")
|
||||||
|
} else if !strings.Contains(err.Error(), "3 operands") {
|
||||||
|
t.Errorf("arity error %q does not name the count", err)
|
||||||
|
}
|
||||||
|
_, err := EncodeExtension(arch.AMD64, "VCVTNEPS2BF16", arch.ExtZmm(1), arch.ExtZmm(2))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("a ZMM destination encoded on the narrow convert, want an error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "YMM register") {
|
||||||
|
t.Errorf("error %q does not name the YMM destination", err)
|
||||||
|
}
|
||||||
|
if _, err := EncodeExtension(arch.AMD64, "VCVTNE2PS2BF16"); err == nil ||
|
||||||
|
!strings.Contains(err.Error(), "takes 3 operands, got 0") {
|
||||||
|
t.Errorf("zero-operand error = %v, want the operand-count diagnostic", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,534 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExtensionRegistryARM64 checks the mnemonic lookup over and above the
|
||||||
|
// generated arm64 table: one mnemonic, several forms, case-insensitive, and
|
||||||
|
// nothing offered for a spelling the layer does not carry.
|
||||||
|
func TestExtensionRegistryARM64(t *testing.T) {
|
||||||
|
add, ok := LookupExtension(arch.ARM64, "ADD")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("LookupExtension(ARM64, ADD) found nothing")
|
||||||
|
}
|
||||||
|
var forms []arch.ExtForm
|
||||||
|
for _, in := range add {
|
||||||
|
if in.Name != "ADD" {
|
||||||
|
t.Errorf("candidate %q leaked into the ADD lookup", in.Name)
|
||||||
|
}
|
||||||
|
forms = append(forms, in.Form)
|
||||||
|
}
|
||||||
|
if len(forms) != 3 ||
|
||||||
|
forms[0] != arch.ExtFormVectors ||
|
||||||
|
forms[1] != arch.ExtFormPredicated ||
|
||||||
|
forms[2] != arch.ExtFormImmediate {
|
||||||
|
t.Errorf("ADD registers forms %v, want unpredicated, predicated and immediate", forms)
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "add"); !ok {
|
||||||
|
t.Error("the lookup is case-sensitive")
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "NOSUCHINSTR"); ok {
|
||||||
|
t.Error("a non-extended mnemonic resolved")
|
||||||
|
}
|
||||||
|
sqadd, ok := LookupExtension(arch.ARM64, "SQADD")
|
||||||
|
if !ok || len(sqadd) != 2 {
|
||||||
|
t.Errorf("SQADD registers %d forms, want the unpredicated and immediate pair", len(sqadd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionAboveGeneratedTable pins the layering: SQADD is nowhere in the
|
||||||
|
// generated arm64 table (the toolchain knows only the NEON spelling VSQADD)
|
||||||
|
// yet the extension layer carries it, while ADD sits in both layers
|
||||||
|
// independently.
|
||||||
|
func TestExtensionAboveGeneratedTable(t *testing.T) {
|
||||||
|
if _, found := arch.ForArch(arch.ARM64).Lookup("SQADD"); found {
|
||||||
|
t.Error("SQADD is in the generated table, the layering assumption broke")
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "SQADD"); !ok {
|
||||||
|
t.Error("SQADD is missing from the extension layer")
|
||||||
|
}
|
||||||
|
if _, found := arch.ForArch(arch.ARM64).Lookup("ADD"); !found {
|
||||||
|
t.Error("ADD vanished from the generated table")
|
||||||
|
}
|
||||||
|
if add, ok := LookupExtension(arch.ARM64, "ADD"); !ok || len(add) != 3 {
|
||||||
|
t.Errorf("ADD carries %d extension forms, want 3", len(add))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionGolden encodes through the registry and pins the same
|
||||||
|
// golden words the arch table tests pin, proving the registry resolves to the
|
||||||
|
// right encoding.
|
||||||
|
func TestEncodeExtensionGolden(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []arch.ExtOperand
|
||||||
|
want uint32
|
||||||
|
}{
|
||||||
|
{"unpredicated add", "ADD",
|
||||||
|
[]arch.ExtOperand{
|
||||||
|
arch.ExtVector(2, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
},
|
||||||
|
0x04200040},
|
||||||
|
{"predicated mul", "MUL",
|
||||||
|
[]arch.ExtOperand{
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtPredicate(2, arch.ExtQualMerging), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
},
|
||||||
|
0x04100800},
|
||||||
|
{"immediate add with derived shift", "ADD",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(32512), arch.ExtVector(0, arch.ExtArrH)},
|
||||||
|
0x2560efe0},
|
||||||
|
{"signed immediate mul", "MUL",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(-1), arch.ExtVector(0, arch.ExtArrB)},
|
||||||
|
0x2530dfe0},
|
||||||
|
} {
|
||||||
|
got, err := EncodeExtension(arch.ARM64, tt.mnem, tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: encode: %v", tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if want := hex.EncodeToString([]byte{
|
||||||
|
byte(tt.want), byte(tt.want >> 8), byte(tt.want >> 16), byte(tt.want >> 24),
|
||||||
|
}); hex.EncodeToString(got) != want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionErrors checks the registry's diagnostics: a wrong arity
|
||||||
|
// names every form's count, an operand the first candidate rejects surfaces
|
||||||
|
// its own message once a later form takes over.
|
||||||
|
func TestEncodeExtensionErrors(t *testing.T) {
|
||||||
|
if _, err := EncodeExtension(arch.ARM64, "ADD", arch.ExtVector(0, arch.ExtArrB)); err == nil {
|
||||||
|
t.Error("one operand encoded, want an arity error")
|
||||||
|
} else if !strings.Contains(err.Error(), "2 or 3 operands") {
|
||||||
|
t.Errorf("arity error %q does not name the counts", err)
|
||||||
|
}
|
||||||
|
// The predicated candidate must answer for its own operands: the /Z
|
||||||
|
// qualifier is rejected with the merging message, not the unpredicated
|
||||||
|
// form's register-kind complaint.
|
||||||
|
_, err := EncodeExtension(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtPredicate(0, arch.ExtQualZeroing), arch.ExtVector(0, arch.ExtArrB))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("/Z encoded, want an error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "/M") {
|
||||||
|
t.Errorf("error %q does not name the merging qualifier", err)
|
||||||
|
}
|
||||||
|
if _, err := EncodeExtension(arch.ARM64, "NOSUCHINSTR", arch.ExtVector(0, arch.ExtArrB)); err == nil ||
|
||||||
|
!strings.Contains(err.Error(), "registers no extended instruction") {
|
||||||
|
t.Errorf("unknown mnemonic error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionEncodable checks the predicate the later Encodable hook will
|
||||||
|
// call: true exactly when the registry encodes the operand list.
|
||||||
|
func TestExtensionEncodable(t *testing.T) {
|
||||||
|
if !ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrS), arch.ExtVector(1, arch.ExtArrS), arch.ExtVector(2, arch.ExtArrS)) {
|
||||||
|
t.Error("an encodable unpredicated add reported false")
|
||||||
|
}
|
||||||
|
if !ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(1, arch.ExtArrS), arch.ExtPredicate(0, arch.ExtQualMerging), arch.ExtVector(0, arch.ExtArrS)) {
|
||||||
|
t.Error("an encodable predicated add reported false")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrS), arch.ExtVector(0, arch.ExtArrB)) {
|
||||||
|
t.Error("mismatched arrangements reported encodable")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "ADD", arch.ExtVector(0, arch.ExtArrB)) {
|
||||||
|
t.Error("a one-operand add reported encodable")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "NOSUCHINSTR") {
|
||||||
|
t.Error("an unregistered mnemonic reported encodable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionArchIsolation is the architecture-binding negative case: no
|
||||||
|
// architecture answers the arm64 mnemonics but arm64, and the amd64 layer
|
||||||
|
// answers nothing of the arm64 family either (its own mnemonics live in
|
||||||
|
// extension_amd64_test.go).
|
||||||
|
func TestExtensionArchIsolation(t *testing.T) {
|
||||||
|
ops := []arch.ExtOperand{
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
}
|
||||||
|
for _, a := range []arch.Arch{arch.RISCV, arch.LOONG64, arch.Unknown} {
|
||||||
|
if cands, ok := LookupExtension(a, "ADD"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(%s, ADD) offered %d candidates", a, len(cands))
|
||||||
|
}
|
||||||
|
if cands, ok := LookupExtension(a, "MUL"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(%s, MUL) offered %d candidates", a, len(cands))
|
||||||
|
}
|
||||||
|
if got, err := EncodeExtension(a, "ADD", ops...); err == nil {
|
||||||
|
t.Errorf("EncodeExtension(%s, ADD) encoded %x, want a refusal", a, got)
|
||||||
|
} else if !strings.Contains(err.Error(), string(a)) {
|
||||||
|
t.Errorf("EncodeExtension(%s) error %q does not name the architecture", a, err)
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(a, "ADD", ops...) {
|
||||||
|
t.Errorf("ExtensionEncodable(%s, ADD) reported true", a)
|
||||||
|
}
|
||||||
|
if names := ExtensionNames(a); len(names) != 0 {
|
||||||
|
t.Errorf("ExtensionNames(%s) = %v, want none", a, names)
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(a); len(got) != 0 {
|
||||||
|
t.Errorf("arch.Extensions(%s) carries %d instructions", a, len(got))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The amd64 layer exists but stays silent about the arm64 family.
|
||||||
|
if cands, ok := LookupExtension(arch.AMD64, "ADD"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(AMD64, ADD) offered %d candidates", len(cands))
|
||||||
|
}
|
||||||
|
if got, err := EncodeExtension(arch.AMD64, "ADD", ops...); err == nil {
|
||||||
|
t.Errorf("EncodeExtension(AMD64, ADD) encoded %x, want a refusal", got)
|
||||||
|
} else if !strings.Contains(err.Error(), string(arch.AMD64)) {
|
||||||
|
t.Errorf("EncodeExtension(AMD64) error %q does not name the architecture", err)
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.AMD64, "ADD", ops...) {
|
||||||
|
t.Error("ExtensionEncodable(AMD64, ADD) reported true")
|
||||||
|
}
|
||||||
|
if names := ExtensionNames(arch.AMD64); len(names) == 0 {
|
||||||
|
t.Error("the amd64 layer registers no names")
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(arch.AMD64); len(got) == 0 {
|
||||||
|
t.Error("arch.Extensions(AMD64) is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionNamesARM64 checks the completion-facing name list: every
|
||||||
|
// distinct mnemonic of the family, first-occurrence order, no duplicates.
|
||||||
|
func TestExtensionNamesARM64(t *testing.T) {
|
||||||
|
want := []string{
|
||||||
|
// The SVE integer add/subtract/multiply family.
|
||||||
|
"ADD", "SUB", "SQADD", "UQADD", "SQSUB", "UQSUB", "MUL", "SMULH", "UMULH", "SUBR",
|
||||||
|
// The SVE and SVE2.1 predicate family: the logical operations, the
|
||||||
|
// breaks, the permutations, the singles, the first-fault group and
|
||||||
|
// the while compares.
|
||||||
|
"PAND", "PANDS", "PBIC", "PBICS", "PEOR", "PEORS",
|
||||||
|
"PNAND", "PNANDS", "PNOR", "PNORS", "PORN", "PORNS", "PORR", "PORRS",
|
||||||
|
"PSEL",
|
||||||
|
"PBRKA", "PBRKAS", "PBRKB", "PBRKBS", "PBRKN", "PBRKNS",
|
||||||
|
"PBRKPA", "PBRKPAS", "PBRKPB", "PBRKPBS",
|
||||||
|
"PTRN1", "PTRN2", "PUZP1", "PUZP2", "PZIP1", "PZIP2",
|
||||||
|
"PPFALSE", "PPFIRST", "PPNEXT", "PPTEST", "PPTRUE", "PPUNPKHI", "PPUNPKLO",
|
||||||
|
"PRDFFR", "PRDFFRS", "PWRFFR", "PREV", "SETFFR",
|
||||||
|
"PWHILEGE", "PWHILEGT", "PWHILEHI", "PWHILEHS",
|
||||||
|
"PWHILELE", "PWHILELO", "PWHILELS", "PWHILELT", "PWHILERW", "PWHILEWR",
|
||||||
|
// The SVE2.1 Z-alias families, in first-occurrence order.
|
||||||
|
"ZABS",
|
||||||
|
"ZREVB",
|
||||||
|
"ZREVH",
|
||||||
|
"ZSXTB",
|
||||||
|
"ZSXTH",
|
||||||
|
"ZUXTB",
|
||||||
|
"ZUXTH",
|
||||||
|
"ZADD",
|
||||||
|
"ZAND",
|
||||||
|
"ZBIC",
|
||||||
|
"ZCLS",
|
||||||
|
"ZCLZ",
|
||||||
|
"ZCNOT",
|
||||||
|
"ZCNT",
|
||||||
|
"ZCOMPACT",
|
||||||
|
"ZDECP",
|
||||||
|
"ZDUP",
|
||||||
|
"ZEOR",
|
||||||
|
"ZEXPAND",
|
||||||
|
"ZINSR",
|
||||||
|
"ZLASTA",
|
||||||
|
"ZLASTB",
|
||||||
|
"ZMOVPRFX",
|
||||||
|
"ZREVD",
|
||||||
|
"ZREVW",
|
||||||
|
"ZSXTW",
|
||||||
|
"ZUXTW",
|
||||||
|
"ZNEG",
|
||||||
|
"ZNOT",
|
||||||
|
"ZORR",
|
||||||
|
"ZRBIT",
|
||||||
|
"ZREV",
|
||||||
|
"ZSEL",
|
||||||
|
"ZSQABS",
|
||||||
|
"ZSQNEG",
|
||||||
|
"ZSUB",
|
||||||
|
"ZSUBR",
|
||||||
|
"ZSUNPKHI",
|
||||||
|
"ZSUNPKLO",
|
||||||
|
"ZTBX",
|
||||||
|
"ZTBXQ",
|
||||||
|
"ZTRN1",
|
||||||
|
"ZTRN2",
|
||||||
|
"ZUUNPKHI",
|
||||||
|
"ZUUNPKLO",
|
||||||
|
"ZUZP1",
|
||||||
|
"ZUZP2",
|
||||||
|
"ZUZPQ1",
|
||||||
|
"ZUZPQ2",
|
||||||
|
"ZZIP1",
|
||||||
|
"ZZIP2",
|
||||||
|
"ZZIPQ1",
|
||||||
|
"ZZIPQ2",
|
||||||
|
// The stage-four families: the SVE2.1 narrowing two-to-one set, the
|
||||||
|
// pairwise forms and the quadword reductions.
|
||||||
|
"ZADDHNB",
|
||||||
|
"ZADDHNT",
|
||||||
|
"ZRADDHNB",
|
||||||
|
"ZRADDHNT",
|
||||||
|
"ZRSUBHNB",
|
||||||
|
"ZRSUBHNT",
|
||||||
|
"ZSUBHNB",
|
||||||
|
"ZSUBHNT",
|
||||||
|
"ZADDP",
|
||||||
|
"ZADDPT",
|
||||||
|
"ZADDQV",
|
||||||
|
"ZANDQV",
|
||||||
|
"ZEORQV",
|
||||||
|
"ZFADDQV",
|
||||||
|
"ZFMAXNMQV",
|
||||||
|
"ZFMAXQV",
|
||||||
|
"ZFMINNMQV",
|
||||||
|
"ZFMINQV",
|
||||||
|
"ZORQV",
|
||||||
|
"ZSMAXQV",
|
||||||
|
"ZSMINQV",
|
||||||
|
"ZUMAXQV",
|
||||||
|
"ZUMINQV",
|
||||||
|
"ZASR",
|
||||||
|
"ZASRR",
|
||||||
|
"ZLSL",
|
||||||
|
"ZLSLR",
|
||||||
|
"ZLSR",
|
||||||
|
"ZLSRR",
|
||||||
|
"ZBCAX",
|
||||||
|
"ZBDEP",
|
||||||
|
"ZBEXT",
|
||||||
|
"ZBGRP",
|
||||||
|
"ZBSL",
|
||||||
|
"ZBSL1N",
|
||||||
|
"ZBSL2N",
|
||||||
|
"ZEOR3",
|
||||||
|
"ZEORBT",
|
||||||
|
"ZEORTB",
|
||||||
|
"ZNBSL",
|
||||||
|
"ZBF1CVT",
|
||||||
|
"ZBF1CVTLT",
|
||||||
|
"ZBF2CVT",
|
||||||
|
"ZBF2CVTLT",
|
||||||
|
"ZF1CVT",
|
||||||
|
"ZF1CVTLT",
|
||||||
|
"ZF2CVT",
|
||||||
|
"ZF2CVTLT",
|
||||||
|
"ZBFADD",
|
||||||
|
"ZBFCLAMP",
|
||||||
|
"ZBFMAX",
|
||||||
|
"ZBFMAXNM",
|
||||||
|
"ZBFMIN",
|
||||||
|
"ZBFMINNM",
|
||||||
|
"ZBFMUL",
|
||||||
|
"ZBFSUB",
|
||||||
|
"ZBFCVT",
|
||||||
|
"ZBFCVTNT",
|
||||||
|
"ZBFDOT",
|
||||||
|
"ZBFMLA",
|
||||||
|
"ZBFMLALB",
|
||||||
|
"ZBFMLALT",
|
||||||
|
"ZBFMLS",
|
||||||
|
"ZBFMLSLB",
|
||||||
|
"ZBFMLSLT",
|
||||||
|
"ZBFMMLA",
|
||||||
|
"ZBFSCALE",
|
||||||
|
"ZCLASTA",
|
||||||
|
"ZCLASTB",
|
||||||
|
"ZCMPEQ",
|
||||||
|
"ZCMPGE",
|
||||||
|
"ZCMPGT",
|
||||||
|
"ZCMPHI",
|
||||||
|
"ZCMPHS",
|
||||||
|
"ZCMPNE",
|
||||||
|
"ADDPL",
|
||||||
|
"ADDVL",
|
||||||
|
"RDVL",
|
||||||
|
"ZLD2B",
|
||||||
|
"ZLD2D",
|
||||||
|
"ZLD2H",
|
||||||
|
"ZLD2Q",
|
||||||
|
"ZLD2W",
|
||||||
|
"ZLD3B",
|
||||||
|
"ZLD3D",
|
||||||
|
"ZLD3H",
|
||||||
|
"ZLD3Q",
|
||||||
|
"ZLD3W",
|
||||||
|
"ZLD4B",
|
||||||
|
"ZLD4D",
|
||||||
|
"ZLD4H",
|
||||||
|
"ZLD4Q",
|
||||||
|
"ZLD4W",
|
||||||
|
"ZST2B",
|
||||||
|
"ZST2D",
|
||||||
|
"ZST2H",
|
||||||
|
"ZST2Q",
|
||||||
|
"ZST2W",
|
||||||
|
"ZST3B",
|
||||||
|
"ZST3D",
|
||||||
|
"ZST3H",
|
||||||
|
"ZST3Q",
|
||||||
|
"ZST3W",
|
||||||
|
"ZST4B",
|
||||||
|
"ZST4D",
|
||||||
|
"ZST4H",
|
||||||
|
"ZST4Q",
|
||||||
|
"ZST4W",
|
||||||
|
// The stage-three families: the SVE2 crypto group, the predicate
|
||||||
|
// counters and loop terminators with the 32-bit while compares, and
|
||||||
|
// the reductions into a SIMD register.
|
||||||
|
"ZADCLB",
|
||||||
|
"ZADCLT",
|
||||||
|
"ZSBCLB",
|
||||||
|
"ZSBCLT",
|
||||||
|
"ZRAX1",
|
||||||
|
"ZSM4EKEY",
|
||||||
|
"ZSM4E",
|
||||||
|
"ZAESD",
|
||||||
|
"ZAESE",
|
||||||
|
"ZAESIMC",
|
||||||
|
"ZAESMC",
|
||||||
|
"CTERMEQ",
|
||||||
|
"CTERMEQW",
|
||||||
|
"CTERMNE",
|
||||||
|
"CTERMNEW",
|
||||||
|
"PCNTP",
|
||||||
|
"PFIRSTP",
|
||||||
|
"PLASTP",
|
||||||
|
"PDECP",
|
||||||
|
"PINCP",
|
||||||
|
"PSQDECP",
|
||||||
|
"PSQINCP",
|
||||||
|
"PUQDECP",
|
||||||
|
"PUQINCP",
|
||||||
|
"PUQDECPW",
|
||||||
|
"PUQINCPW",
|
||||||
|
"PSQDECPW",
|
||||||
|
"PSQINCPW",
|
||||||
|
"PWHILEGEW",
|
||||||
|
"PWHILEGTW",
|
||||||
|
"PWHILEHIW",
|
||||||
|
"PWHILEHSW",
|
||||||
|
"PWHILELEW",
|
||||||
|
"PWHILELOW",
|
||||||
|
"PWHILELSW",
|
||||||
|
"PWHILELTW",
|
||||||
|
"ZSADDVD",
|
||||||
|
"ZUADDVD",
|
||||||
|
"ZANDVB",
|
||||||
|
"ZANDVH",
|
||||||
|
"ZANDVS",
|
||||||
|
"ZANDVD",
|
||||||
|
"ZEORVB",
|
||||||
|
"ZEORVH",
|
||||||
|
"ZEORVS",
|
||||||
|
"ZEORVD",
|
||||||
|
"ZORVB",
|
||||||
|
"ZORVH",
|
||||||
|
"ZORVS",
|
||||||
|
"ZORVD",
|
||||||
|
"ZSMAXVB",
|
||||||
|
"ZSMAXVH",
|
||||||
|
"ZSMAXVS",
|
||||||
|
"ZSMAXVD",
|
||||||
|
"ZSMINVB",
|
||||||
|
"ZSMINVH",
|
||||||
|
"ZSMINVS",
|
||||||
|
"ZSMINVD",
|
||||||
|
"ZUMAXVB",
|
||||||
|
"ZUMAXVH",
|
||||||
|
"ZUMAXVS",
|
||||||
|
"ZUMAXVD",
|
||||||
|
"ZUMINVB",
|
||||||
|
"ZUMINVH",
|
||||||
|
"ZUMINVS",
|
||||||
|
"ZUMINVD",
|
||||||
|
"ZFADDVH",
|
||||||
|
"ZFADDVS",
|
||||||
|
"ZFADDVD",
|
||||||
|
"ZFMAXNMVH",
|
||||||
|
"ZFMAXNMVS",
|
||||||
|
"ZFMAXNMVD",
|
||||||
|
"ZFMAXVH",
|
||||||
|
"ZFMAXVS",
|
||||||
|
"ZFMAXVD",
|
||||||
|
"ZFMINNMVH",
|
||||||
|
"ZFMINNMVS",
|
||||||
|
"ZFMINNMVD",
|
||||||
|
"ZFMINVH",
|
||||||
|
"ZFMINVS",
|
||||||
|
"ZFMINVD",
|
||||||
|
"ZFADDAH",
|
||||||
|
"ZFADDAS",
|
||||||
|
"ZFADDAD",
|
||||||
|
// The gather loads and scatter stores: plain, sign-extended and
|
||||||
|
// first-fault loads, and the stores, in first-occurrence order.
|
||||||
|
"ZLD1B",
|
||||||
|
"ZLD1D",
|
||||||
|
"ZLD1H",
|
||||||
|
"ZLD1SB",
|
||||||
|
"ZLD1SH",
|
||||||
|
"ZLD1SW",
|
||||||
|
"ZLD1W",
|
||||||
|
"ZLDFF1B",
|
||||||
|
"ZLDFF1D",
|
||||||
|
"ZLDFF1H",
|
||||||
|
"ZLDFF1SB",
|
||||||
|
"ZLDFF1SH",
|
||||||
|
"ZLDFF1SW",
|
||||||
|
"ZLDFF1W",
|
||||||
|
"ZST1B",
|
||||||
|
"ZST1D",
|
||||||
|
"ZST1H",
|
||||||
|
"ZST1W",
|
||||||
|
// The shift-immediate classes: the narrowing and widening
|
||||||
|
// three-vector shifts and the predicated saturating left shifts,
|
||||||
|
// in first-occurrence order.
|
||||||
|
"ZSQSHRUNB",
|
||||||
|
"ZSQSHRUNT",
|
||||||
|
"ZSHRNB",
|
||||||
|
"ZSHRNT",
|
||||||
|
"ZRSHRNB",
|
||||||
|
"ZRSHRNT",
|
||||||
|
"ZSQSHRNB",
|
||||||
|
"ZSQSHRNT",
|
||||||
|
"ZSQRSHRNB",
|
||||||
|
"ZSQRSHRNT",
|
||||||
|
"ZUQSHRNB",
|
||||||
|
"ZUQSHRNT",
|
||||||
|
"ZUQRSHRNB",
|
||||||
|
"ZUQRSHRNT",
|
||||||
|
"ZSSHLLB",
|
||||||
|
"ZSSHLLT",
|
||||||
|
"ZUSHLLB",
|
||||||
|
"ZUSHLLT",
|
||||||
|
"ZSQSHL",
|
||||||
|
"ZSQSHLU",
|
||||||
|
"ZUQSHL",
|
||||||
|
"ZSRI",
|
||||||
|
"ZSSRA",
|
||||||
|
"ZUSRA",
|
||||||
|
"ZSRSRA",
|
||||||
|
"ZURSRA",
|
||||||
|
"ZASRD",
|
||||||
|
"ZXAR",
|
||||||
|
}
|
||||||
|
got := ExtensionNames(arch.ARM64)
|
||||||
|
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||||
|
t.Errorf("ExtensionNames(ARM64) = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if n := len(arch.Extensions(arch.ARM64)); n != 537 {
|
||||||
|
t.Errorf("the family registers %d instructions, want 537", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flagListShapes are the TEXT shapes the flags operand decides: joined
|
||||||
|
// names, the legacy numeric spellings, the arithmetic and immediate forms,
|
||||||
|
// each with a frame size beside it. A flag the operand carries suppresses
|
||||||
|
// the stack-split guard; the toolchain reads one integer from the operand,
|
||||||
|
// and after the front end folds it the bytes must not tell the difference.
|
||||||
|
// The bodies are leaf-shaped on purpose: the frame engine's open divergences
|
||||||
|
// (the NOFRAME prologue for a non-leaf, the big-frame guard's shape) sit
|
||||||
|
// outside the flags operand and are somebody else's gap list.
|
||||||
|
var flagListShapes = []struct {
|
||||||
|
name string // the subtest's name
|
||||||
|
flags string // the flags operand as written
|
||||||
|
frame string // the frame operand as written
|
||||||
|
body string // the function body
|
||||||
|
}{
|
||||||
|
{"name", "NOSPLIT", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"numeric", "4", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"numericOR", "2|4", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"joinedFrame", "DUPOK|NOSPLIT", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"joinedFrameless", "NOSPLIT|TOPFRAME", "$0-0", "\tRET\n"},
|
||||||
|
{"parenthesised", "(NOSPLIT|NOFRAME)", "$0-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFlagListAssemblyPathRejectsUnknown holds the front end's rejections
|
||||||
|
// that mirror the toolchain's: an identifier outside the flag table
|
||||||
|
// ("unexpected TYPO evaluating expression") and the immediate spelling of
|
||||||
|
// the operand ("TEXT: expected integer constant"), each refused on the
|
||||||
|
// assembly path before the encoder ever sees a tree.
|
||||||
|
func TestFlagListAssemblyPathRejectsUnknown(t *testing.T) {
|
||||||
|
for _, c := range []struct{ src, want string }{
|
||||||
|
{"#include \"textflag.h\"\n\nTEXT f(SB), NOSPLIT|TYPO, $0-0\n\tRET\n",
|
||||||
|
"unexpected TYPO evaluating expression"},
|
||||||
|
{"#include \"textflag.h\"\n\nTEXT f(SB), $NOSPLIT, $0-0\n\tRET\n",
|
||||||
|
"TEXT: expected integer constant; found $NOSPLIT"},
|
||||||
|
{"#include \"textflag.h\"\n\nGLOBL g<>(SB), $8, $8\n",
|
||||||
|
"GLOBL: expected integer constant; found $8"},
|
||||||
|
} {
|
||||||
|
_, errs := parser.ParseWithOptions("f.s", c.src, parser.Options{Expand: true})
|
||||||
|
if len(errs) == 0 || !strings.Contains(errs[0].Error(), c.want) {
|
||||||
|
t.Errorf("errors = %v, want %q", errs, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFlagListByteParity assembles every flag-list shape through the fixed
|
||||||
|
// front end and holds the function's bytes equal to the installed
|
||||||
|
// toolchain's, no guard words where the flag suppresses them and none
|
||||||
|
// missing where it demands one.
|
||||||
|
func TestFlagListByteParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
out, err := exec.Command(goBin, "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("go env GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
include := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include")
|
||||||
|
|
||||||
|
for _, c := range flagListShapes {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT f(SB), " + c.flags + ", " + c.frame + "\n" + c.body
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "f.s")
|
||||||
|
if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
af, errs := parser.ParseWithOptions(file, src, parser.Options{Expand: true})
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(af)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("%d functions, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
|
||||||
|
objPath := filepath.Join(t.TempDir(), "oracle.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm", "-std", "-I", include,
|
||||||
|
"-p", "flaglisttest", "-o", objPath, file)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
if oout, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("go tool asm: %v\n%s", err, oout)
|
||||||
|
}
|
||||||
|
blocks, ok := oracleFuncText(t, mustRead(t, objPath))["f"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("the oracle output carries no f")
|
||||||
|
}
|
||||||
|
if len(blocks) != 1 {
|
||||||
|
t.Fatalf("%d text blocks for f, want 1", len(blocks))
|
||||||
|
}
|
||||||
|
goCode := blocks[0]
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
t.Errorf("bytes differ:\ngasm: % x\ngo: % x", gasmCode[:cmpLen], goCode[:cmpLen])
|
||||||
|
}
|
||||||
|
if len(goCode) > len(gasmCode) {
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Errorf("non-zero trailing bytes in the oracle output")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
kindSTEXT = 1
|
kindSTEXT = 1
|
||||||
kindSRODATA = 3
|
kindSRODATA = 3
|
||||||
|
kindSNOPTRDATA = 5
|
||||||
kindSDATA = 7
|
kindSDATA = 7
|
||||||
kindSDWARFFCN = 14
|
kindSDWARFFCN = 14
|
||||||
kindSDWARFLINES = 20
|
kindSDWARFLINES = 20
|
||||||
@@ -305,9 +306,16 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
if !d.Static {
|
if !d.Static {
|
||||||
name = pkgPath + "." + name
|
name = pkgPath + "." + name
|
||||||
}
|
}
|
||||||
|
// RODATA implies no pointers, so it wins over NOPTR: the kind is
|
||||||
|
// SRODATA either way, exactly as the toolchain chooses it. Plain
|
||||||
|
// NOPTR data is SNOPTRDATA, which the linker keeps out of the GC's
|
||||||
|
// type scan; a plain SDATA symbol would demand Go type information
|
||||||
|
// no assembly file can supply, and the link would fail.
|
||||||
typ := uint8(kindSDATA)
|
typ := uint8(kindSDATA)
|
||||||
if d.Rodata {
|
if d.Rodata {
|
||||||
typ = kindSRODATA
|
typ = kindSRODATA
|
||||||
|
} else if d.Noptr {
|
||||||
|
typ = kindSNOPTRDATA
|
||||||
}
|
}
|
||||||
flag := uint8(0)
|
flag := uint8(0)
|
||||||
if d.Dupok {
|
if d.Dupok {
|
||||||
@@ -463,6 +471,61 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The data symbols' own relocations: the symbol-valued DATA fields
|
||||||
|
// ("DATA s+0(SB)/8, $other(SB)"). The toolchain patches each field
|
||||||
|
// with the target's absolute address through an R_ADDR of the DATA
|
||||||
|
// line's width, on every architecture (the code relocations are
|
||||||
|
// per-architecture PC-relative shapes; a data pointer word is not), so
|
||||||
|
// this mapping bypasses relocField. The definitions were appended in
|
||||||
|
// DataSyms order, so data symbol i is definition index i.
|
||||||
|
for i, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
if r.Kind != RelAddr {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: data symbol %q carries a non-data relocation", d.Name)
|
||||||
|
}
|
||||||
|
var rec [23]byte
|
||||||
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||||
|
rec[4] = r.Siz
|
||||||
|
binary.LittleEndian.PutUint16(rec[5:], relocAddr)
|
||||||
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||||
|
switch {
|
||||||
|
case r.External && r.Name == goobjBuiltinMorestack:
|
||||||
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxBuiltin)
|
||||||
|
binary.LittleEndian.PutUint32(rec[19:], goobjBuiltinMorestackNoctxt)
|
||||||
|
case r.External:
|
||||||
|
pkg, name := splitQualified(r.Name)
|
||||||
|
if pkg == "" {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: external symbol %q has no package prefix", r.Name)
|
||||||
|
}
|
||||||
|
pIdx, ok := extPkgIdx[pkg]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: package %q not resolved", pkg)
|
||||||
|
}
|
||||||
|
sIdx, ok := extSymIdx[pkg+"·"+name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: symbol %s·%s not resolved", pkg, name)
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
|
||||||
|
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
|
||||||
|
default:
|
||||||
|
if di, ok := defIdx[r.Name]; ok {
|
||||||
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
|
||||||
|
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// A DATA field may hold the address of a TEXT function of
|
||||||
|
// the same file (the rt0 lib entry spelling), which is a
|
||||||
|
// non-package definition.
|
||||||
|
ni, isText := textNpIdx[r.Name]
|
||||||
|
if !isText {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxNone)
|
||||||
|
binary.LittleEndian.PutUint32(rec[19:], uint32(ni))
|
||||||
|
}
|
||||||
|
symRelocs[i] = append(symRelocs[i], rec[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
// The DWARF symbols' own relocations (the function address references).
|
// The DWARF symbols' own relocations (the function address references).
|
||||||
for _, ds := range dwarfRelocs {
|
for _, ds := range dwarfRelocs {
|
||||||
for _, r := range ds.relocs {
|
for _, r := range ds.relocs {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ func TestReadRuntimeSymbols(t *testing.T) {
|
|||||||
// TestResolveExternalSymbols verifies end-to-end resolution of external
|
// TestResolveExternalSymbols verifies end-to-end resolution of external
|
||||||
// symbol references.
|
// symbol references.
|
||||||
func TestResolveExternalSymbols(t *testing.T) {
|
func TestResolveExternalSymbols(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("resolves through a live go list -export: skipped in -short mode")
|
||||||
|
}
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
if _, err := exec.LookPath("go"); err != nil {
|
||||||
t.Skip("go toolchain not available")
|
t.Skip("go toolchain not available")
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-1
@@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// goobjView is a minimal parsed view of a GOOBJ payload, enough to check
|
// goobjView is a minimal parsed view of a GOOBJ payload, enough to check
|
||||||
@@ -514,6 +514,145 @@ func fieldAfter(line, flag string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildLogSteps is what a substitution test needs from a `go build -x -work`
|
||||||
|
// log: the work directory, the assembler's object, the package archive and
|
||||||
|
// the link command line.
|
||||||
|
type buildLogSteps struct {
|
||||||
|
work string
|
||||||
|
asmObj string // $WORK expanded
|
||||||
|
pkgArch string // $WORK expanded
|
||||||
|
linkLine string // still carries $WORK placeholders
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBuildLog extracts the build steps from a `go build -x -work` log.
|
||||||
|
// asmFile names the assembly file whose object the test substitutes. A
|
||||||
|
// missing step is a failure, not a skip: the toolchain changed shape and the
|
||||||
|
// substitution would silently test nothing.
|
||||||
|
func parseBuildLog(t *testing.T, log []byte, asmFile string) buildLogSteps {
|
||||||
|
t.Helper()
|
||||||
|
var st buildLogSteps
|
||||||
|
for line := range strings.SplitSeq(string(log), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "WORK="):
|
||||||
|
st.work = strings.TrimPrefix(line, "WORK=")
|
||||||
|
case strings.Contains(line, "/asm ") && strings.Contains(line, asmFile) && !strings.Contains(line, "-gensymabis"):
|
||||||
|
st.asmObj = fieldAfter(line, "-o")
|
||||||
|
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
|
||||||
|
rest := strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
|
||||||
|
st.pkgArch = strings.Fields(strings.SplitN(rest, "#", 2)[0])[0]
|
||||||
|
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||||
|
st.linkLine = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if st.work == "" || st.asmObj == "" || st.pkgArch == "" || st.linkLine == "" {
|
||||||
|
t.Fatalf("could not locate the build steps (work=%q asmObj=%q pkgArch=%q link=%q):\n%s",
|
||||||
|
st.work, st.asmObj, st.pkgArch, st.linkLine, log)
|
||||||
|
}
|
||||||
|
st.asmObj = strings.ReplaceAll(st.asmObj, "$WORK", st.work)
|
||||||
|
st.pkgArch = strings.ReplaceAll(st.pkgArch, "$WORK", st.work)
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
// substituteAndRelink swaps the gasm object into the package archive the
|
||||||
|
// baseline build produced and re-runs the captured link line against the
|
||||||
|
// rebuilt archive, writing the binary to outBin (the -x log's link step
|
||||||
|
// always targets the action graph's internal a.out, which the helper
|
||||||
|
// redirects; the copy to the -o target is a separate build action the helper
|
||||||
|
// does not need). The archive handed to the linker is proven to carry the
|
||||||
|
// gasm object byte for byte, so a build-layout change that skipped the
|
||||||
|
// substitution fails here instead of passing vacuously.
|
||||||
|
func substituteAndRelink(t *testing.T, goBin, dir string, st buildLogSteps, outBin string, gasmObj []byte, extraEnv ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// The deliberate-run boundary: this path drives a real `go build` and
|
||||||
|
// cmd/link per invocation, minutes-scale work on the small single-core
|
||||||
|
// CI runner. Under -short (the push pipeline's mode) it skips; the
|
||||||
|
// local test gate and the dispatched workflows run it in full.
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("end-to-end go build and link: skipped in -short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the archive, overwrite the assembler's member with the gasm
|
||||||
|
// object and repack (go tool pack has no replace-in-place).
|
||||||
|
membersDir := filepath.Join(dir, "members")
|
||||||
|
if err := os.MkdirAll(membersDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extract := exec.Command(goBin, "tool", "pack", "x", st.pkgArch)
|
||||||
|
extract.Dir = membersDir
|
||||||
|
if out, err := extract.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
member := filepath.Join(membersDir, filepath.Base(st.asmObj))
|
||||||
|
if _, err := os.Stat(member); err != nil {
|
||||||
|
t.Fatalf("the assembler's archive member was not extracted: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(member, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(member, gasmObj, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
listCmd := exec.Command(goBin, "tool", "pack", "t", st.pkgArch)
|
||||||
|
listOut, err := listCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pack t: %v\n%s", err, listOut)
|
||||||
|
}
|
||||||
|
newArch := filepath.Join(dir, "pkg.a")
|
||||||
|
args := []string{"tool", "pack", "c", newArch}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for m := range strings.FieldsSeq(string(listOut)) {
|
||||||
|
if seen[m] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[m] = true
|
||||||
|
if err := os.Chmod(filepath.Join(membersDir, m), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args = append(args, m)
|
||||||
|
}
|
||||||
|
pack := exec.Command(goBin, args...)
|
||||||
|
pack.Dir = membersDir
|
||||||
|
if out, err := pack.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack c: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prove the substitution: the archive the linker is about to consume
|
||||||
|
// holds the gasm object, byte for byte.
|
||||||
|
checkDir := filepath.Join(dir, "check")
|
||||||
|
if err := os.MkdirAll(checkDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
check := exec.Command(goBin, "tool", "pack", "x", newArch)
|
||||||
|
check.Dir = checkDir
|
||||||
|
if out, err := check.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x (verification): %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
got, err := os.ReadFile(filepath.Join(checkDir, filepath.Base(st.asmObj)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read the substituted member back: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, gasmObj) {
|
||||||
|
t.Fatal("the repacked archive does not carry the gasm object")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-link. The line carries a GOROOT assignment and $WORK placeholders;
|
||||||
|
// GOEXPERIMENT must match the toolchain's own, because the linker
|
||||||
|
// compares the object header against its configuration.
|
||||||
|
goExp, _ := exec.Command(goBin, "env", "GOEXPERIMENT").Output()
|
||||||
|
linkLine := strings.ReplaceAll(st.linkLine, "$WORK", st.work)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "_pkg_.a"), newArch)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "exe", "a.out"), outBin)
|
||||||
|
env := append(os.Environ(), "GOEXPERIMENT="+strings.TrimSpace(string(goExp)))
|
||||||
|
env = append(env, extraEnv...)
|
||||||
|
link := exec.Command("sh", "-c", linkLine)
|
||||||
|
link.Dir = dir
|
||||||
|
link.Env = env
|
||||||
|
if out, err := link.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("link with the gasm object: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestGOObjectExternalPackageLink is the cross-package end-to-end check: a
|
// TestGOObjectExternalPackageLink is the cross-package end-to-end check: a
|
||||||
// GOOBJ whose code references a real external package symbol (runtime's
|
// GOOBJ whose code references a real external package symbol (runtime's
|
||||||
// morestack, a plain reference rather than the builtin noctxt form) must
|
// morestack, a plain reference rather than the builtin noctxt form) must
|
||||||
@@ -524,6 +663,9 @@ func fieldAfter(line, flag string) string {
|
|||||||
// failed. The binary is not run: morestack returns to the call site's
|
// failed. The binary is not run: morestack returns to the call site's
|
||||||
// stack check, which a hand-written caller has none of.
|
// stack check, which a hand-written caller has none of.
|
||||||
func TestGOObjectExternalPackageLink(t *testing.T) {
|
func TestGOObjectExternalPackageLink(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("end-to-end go build and link: skipped in -short mode")
|
||||||
|
}
|
||||||
goBin, err := exec.LookPath("go")
|
goBin, err := exec.LookPath("go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("no Go toolchain available")
|
t.Skip("no Go toolchain available")
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
return relocArm64Branch, 4
|
return relocArm64Branch, 4
|
||||||
case RelArm64LDST64:
|
case RelArm64LDST64:
|
||||||
return relocArm64LDST64, 8
|
return relocArm64LDST64, 8
|
||||||
|
case RelArm64TLSLE:
|
||||||
|
return relocArm64TLSLE, 4
|
||||||
default:
|
default:
|
||||||
return relocArm64Addr, 8
|
return relocArm64Addr, 8
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,7 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
const (
|
const (
|
||||||
relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair
|
relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair
|
||||||
relocArm64Branch = 9 // R_CALLARM64, BL instruction
|
relocArm64Branch = 9 // R_CALLARM64, BL instruction
|
||||||
|
relocArm64TLSLE = 32 // R_ARM64_TLS_LE, MOVZ local-exec TLS load
|
||||||
relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair
|
relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -30,6 +30,8 @@ func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
return relocRISCVPcrelStype, 8
|
return relocRISCVPcrelStype, 8
|
||||||
case RelRISCVJal:
|
case RelRISCVJal:
|
||||||
return relocRISCVJal, 4
|
return relocRISCVJal, 4
|
||||||
|
case RelRISCVTLSLE:
|
||||||
|
return relocRISCVTLSLE, 8
|
||||||
default:
|
default:
|
||||||
return relocRISCVPcrelItype, 8
|
return relocRISCVPcrelItype, 8
|
||||||
}
|
}
|
||||||
@@ -38,11 +40,13 @@ func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
|
|
||||||
// RISC-V relocation types (cmd/internal/objabi). The Go linker applies
|
// RISC-V relocation types (cmd/internal/objabi). The Go linker applies
|
||||||
// R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a
|
// R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a
|
||||||
// single 8-byte field; R_RISCV_JAL covers a single 4-byte J-type instruction.
|
// single 8-byte field; R_RISCV_JAL covers a single 4-byte J-type instruction;
|
||||||
|
// R_RISCV_TLS_LE covers the LUI + I-type pair of a local-exec TLS reference.
|
||||||
const (
|
const (
|
||||||
relocRISCVJal = 59 // R_RISCV_JAL
|
relocRISCVJal = 59 // R_RISCV_JAL
|
||||||
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
||||||
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
||||||
|
relocRISCVTLSLE = 65 // R_RISCV_TLS_LE
|
||||||
)
|
)
|
||||||
|
|
||||||
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
||||||
|
|||||||
+6
-6
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The expected bytes are pinned from `go tool asm` output (Go 1.27, amd64,
|
// The expected bytes are pinned from `go tool asm` output (Go 1.27, amd64,
|
||||||
@@ -307,12 +307,12 @@ func TestStackGuardBytesLOONG64(t *testing.T) {
|
|||||||
func TestStackGuardGOObjInternalCall(t *testing.T) {
|
func TestStackGuardGOObjInternalCall(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
src string
|
src string
|
||||||
assemble func(*ast.File) (*Image, error)
|
assemble func(*ast.File, ...AssembleOption) (*Image, error)
|
||||||
}{
|
}{
|
||||||
{"g_amd64.s", AssembleFile},
|
{"g_amd64.s", AssembleFile},
|
||||||
{"g_arm64.s", AssembleFileARM64},
|
{"g_arm64.s", func(f *ast.File, _ ...AssembleOption) (*Image, error) { return AssembleFileARM64(f) }},
|
||||||
{"g_riscv64.s", AssembleFileRISCV},
|
{"g_riscv64.s", func(f *ast.File, _ ...AssembleOption) (*Image, error) { return AssembleFileRISCV(f) }},
|
||||||
{"g_loong64.s", AssembleFileLOONG64},
|
{"g_loong64.s", func(f *ast.File, _ ...AssembleOption) (*Image, error) { return AssembleFileLOONG64(f) }},
|
||||||
} {
|
} {
|
||||||
f, errs := parser.Parse(tt.src, "TEXT \u00b7callsmall(SB), $16-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n")
|
f, errs := parser.Parse(tt.src, "TEXT \u00b7callsmall(SB), $16-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
|
|||||||
+374
-45
@@ -65,6 +65,15 @@ var bitTestOp = map[string]int{
|
|||||||
// noOperandTable maps a fixed no-operand mnemonic to its opcode bytes. The
|
// noOperandTable maps a fixed no-operand mnemonic to its opcode bytes. The
|
||||||
// fence names carry their opcode inside the 0F AE /digit group spelled out in
|
// fence names carry their opcode inside the 0F AE /digit group spelled out in
|
||||||
// full (E8/F0/F8), and PAUSE is F3 90.
|
// full (E8/F0/F8), and PAUSE is F3 90.
|
||||||
|
//
|
||||||
|
// LOCK, REP and REPN are the prefix statements. go tool asm encodes each as
|
||||||
|
// a standalone one-byte instruction with a PC of its own (F0, F3 and F2
|
||||||
|
// respectively), not as a prefix field merged into the next instruction: the
|
||||||
|
// statement that follows is encoded unaware of it, and nothing validates
|
||||||
|
// that the pairing is a legal one (LOCK before NOP assembles without
|
||||||
|
// complaint, each byte pinned against the toolchain). Because the bytes
|
||||||
|
// land in the stream before the following statement anyway, a LOCKed
|
||||||
|
// CMPXCHGQ encodes identically to a prefixed form.
|
||||||
var noOperandTable = map[string][]byte{
|
var noOperandTable = map[string][]byte{
|
||||||
"CPUID": {0x0F, 0xA2},
|
"CPUID": {0x0F, 0xA2},
|
||||||
"RDTSC": {0x0F, 0x31},
|
"RDTSC": {0x0F, 0x31},
|
||||||
@@ -78,6 +87,43 @@ var noOperandTable = map[string][]byte{
|
|||||||
"MFENCE": {0x0F, 0xAE, 0xF0},
|
"MFENCE": {0x0F, 0xAE, 0xF0},
|
||||||
"SFENCE": {0x0F, 0xAE, 0xF8},
|
"SFENCE": {0x0F, 0xAE, 0xF8},
|
||||||
"UNDEF": {0x0F, 0x0B},
|
"UNDEF": {0x0F, 0x0B},
|
||||||
|
"LOCK": {0xF0},
|
||||||
|
"REP": {0xF3},
|
||||||
|
"REPN": {0xF2},
|
||||||
|
"ENDBR64": {0xF3, 0x0F, 0x1E, 0xFA},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysUnaryTable maps the one-operand system instructions to their bytes:
|
||||||
|
// the prefix, the opcode and the /digit the reg field carries. The operand
|
||||||
|
// is a register or memory in r/m.
|
||||||
|
var sysUnaryTable = map[string]struct {
|
||||||
|
prefix byte
|
||||||
|
opcode []byte
|
||||||
|
digit int
|
||||||
|
}{
|
||||||
|
"CLWB": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"TPAUSE": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"UMONITOR": {0xF3, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"UMWAIT": {0xF2, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"RDPID": {0xF3, []byte{0x0F, 0xC7}, 7},
|
||||||
|
"CLDEMOTE": {0x00, []byte{0x0F, 0x1C}, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSysUnary emits a one-operand system instruction: the operand in r/m
|
||||||
|
// under the fixed /digit, no REX.W.
|
||||||
|
func (e *enc) encodeSysUnary(mnem string, m struct {
|
||||||
|
prefix byte
|
||||||
|
opcode []byte
|
||||||
|
digit int
|
||||||
|
}, ops []Operand) error {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: m.opcode, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MOV --------------------------------------------------------------------
|
// --- MOV --------------------------------------------------------------------
|
||||||
@@ -99,6 +145,102 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
|||||||
// silently emit REX.W 8B with the wrong operand meaning.
|
// silently emit REX.W 8B with the wrong operand meaning.
|
||||||
_, srcVec := vecReg(src)
|
_, srcVec := vecReg(src)
|
||||||
dstReg, dstVec := vecReg(dst)
|
dstReg, dstVec := vecReg(dst)
|
||||||
|
|
||||||
|
// Control and debug register moves: 0F 20 (CRn→r64), 0F 22 (r64→CRn),
|
||||||
|
// 0F 21 (DRn→r64) and 0F 23 (r64→DRn). The CR/DR number rides the reg
|
||||||
|
// field, the general register r/m; CR8+/DR8+ take REX.R.
|
||||||
|
if c, ok := src.(Reg); ok && c.ctl != 0 {
|
||||||
|
g, ok := dst.(Reg)
|
||||||
|
if !ok || g.isVec() || g.ctl != 0 {
|
||||||
|
return fmt.Errorf("MOV: control/debug register load needs a general register destination")
|
||||||
|
}
|
||||||
|
opc := byte(0x20)
|
||||||
|
if c.ctl == 2 {
|
||||||
|
opc = 0x21
|
||||||
|
}
|
||||||
|
return e.emit(&instr{
|
||||||
|
opcode: []byte{0x0F, opc},
|
||||||
|
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||||
|
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if c, ok := dst.(Reg); ok && c.ctl != 0 {
|
||||||
|
g, ok := src.(Reg)
|
||||||
|
if !ok || g.isVec() || g.ctl != 0 {
|
||||||
|
return fmt.Errorf("MOV: control/debug register store needs a general register source")
|
||||||
|
}
|
||||||
|
opc := byte(0x22)
|
||||||
|
if c.ctl == 2 {
|
||||||
|
opc = 0x23
|
||||||
|
}
|
||||||
|
return e.emit(&instr{
|
||||||
|
opcode: []byte{0x0F, opc},
|
||||||
|
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||||
|
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MMX register moves: MOVQ M0, mem and MOVQ mem, M0 are the MMX
|
||||||
|
// load/store pair 0F 6F/0F 7F (no prefix); a register pair takes the
|
||||||
|
// load opcode. The GPR crossings ride the MOVD opcodes with REX.W
|
||||||
|
// (0F 6E into the bank, 0F 7E out), and an XMM source crosses into the
|
||||||
|
// bank through the F2 0F D6 move. The XMM MOVQ forms follow below.
|
||||||
|
if m, ok := src.(Reg); ok && m.mmx {
|
||||||
|
switch d := dst.(type) {
|
||||||
|
case Reg:
|
||||||
|
if !d.mmx && (d.isVec() || d.fp) {
|
||||||
|
return fmt.Errorf("MOV: MMX register moves stay inside the M bank")
|
||||||
|
}
|
||||||
|
if !d.mmx {
|
||||||
|
// M → GPR: 0F 7E with REX.W, the bank in reg and the GPR in
|
||||||
|
// r/m, the store layout the toolchain picks.
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x7E}, modrm: -1, sib: -1, rexW: true}
|
||||||
|
if err := setRM(i, m, d, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, d, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
case Mem:
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x7F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, d, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("MOV: invalid MMX destination")
|
||||||
|
}
|
||||||
|
if m, ok := dst.(Reg); ok && m.mmx {
|
||||||
|
switch src.(type) {
|
||||||
|
case Mem, sbMem:
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
case Reg:
|
||||||
|
if g := src.(Reg); g.isVec() {
|
||||||
|
// X → M: F2 0F D6, the bank in reg, the XMM source in r/m.
|
||||||
|
i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
// GPR → M: 0F 6E with REX.W, the bank in reg, the GPR in r/m.
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6E}, modrm: -1, sib: -1, rexW: true}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("MOV: MMX load takes a register or memory source")
|
||||||
|
}
|
||||||
|
|
||||||
if srcVec || dstVec {
|
if srcVec || dstVec {
|
||||||
if dstVec {
|
if dstVec {
|
||||||
if g, ok := src.(Reg); ok && !g.isVec() {
|
if g, ok := src.(Reg); ok && !g.isVec() {
|
||||||
@@ -180,17 +322,41 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
|||||||
}
|
}
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
|
|
||||||
|
case TLSMem:
|
||||||
|
if !dstIsReg {
|
||||||
|
return fmt.Errorf("MOV: two memory operands")
|
||||||
|
}
|
||||||
|
// MOV r, off(TLS): the segment-prefixed absolute load, reg=dst,
|
||||||
|
// rm=src(tlsMem) through the SIB escape; the disp32 is the TLS slot
|
||||||
|
// offset with its R_TLSLE patch site.
|
||||||
|
i := newInstr(size, []byte{movRR(size)})
|
||||||
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
|
||||||
|
case SegAbs:
|
||||||
|
if !dstIsReg {
|
||||||
|
return fmt.Errorf("MOV: two memory operands")
|
||||||
|
}
|
||||||
|
// MOV r, 0x30(GS): the segment-absolute load.
|
||||||
|
i := newInstr(size, []byte{movRR(size)})
|
||||||
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
|
||||||
case Imm:
|
case Imm:
|
||||||
if dstIsReg {
|
if dstIsReg {
|
||||||
v := int64(src)
|
v := int64(src)
|
||||||
// The Go assembler compresses 64-bit moves whose immediate fits
|
// The Go assembler compresses 64-bit moves whose immediate
|
||||||
// a signed int32, choosing per sign:
|
// fits the zero-extending 32-bit span, choosing per sign:
|
||||||
// v >= 0: B8+rd imm32 without REX.W (zero-extended by the
|
// v >= 0: B8+rd imm32 without REX.W (zero-extended by the
|
||||||
// hardware, REX.B still emitted for R8-R15);
|
// hardware, REX.B still emitted for R8-R15);
|
||||||
// v < 0: REX.W C7 /0 imm32 (sign-extended, the plain B8+rd
|
// v < 0: REX.W C7 /0 imm32 (sign-extended, the plain B8+rd
|
||||||
// form would zero-extend and corrupt the value).
|
// form would zero-extend and corrupt the value).
|
||||||
// Out-of-range immediates keep the B8+rd imm64 form.
|
// Out-of-range immediates keep the B8+rd imm64 form.
|
||||||
if size == 8 && v >= 0 && v <= (1<<31)-1 {
|
if size == 8 && v >= 0 && v <= (1<<32)-1 {
|
||||||
i := newInstr(4, []byte{0xB8 + byte(dstReg.idx&7)})
|
i := newInstr(4, []byte{0xB8 + byte(dstReg.idx&7)})
|
||||||
i.rexB = dstReg.idx >= 8
|
i.rexB = dstReg.idx >= 8
|
||||||
i.imm = le32(v)
|
i.imm = le32(v)
|
||||||
@@ -220,11 +386,24 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
|||||||
i.imm = imm
|
i.imm = imm
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
// MOV r/m, imm: 0xC6 (8-bit) / 0xC7 /0.
|
// MOV r/m, imm: 0xC6 (8-bit) / 0xC7 /0. An immediate in the
|
||||||
|
// destination slot is the absolute-address crash-store spelling,
|
||||||
|
// MOVL $0xf1, 0xf1: the parser reads the trailing bare constant
|
||||||
|
// as an immediate, and the store's disp32 carries the address.
|
||||||
op := byte(0xC7)
|
op := byte(0xC7)
|
||||||
if size == 1 {
|
if size == 1 {
|
||||||
op = 0xC6
|
op = 0xC6
|
||||||
}
|
}
|
||||||
|
if d, ok := dst.(Imm); ok {
|
||||||
|
i := newInstr(size, []byte{op})
|
||||||
|
setSegAbs(i, 0, SegAbs{Disp: int64(d)})
|
||||||
|
immBytes, err := immediate(int64(src), size, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.imm = immBytes
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
i := newInstr(size, []byte{op})
|
i := newInstr(size, []byte{op})
|
||||||
if err := setRMDigit(i, 0, dst, size); err != nil {
|
if err := setRMDigit(i, 0, dst, size); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -350,8 +529,10 @@ func (e *enc) encodeALUImm(digit int, dst Operand, imm int64, size int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The byte accumulator short form (0x04+digit*8, no ModR/M) when
|
// The byte accumulator short form (0x04+digit*8, no ModR/M) when
|
||||||
// the destination is AL, the form the Go assembler prefers here.
|
// the destination is spelled AL itself; the size-agnostic AX takes
|
||||||
if r, ok := dst.(Reg); ok && r.idx == 0 {
|
// the generic 0x80 /digit form, the division go tool asm makes
|
||||||
|
// (ADDB $7, AL is 04 07 while ADDB $3, AX is 80 c0 03).
|
||||||
|
if r, ok := dst.(Reg); ok && r.idx == 0 && byteReg(r) {
|
||||||
i := &instr{opcode: []byte{byte(0x04 + digit*8)}, modrm: -1, sib: -1}
|
i := &instr{opcode: []byte{byte(0x04 + digit*8)}, modrm: -1, sib: -1}
|
||||||
i.imm = immBytes
|
i.imm = immBytes
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
@@ -408,8 +589,11 @@ func (e *enc) encodeTest(ops []Operand, size int) error {
|
|||||||
if imm, ok := src.(Imm); ok {
|
if imm, ok := src.(Imm); ok {
|
||||||
// TEST r/m, imm: 0xF6 (8-bit) / 0xF7 /0, but the Go assembler
|
// TEST r/m, imm: 0xF6 (8-bit) / 0xF7 /0, but the Go assembler
|
||||||
// always uses the accumulator forms (A8/A9, no ModR/M) when the
|
// always uses the accumulator forms (A8/A9, no ModR/M) when the
|
||||||
// register operand is AL/AX, whatever the immediate's width.
|
// register operand is AL/AX, whatever the immediate's width. At
|
||||||
if r, ok := dst.(Reg); ok && r.idx == 0 {
|
// byte width the short form belongs to the AL spelling alone; the
|
||||||
|
// size-agnostic AX takes the generic F6 /0 (TESTB $7, AX is
|
||||||
|
// f6 c0 07), the same division the ALU accumulator makes.
|
||||||
|
if r, ok := dst.(Reg); ok && r.idx == 0 && (size != 1 || byteReg(r)) {
|
||||||
op := byte(0xA9)
|
op := byte(0xA9)
|
||||||
if size == 1 {
|
if size == 1 {
|
||||||
op = 0xA8
|
op = 0xA8
|
||||||
@@ -469,6 +653,14 @@ func (e *enc) encodeLea(ops []Operand, size int) error {
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("LEA: source must be a memory operand")
|
return fmt.Errorf("LEA: source must be a memory operand")
|
||||||
}
|
}
|
||||||
|
// LEA accepts the full unsigned 32-bit displacement span where the
|
||||||
|
// loads and stores reject it beyond the signed one; the wide values
|
||||||
|
// ride the same disp32 bytes as their two's-complement bit pattern.
|
||||||
|
if m, ok := src.(Mem); ok && m.Disp >= 1<<31 && m.Disp <= (1<<32)-1 {
|
||||||
|
c := m
|
||||||
|
c.Disp = int64(int32(uint32(m.Disp)))
|
||||||
|
src = c
|
||||||
|
}
|
||||||
i := newInstr(size, []byte{0x8D})
|
i := newInstr(size, []byte{0x8D})
|
||||||
if err := setRM(i, dstReg, src, size); err != nil {
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -604,7 +796,7 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
|
|||||||
}
|
}
|
||||||
i.imm = []byte{byte(imm)}
|
i.imm = []byte{byte(imm)}
|
||||||
}
|
}
|
||||||
if err := setRMReg(i, srcReg.idx, srcReg.idx >= 8, false, dst, size); err != nil {
|
if err := setRMReg(i, srcReg.idx&7, srcReg.idx >= 8, false, dst, size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
@@ -614,8 +806,29 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
|
|||||||
|
|
||||||
func (e *enc) encodeImul(ops []Operand, size int) error {
|
func (e *enc) encodeImul(ops []Operand, size int) error {
|
||||||
switch len(ops) {
|
switch len(ops) {
|
||||||
|
case 1:
|
||||||
|
// The one-operand form, IMUL r/m: F6/F7 /5 with AL/AX/EAX/RAX as the
|
||||||
|
// implied destination (the toolchain's one-register shape).
|
||||||
|
opc := byte(0xF7)
|
||||||
|
if size == 1 {
|
||||||
|
opc = 0xF6
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{opc})
|
||||||
|
if err := setRMDigit(i, 5, ops[0], size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
case 2:
|
case 2:
|
||||||
// IMUL r, r/m: 0x0F 0xAF.
|
// Two shapes. The leading-immediate spelling IMUL $imm, r multiplies
|
||||||
|
// r in place (dst = rm = r): the shape GOROOT's clock code writes.
|
||||||
|
// Otherwise IMUL r, r/m: 0x0F 0xAF.
|
||||||
|
if imm, ok := ops[0].(Imm); ok {
|
||||||
|
dstReg, isReg := ops[1].(Reg)
|
||||||
|
if !isReg {
|
||||||
|
return fmt.Errorf("IMUL: destination must be a register")
|
||||||
|
}
|
||||||
|
return e.encodeImulImm(imm, dstReg, dstReg, size)
|
||||||
|
}
|
||||||
dstReg, ok := ops[1].(Reg)
|
dstReg, ok := ops[1].(Reg)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("IMUL: destination must be a register")
|
return fmt.Errorf("IMUL: destination must be a register")
|
||||||
@@ -635,17 +848,26 @@ func (e *enc) encodeImul(ops []Operand, size int) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("IMUL: immediate operand expected first")
|
return fmt.Errorf("IMUL: immediate operand expected first")
|
||||||
}
|
}
|
||||||
// Plan 9 order: IMUL $imm, src, dst.
|
// Plan 9 order: IMUL $imm, src, dst; the source stays a general
|
||||||
|
// r/m operand (setRM takes registers and memory alike).
|
||||||
|
return e.encodeImulImm(imm, ops[1], dstReg, size)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IMUL expects 1, 2 or 3 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeImulImm emits the immediate multiply: 0x6B with a sign-extended imm8
|
||||||
|
// when the value fits, 0x69 with a 32-bit immediate otherwise.
|
||||||
|
func (e *enc) encodeImulImm(imm Imm, rm Operand, dst Reg, size int) error {
|
||||||
if fits8(int64(imm)) {
|
if fits8(int64(imm)) {
|
||||||
i := newInstr(size, []byte{0x6B})
|
i := newInstr(size, []byte{0x6B})
|
||||||
if err := setRM(i, dstReg, ops[1], size); err != nil {
|
if err := setRM(i, dst, rm, size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
i.imm = []byte{byte(int8(imm))}
|
i.imm = []byte{byte(int8(imm))}
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
i := newInstr(size, []byte{0x69})
|
i := newInstr(size, []byte{0x69})
|
||||||
if err := setRM(i, dstReg, ops[1], size); err != nil {
|
if err := setRM(i, dst, rm, size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
immBytes, err := immediate(int64(imm), size, false)
|
immBytes, err := immediate(int64(imm), size, false)
|
||||||
@@ -655,8 +877,6 @@ func (e *enc) encodeImul(ops []Operand, size int) error {
|
|||||||
i.imm = immBytes
|
i.imm = immBytes
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("IMUL expects 2 or 3 operands, got %d", len(ops))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- PUSH / POP -------------------------------------------------------------
|
// --- PUSH / POP -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -677,6 +897,27 @@ func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {
|
|||||||
w16 := size == 2
|
w16 := size == 2
|
||||||
switch op := ops[0].(type) {
|
switch op := ops[0].(type) {
|
||||||
case Reg:
|
case Reg:
|
||||||
|
// Segment registers: FS and GS carry their own one-byte opcodes
|
||||||
|
// under 0F (A0/A8 push, A1/A9 pop); the other four spellings are
|
||||||
|
// not pushable in 64-bit mode.
|
||||||
|
if n, isSeg := op.segNumber(); isSeg {
|
||||||
|
switch n {
|
||||||
|
case 4: // FS
|
||||||
|
if push {
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA0}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA1}, modrm: -1, sib: -1})
|
||||||
|
case 5: // GS
|
||||||
|
if push {
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA8}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA9}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return fmt.Errorf("PUSH/POP: only FS and GS are encodable in 64-bit mode")
|
||||||
|
}
|
||||||
|
if op.mmx || op.isVec() || op.fp || op.ctl != 0 {
|
||||||
|
return fmt.Errorf("PUSH/POP: invalid register operand")
|
||||||
|
}
|
||||||
base := byte(0x50) // PUSH r; POP is 0x58
|
base := byte(0x50) // PUSH r; POP is 0x58
|
||||||
if !push {
|
if !push {
|
||||||
base = 0x58
|
base = 0x58
|
||||||
@@ -707,8 +948,12 @@ func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {
|
|||||||
}
|
}
|
||||||
// PUSH imm32, sign-extended to 64 bits; go tool asm bounds the
|
// PUSH imm32, sign-extended to 64 bits; go tool asm bounds the
|
||||||
// immediate by the same signed/unsigned 32-bit span as every other
|
// immediate by the same signed/unsigned 32-bit span as every other
|
||||||
// scalar immediate.
|
// scalar immediate. The W spelling takes imm16 alone.
|
||||||
immBytes, err := immediate(int64(op), 8, false)
|
immWidth := 8
|
||||||
|
if w16 {
|
||||||
|
immWidth = 2
|
||||||
|
}
|
||||||
|
immBytes, err := immediate(int64(op), immWidth, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -828,29 +1073,46 @@ func immediate(v int64, size int, full64 bool) ([]byte, error) {
|
|||||||
|
|
||||||
// --- CMOVcc / SETcc ---------------------------------------------------------
|
// --- CMOVcc / SETcc ---------------------------------------------------------
|
||||||
|
|
||||||
|
// cmovCondition splits a CMOVcc suffix into an optional size letter and the
|
||||||
|
// condition code. Two vocabularies meet here: the Plan 9 spellings prefix
|
||||||
|
// the condition with a size letter (CMOVLGT, CMOVQEQ), while the toolchain's
|
||||||
|
// renderer prints the condition alone (CMOVLE, CMOVG) and leaves the width
|
||||||
|
// to the operand registers. A suffix that is itself a condition name reads
|
||||||
|
// as that condition, so the renderer's text re-encodes; CMOVBGT, CMOVWXX and
|
||||||
|
// the bare CMOV still find no condition and stay rejected.
|
||||||
|
func cmovCondition(rest string) (size int, cc int, ok bool) {
|
||||||
|
if cc, ok := jccMap[rest]; ok {
|
||||||
|
return 0, cc, true
|
||||||
|
}
|
||||||
|
if len(rest) >= 2 {
|
||||||
|
switch rest[0] {
|
||||||
|
case 'W':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 2, cc, true
|
||||||
|
}
|
||||||
|
case 'L':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 4, cc, true
|
||||||
|
}
|
||||||
|
case 'Q':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 8, cc, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition
|
// encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition
|
||||||
// (CMOVLGT, CMOVQEQ, …). The condition reads exactly like the Jcc spellings;
|
// (CMOVLGT, CMOVQEQ, …), or the renderer's condition alone (CMOVLE) with the
|
||||||
// the instruction is 0F 40+cc with reg = dst, rm = src.
|
// width taken from the destination register. The instruction is 0F 40+cc
|
||||||
|
// with reg = dst, rm = src.
|
||||||
func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
rest := upper[len("CMOV"):]
|
rest := upper[len("CMOV"):]
|
||||||
if len(rest) < 2 {
|
size, cc, ok := cmovCondition(rest)
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
|
||||||
}
|
|
||||||
var size int
|
|
||||||
switch rest[0] {
|
|
||||||
case 'W':
|
|
||||||
size = 2
|
|
||||||
case 'L':
|
|
||||||
size = 4
|
|
||||||
case 'Q':
|
|
||||||
size = 8
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
|
||||||
}
|
|
||||||
cc, ok := jccMap[rest[1:]]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
return fmt.Errorf("unsupported instruction %q", upper)
|
||||||
}
|
}
|
||||||
@@ -859,6 +1121,14 @@ func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("CMOVcc destination must be a register")
|
return fmt.Errorf("CMOVcc destination must be a register")
|
||||||
}
|
}
|
||||||
|
if size == 0 {
|
||||||
|
// The renderer's spelling carries no size letter: the width rides
|
||||||
|
// the destination register's own size class.
|
||||||
|
size = dstReg.size
|
||||||
|
if size != 1 && size != 2 && size != 4 && size != 8 {
|
||||||
|
return fmt.Errorf("CMOVcc destination must be a general register")
|
||||||
|
}
|
||||||
|
}
|
||||||
i := newInstr(size, []byte{0x0F, byte(0x40 + cc)})
|
i := newInstr(size, []byte{0x0F, byte(0x40 + cc)})
|
||||||
if err := setRM(i, dstReg, src, size); err != nil {
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1028,6 +1298,38 @@ var sseMoveTable = map[string]sseMove{
|
|||||||
"MOVSS": {0xF3, 0x10, 0x11}, // scalar single
|
"MOVSS": {0xF3, 0x10, 0x11}, // scalar single
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sseStoreOnly holds the store-only SSE forms, OP xmm, mem: the XMM register
|
||||||
|
// rides the reg field and memory r/m (the non-temporal store).
|
||||||
|
var sseStoreOnly = map[string]struct {
|
||||||
|
prefix byte
|
||||||
|
op byte
|
||||||
|
}{
|
||||||
|
"MOVNTDQ": {0x66, 0xE7},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEStoreOnly encodes OP xmm, mem (reg = the XMM source, r/m = the
|
||||||
|
// destination memory).
|
||||||
|
func (e *enc) encodeSSEStoreOnly(mnem string, m struct {
|
||||||
|
prefix byte
|
||||||
|
op byte
|
||||||
|
}, ops []Operand) error {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, ok := ops[0].(Reg)
|
||||||
|
if !ok || !srcReg.isVec() {
|
||||||
|
return fmt.Errorf("%s source must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[1]) {
|
||||||
|
return fmt.Errorf("%s destination must be a memory operand", mnem)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, srcReg, ops[1], 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
// encodeSSEMove encodes a legacy SSE move: a vector-to-vector move uses the
|
// encodeSSEMove encodes a legacy SSE move: a vector-to-vector move uses the
|
||||||
// load form (reg = destination), matching the Go assembler.
|
// load form (reg = destination), matching the Go assembler.
|
||||||
func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
||||||
@@ -1164,13 +1466,15 @@ type sseExtract struct {
|
|||||||
op []byte
|
op []byte
|
||||||
opMem []byte // used when the destination is memory; nil shares op
|
opMem []byte // used when the destination is memory; nil shares op
|
||||||
rexW bool // PEXTRQ's REX.W
|
rexW bool // PEXTRQ's REX.W
|
||||||
|
rev bool // PEXTRW's GPR form swaps the fields: the register
|
||||||
|
// destination rides reg and the XMM source r/m
|
||||||
}
|
}
|
||||||
|
|
||||||
var sseExtractTable = map[string]sseExtract{
|
var sseExtractTable = map[string]sseExtract{
|
||||||
"PEXTRB": {[]byte{0x0F, 0x3A, 0x14}, nil, false},
|
"PEXTRB": {[]byte{0x0F, 0x3A, 0x14}, nil, false, false},
|
||||||
"PEXTRD": {[]byte{0x0F, 0x3A, 0x16}, nil, false},
|
"PEXTRD": {[]byte{0x0F, 0x3A, 0x16}, nil, false, false},
|
||||||
"PEXTRQ": {[]byte{0x0F, 0x3A, 0x16}, nil, true},
|
"PEXTRQ": {[]byte{0x0F, 0x3A, 0x16}, nil, true, false},
|
||||||
"PEXTRW": {[]byte{0x0F, 0xC5}, []byte{0x0F, 0x3A, 0x15}, false},
|
"PEXTRW": {[]byte{0x0F, 0xC5}, []byte{0x0F, 0x3A, 0x15}, false, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// sseInsert describes a lane insert: OP $imm, src, xdst with reg = the XMM
|
// sseInsert describes a lane insert: OP $imm, src, xdst with reg = the XMM
|
||||||
@@ -1243,14 +1547,23 @@ func (e *enc) encodeSSEBin(m sseBin, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
src, dst := ops[0], ops[1]
|
src, dst := ops[0], ops[1]
|
||||||
dstReg, ok := dst.(Reg)
|
dstReg, ok := dst.(Reg)
|
||||||
if !ok || !dstReg.isVec() {
|
if !ok || (!dstReg.isVec() && !dstReg.mmx) {
|
||||||
return fmt.Errorf("SSE binary destination must be a vector register")
|
return fmt.Errorf("SSE binary destination must be a vector register")
|
||||||
}
|
}
|
||||||
|
// The MMX twins of the packed-integer SSE2 ops drop the 0x66 prefix:
|
||||||
|
// PADDD M2, M1 is 0F FE where the XMM form is 66 0F FE.
|
||||||
|
prefix := m.prefix
|
||||||
|
if dstReg.mmx {
|
||||||
|
if prefix != 0x66 {
|
||||||
|
return fmt.Errorf("SSE binary: this form takes no MMX register operand")
|
||||||
|
}
|
||||||
|
prefix = 0
|
||||||
|
}
|
||||||
opcode := []byte{0x0F, m.op}
|
opcode := []byte{0x0F, m.op}
|
||||||
if m.map38 {
|
if m.map38 {
|
||||||
opcode = []byte{0x0F, 0x38, m.op}
|
opcode = []byte{0x0F, 0x38, m.op}
|
||||||
}
|
}
|
||||||
i := &instr{prefix: m.prefix, opcode: opcode, modrm: -1, sib: -1}
|
i := &instr{prefix: prefix, opcode: opcode, modrm: -1, sib: -1}
|
||||||
if err := setRM(i, dstReg, src, 8); err != nil {
|
if err := setRM(i, dstReg, src, 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1648,8 +1961,17 @@ func (e *enc) encodeSSEExtract(m sseExtract, ops []Operand) error {
|
|||||||
if m.opMem != nil && memOperand(ops[2]) {
|
if m.opMem != nil && memOperand(ops[2]) {
|
||||||
opcode = m.opMem
|
opcode = m.opMem
|
||||||
}
|
}
|
||||||
|
reg, rm := srcReg, ops[2]
|
||||||
|
if m.rev && opcode[1] == 0xC5 {
|
||||||
|
// the 0F C5 layout: the GPR destination rides reg, the XMM source r/m
|
||||||
|
if d, ok := ops[2].(Reg); !ok {
|
||||||
|
return fmt.Errorf("PEXTRW destination must be a register or memory")
|
||||||
|
} else {
|
||||||
|
reg, rm = d, ops[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
i := &instr{prefix: 0x66, opcode: opcode, modrm: -1, sib: -1, rexW: m.rexW}
|
i := &instr{prefix: 0x66, opcode: opcode, modrm: -1, sib: -1, rexW: m.rexW}
|
||||||
if err := setRM(i, srcReg, ops[2], 8); err != nil {
|
if err := setRM(i, reg, rm, 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
i.imm = []byte{immByte}
|
i.imm = []byte{immByte}
|
||||||
@@ -1726,12 +2048,19 @@ func (e *enc) encodeSSEShift(name string, ops []Operand) error {
|
|||||||
// immediate LAST in Plan 9 order (src, dst, $imm), unlike the shuffle family:
|
// immediate LAST in Plan 9 order (src, dst, $imm), unlike the shuffle family:
|
||||||
// F2 0F C2 with reg = dst, rm = src.
|
// F2 0F C2 with reg = dst, rm = src.
|
||||||
func (e *enc) encodeCmpsd(ops []Operand) error {
|
func (e *enc) encodeCmpsd(ops []Operand) error {
|
||||||
|
return e.encodeSSECmp("CMPSD", 0xF2, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSECmp encodes the SSE compare family (CMPSD/CMPSS/CMPPS/CMPPD):
|
||||||
|
// 0F C2 /r ib with the predicate immediate last in Plan 9 order
|
||||||
|
// (src, dst, $imm) and the packed forms' prefixes.
|
||||||
|
func (e *enc) encodeSSECmp(mnem string, prefix byte, ops []Operand) error {
|
||||||
if len(ops) != 3 {
|
if len(ops) != 3 {
|
||||||
return fmt.Errorf("CMPSD expects 3 operands (src, dst, $imm), got %d", len(ops))
|
return fmt.Errorf("%s expects 3 operands (src, dst, $imm), got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
imm, ok := ops[2].(Imm)
|
imm, ok := ops[2].(Imm)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("CMPSD predicate must be an immediate")
|
return fmt.Errorf("%s predicate must be an immediate", mnem)
|
||||||
}
|
}
|
||||||
immByte, err := imm8(int64(imm))
|
immByte, err := imm8(int64(imm))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1739,9 +2068,9 @@ func (e *enc) encodeCmpsd(ops []Operand) error {
|
|||||||
}
|
}
|
||||||
dstReg, ok2 := ops[1].(Reg)
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
if !ok2 || !dstReg.isVec() {
|
if !ok2 || !dstReg.isVec() {
|
||||||
return fmt.Errorf("CMPSD destination must be a vector register")
|
return fmt.Errorf("%s destination must be a vector register", mnem)
|
||||||
}
|
}
|
||||||
i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
||||||
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAssembleGoFlacAVX2Kernel assembles the whole production AVX2 kernel;
|
// TestAssembleGoFlacAVX2Kernel assembles the whole production AVX2 kernel;
|
||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
||||||
path := "../../go-libraries/go-flac/avx2_amd64.s"
|
path := "../../go-libraries/go-flac/avx2_amd64.s"
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
t.Skip("go-libraries repository not present next to gasm-devkit")
|
t.Skip("go-libraries repository not present next to gasm-sdk")
|
||||||
}
|
}
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,7 +86,7 @@ func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
|||||||
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
||||||
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
t.Skip("go-libraries repository not present next to gasm-devkit")
|
t.Skip("go-libraries repository not present next to gasm-sdk")
|
||||||
}
|
}
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The differential kernels for the DATA-path and front-end gaps are kept in
|
||||||
|
// testdata/verify beside the campaign's other kernels; the verify package's
|
||||||
|
// suites are not open to the asm package, so this test is their runner: each
|
||||||
|
// kernel assembles through gasm and through go tool asm, and the functions'
|
||||||
|
// bytes must agree with the relocation sites masked on both sides.
|
||||||
|
|
||||||
|
// toolAsmObject assembles path with the installed toolchain's assembler for
|
||||||
|
// goarch ("" = the host) and returns the object bytes. Every live-oracle
|
||||||
|
// comparison funnels through here, so this is also where the deliberate-run
|
||||||
|
// boundary sits: under -short (the push pipeline's mode) the comparisons
|
||||||
|
// skip, because each spawns a go tool asm subprocess and the small single-
|
||||||
|
// core runner pays seconds per spawn. The encodings stay pinned by the
|
||||||
|
// golden-byte tests in every mode; the live oracle runs in the local test
|
||||||
|
// gate and the dispatched workflows.
|
||||||
|
func toolAsmObject(t *testing.T, path, goarch string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
out, err := exec.Command(goBin, "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("go env GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
includeDir := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include")
|
||||||
|
|
||||||
|
pkg := strings.TrimSuffix(filepath.Base(path), ".s")
|
||||||
|
pkg = strings.TrimSuffix(pkg, "_amd64")
|
||||||
|
pkg = strings.TrimSuffix(pkg, "_arm64")
|
||||||
|
|
||||||
|
objPath := filepath.Join(t.TempDir(), "oracle.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm", "-I", includeDir, "-p", pkg, "-o", objPath, path)
|
||||||
|
if goarch != "" {
|
||||||
|
environ := os.Environ()
|
||||||
|
env := make([]string, 0, len(environ)+1)
|
||||||
|
for _, e := range environ {
|
||||||
|
if !strings.HasPrefix(e, "GOARCH=") {
|
||||||
|
env = append(env, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Env = append(env, "GOARCH="+goarch)
|
||||||
|
}
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("go tool asm %s: %v\n%s", filepath.Base(path), err, out)
|
||||||
|
}
|
||||||
|
obj, err := os.ReadFile(objPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracleFuncCode extracts the non-package TEXT functions' code bytes from a
|
||||||
|
// toolchain object, keyed by the name the object records (pkg.name). Each
|
||||||
|
// function's span is its own symbol size: a toolchain object that follows
|
||||||
|
// the text with data symbols (the synthesised float-constant pool) would
|
||||||
|
// otherwise fold them into the last function's bytes.
|
||||||
|
func oracleFuncCode(t *testing.T, obj []byte) map[string][]byte {
|
||||||
|
t.Helper()
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
le := binary.LittleEndian
|
||||||
|
const symSize = 21
|
||||||
|
nps := v.syms(blkNonpkgdef)
|
||||||
|
data := v.blk(blkData)
|
||||||
|
didx := v.blk(blkDataIdx)
|
||||||
|
preceding := 0
|
||||||
|
for _, bi := range []int{blkSymdef, blkHashed64def, blkHasheddef} {
|
||||||
|
preceding += len(v.blk(bi)) / symSize
|
||||||
|
}
|
||||||
|
out := make(map[string][]byte, len(nps))
|
||||||
|
for i, s := range nps {
|
||||||
|
if s.typ != kindSTEXT {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := le.Uint32(didx[4*(preceding+i):])
|
||||||
|
out[s.name] = data[start : start+s.size]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// maskCode zeroes every relocation field, the way the toolchain's object
|
||||||
|
// leaves them for the linker.
|
||||||
|
func maskCode(code []byte, relocs []Reloc) []byte {
|
||||||
|
for _, r := range relocs {
|
||||||
|
for j := r.Off; j < r.Off+4 && j < len(code); j++ {
|
||||||
|
code[j] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// code assembles src for amd64 and returns the image's code bytes.
|
||||||
|
func code(path, src string) []byte {
|
||||||
|
f, errs := parser.Parse(path, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return img.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDifferentialKernels pins the new kernels against the oracle.
|
||||||
|
func TestDifferentialKernels(t *testing.T) {
|
||||||
|
if runtime.GOARCH != "amd64" {
|
||||||
|
t.Skip("the amd64 kernels assume an amd64 host assembler default")
|
||||||
|
}
|
||||||
|
for _, k := range []struct {
|
||||||
|
path string
|
||||||
|
goarch string
|
||||||
|
arm64 bool
|
||||||
|
}{
|
||||||
|
{filepath.Join("..", "testdata", "verify", "datarel_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "divslash_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "semicolons_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "quadreg_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "floatimm_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "bookkeep_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "forms_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "datarel_arm64.s"), "arm64", true},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "divslash_arm64.s"), "arm64", true},
|
||||||
|
} {
|
||||||
|
t.Run(filepath.Base(k.path), func(t *testing.T) {
|
||||||
|
src, err := os.ReadFile(k.path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read: %v", err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse(k.path, string(src))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
var img *Image
|
||||||
|
if k.arm64 {
|
||||||
|
img, err = AssembleFileARM64(f)
|
||||||
|
} else {
|
||||||
|
img, err = AssembleFile(f)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
gt := oracleFuncCode(t, toolAsmObject(t, k.path, k.goarch))
|
||||||
|
// The oracle keys its functions by the qualified object name
|
||||||
|
// (pkg.name); match on the local part.
|
||||||
|
byLocal := make(map[string][]byte, len(gt))
|
||||||
|
for name, code := range gt {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
name = after
|
||||||
|
}
|
||||||
|
byLocal[name] = code
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := 0
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
goCode, ok := byLocal[fn.Name]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: not in ground truth (%d functions: %v)", fn.Name, len(gt), keysOf(byLocal))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
goCode = maskCode(append([]byte(nil), goCode...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
t.Errorf("%s: MISMATCH gasm=%d go=%d bytes\ngasm %x\ngo %x", fn.Name, len(gasmCode), len(goCode), gasmCode, goCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Errorf("%s: non-zero trailing bytes in go tool asm output", fn.Name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matched++
|
||||||
|
t.Logf("%s: MATCH (%d bytes)", fn.Name, len(gasmCode))
|
||||||
|
}
|
||||||
|
if matched == 0 {
|
||||||
|
t.Fatal("no functions matched")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keysOf(m map[string][]byte) []string {
|
||||||
|
out := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
out = append(out, k)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSemicolonSpellingParity pins that the ';' statement separator changes
|
||||||
|
// nothing about the encoding: the one-line spelling assembles to exactly the
|
||||||
|
// bytes of the same statements written one per line.
|
||||||
|
func TestSemicolonSpellingParity(t *testing.T) {
|
||||||
|
for _, tt := range []struct{ one, two string }{
|
||||||
|
{"\tROLQ $3, DI; ROLQ $13, DI\n", "\tROLQ $3, DI\n\tROLQ $13, DI\n"},
|
||||||
|
{"\tREP; MOVSQ\n", "\tREP\n\tMOVSQ\n"},
|
||||||
|
{"\tXORQ AX, AX; XORQ CX, CX\n", "\tXORQ AX, AX\n\tXORQ CX, CX\n"},
|
||||||
|
} {
|
||||||
|
one := code("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n"+tt.one+"\tRET\n")
|
||||||
|
two := code("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n"+tt.two+"\tRET\n")
|
||||||
|
if !bytes.Equal(one, two) {
|
||||||
|
t.Errorf("semicolon spelling %q: %x, want the two-line bytes %x", tt.one, one, two)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGOObjectLOONG64Structure checks the emitted loong64 object's blocks:
|
// TestGOObjectLOONG64Structure checks the emitted loong64 object's blocks:
|
||||||
|
|||||||
+307
-19
@@ -5,10 +5,11 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Image is an assembled file: the function bodies laid out in source order,
|
// Image is an assembled file: the function bodies laid out in source order,
|
||||||
@@ -97,12 +98,15 @@ const (
|
|||||||
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
||||||
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
||||||
RelRISCVJal // R_RISCV_JAL (J-type call)
|
RelRISCVJal // R_RISCV_JAL (J-type call)
|
||||||
|
RelRISCVTLSLE // R_RISCV_TLS_LE (LUI + I-type local-exec pair)
|
||||||
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
||||||
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
||||||
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
||||||
RelArm64Branch // R_CALLARM64 (BL instruction)
|
RelArm64Branch // R_CALLARM64 (BL instruction)
|
||||||
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
||||||
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
||||||
|
RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field
|
||||||
|
RelArm64TLSLE // R_ARM64_TLS_LE (MOVZ local-exec TLS load)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reloc struct {
|
type Reloc struct {
|
||||||
@@ -112,13 +116,17 @@ type Reloc struct {
|
|||||||
// Addend select the target: the symbol plus the byte offset. An
|
// Addend select the target: the symbol plus the byte offset. An
|
||||||
// External relocation names a symbol no GLOBL in the file defines;
|
// External relocation names a symbol no GLOBL in the file defines;
|
||||||
// the object-file emitters carry it into the output's relocation
|
// the object-file emitters carry it into the output's relocation
|
||||||
// table.
|
// table. Siz is the width of the patched field and is set only for
|
||||||
|
// data-field relocations (RelAddr, Off relative to the data symbol),
|
||||||
|
// whose width is the DATA line's; code relocations take their width
|
||||||
|
// from the architecture's instruction encoding.
|
||||||
Off int
|
Off int
|
||||||
After int
|
After int
|
||||||
Name string
|
Name string
|
||||||
Addend int64
|
Addend int64
|
||||||
External bool
|
External bool
|
||||||
Kind RelocKind
|
Kind RelocKind
|
||||||
|
Siz uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataSymbol describes one GLOBL symbol laid out in the data section.
|
// DataSymbol describes one GLOBL symbol laid out in the data section.
|
||||||
@@ -128,8 +136,14 @@ type DataSymbol struct {
|
|||||||
Offset int // byte offset within Data
|
Offset int // byte offset within Data
|
||||||
Size int
|
Size int
|
||||||
Static bool // the <> marker: file-local, not exported
|
Static bool // the <> marker: file-local, not exported
|
||||||
Rodata bool // the RODATA flag: read-only data
|
Rodata bool // the RODATA flag: read-only data (implies no pointers)
|
||||||
|
Noptr bool // the NOPTR flag: data with no pointers, kept out of GC scanning
|
||||||
Dupok bool // the DUPOK flag: duplicate-OK
|
Dupok bool // the DUPOK flag: duplicate-OK
|
||||||
|
// Relocs carries the symbol-valued DATA initialisers ("DATA s+0(SB)/8,
|
||||||
|
// $other(SB)"): fields of this symbol's data that hold another symbol's
|
||||||
|
// address, resolved by the linker. Off is relative to the symbol's
|
||||||
|
// data start.
|
||||||
|
Relocs []Reloc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the whole image: code, then data.
|
// Bytes returns the whole image: code, then data.
|
||||||
@@ -139,6 +153,18 @@ func (img *Image) Bytes() []byte {
|
|||||||
return append(out, img.Data...)
|
return append(out, img.Data...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssembleOption adjusts the file-level assembly context.
|
||||||
|
type AssembleOption func(*linkInfo)
|
||||||
|
|
||||||
|
// WithGOOS selects the target operating system for the forms that depend on
|
||||||
|
// it, the TLS access shape above all: linux and freebsd take the
|
||||||
|
// one-instruction form, windows and plan9 keep the two-instruction load.
|
||||||
|
func WithGOOS(goos string) AssembleOption {
|
||||||
|
return func(l *linkInfo) {
|
||||||
|
l.goos = goos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AssembleFile assembles every TEXT function of a parsed file and lays out
|
// AssembleFile assembles every TEXT function of a parsed file and lays out
|
||||||
// its static symbols (GLOBL/DATA) in a data section behind the code. Each
|
// its static symbols (GLOBL/DATA) in a data section behind the code. Each
|
||||||
// reference to a file-local static symbol becomes a RIP-relative load whose
|
// reference to a file-local static symbol becomes a RIP-relative load whose
|
||||||
@@ -146,7 +172,7 @@ func (img *Image) Bytes() []byte {
|
|||||||
// GLOBL defines is recorded as an external relocation (Externals) with its
|
// GLOBL defines is recorded as an external relocation (Externals) with its
|
||||||
// displacement left zero, the object-file emitters resolve it at link
|
// displacement left zero, the object-file emitters resolve it at link
|
||||||
// time, while the raw image (Bytes) cannot represent it.
|
// time, while the raw image (Bytes) cannot represent it.
|
||||||
func AssembleFile(f *ast.File) (*Image, error) {
|
func AssembleFile(f *ast.File, opts ...AssembleOption) (*Image, error) {
|
||||||
dataSyms, err := collectData(f)
|
dataSyms, err := collectData(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -155,7 +181,18 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
for _, d := range dataSyms {
|
for _, d := range dataSyms {
|
||||||
known[d.name] = true
|
known[d.name] = true
|
||||||
}
|
}
|
||||||
|
// TEXT symbols are file-level definitions too: a symbol immediate
|
||||||
|
// ($fn(SB)) may name one, exactly as a data reference names a GLOBL.
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
if t, ok := d.(*ast.Text); ok {
|
||||||
|
known[t.Name.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
link := &linkInfo{symbols: known, allowExternal: true}
|
link := &linkInfo{symbols: known, allowExternal: true}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(link)
|
||||||
|
}
|
||||||
|
poolSeen := map[string]bool{}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
textOff := map[string]int{}
|
textOff := map[string]int{}
|
||||||
@@ -169,7 +206,26 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
code, patches, labels, steps, lines, err := assemble(t, link)
|
code, patches, labels, steps, lines, pool, err := assemble(t, link)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
|
}
|
||||||
|
// The pooled floating-point constants join the declared data as
|
||||||
|
// read-only symbols, deduplicated across the file (the toolchain
|
||||||
|
// synthesises the same symbols into its rodata).
|
||||||
|
for _, entry := range pool {
|
||||||
|
if poolSeen[entry.name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
poolSeen[entry.name] = true
|
||||||
|
dataSyms = append(dataSyms, dataSym{
|
||||||
|
name: entry.name,
|
||||||
|
buf: entry.data,
|
||||||
|
size: len(entry.data),
|
||||||
|
rodata: true,
|
||||||
|
dupok: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
}
|
}
|
||||||
@@ -216,6 +272,7 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
Size: len(d.buf),
|
Size: len(d.buf),
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
img.Data = append(img.Data, d.buf...)
|
img.Data = append(img.Data, d.buf...)
|
||||||
@@ -257,6 +314,22 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
|
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The data symbols' symbol-valued DATA fields resolve the same way the
|
||||||
|
// code references do: a name the file defines (GLOBL or TEXT) stays an
|
||||||
|
// internal reference the emitters resolve, anything else is external.
|
||||||
|
// img.DataSyms was laid out in dataSyms order, so the indexes line up.
|
||||||
|
for i := range img.DataSyms {
|
||||||
|
for _, r := range dataSyms[i].relocs {
|
||||||
|
reloc := r
|
||||||
|
if _, ok := img.Symbols[reloc.Name]; !ok {
|
||||||
|
if _, ok := textOff[reloc.Name]; !ok {
|
||||||
|
reloc.External = true
|
||||||
|
externals[reloc.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
|
||||||
|
}
|
||||||
|
}
|
||||||
for name := range externals {
|
for name := range externals {
|
||||||
img.Externals = append(img.Externals, name)
|
img.Externals = append(img.Externals, name)
|
||||||
}
|
}
|
||||||
@@ -264,6 +337,35 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// riscvTLSSymbols names the symbols the file declares with the TLSBSS flag
|
||||||
|
// (or its legacy numeric constant 256 from textflag.h): the assembler gives
|
||||||
|
// their SB references the local-exec TLS sequence.
|
||||||
|
func riscvTLSSymbols(f *ast.File) map[string]bool {
|
||||||
|
var tls map[string]bool
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
gd, ok := d.(*ast.Globl)
|
||||||
|
if !ok || gd.Name == nil || gd.Name.Pseudo != "SB" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, fl := range gd.Flags {
|
||||||
|
isTLS := fl == "TLSBSS"
|
||||||
|
if !isTLS {
|
||||||
|
if n, err := strconv.Atoi(fl); err == nil && n&256 != 0 {
|
||||||
|
isTLS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isTLS {
|
||||||
|
if tls == nil {
|
||||||
|
tls = map[string]bool{}
|
||||||
|
}
|
||||||
|
tls[gd.Name.Name] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tls
|
||||||
|
}
|
||||||
|
|
||||||
// AssembleFileRISCV assembles every TEXT function of a parsed RISC-V file
|
// AssembleFileRISCV assembles every TEXT function of a parsed RISC-V file
|
||||||
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
||||||
// code. SB references in the code are encoded as AUIPC pairs with zero
|
// code. SB references in the code are encoded as AUIPC pairs with zero
|
||||||
@@ -273,6 +375,14 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// The symbols the file declares TLSBSS resolve through the local-exec
|
||||||
|
// sequence (LUI + ADDIW + ADD of TP), exactly as the toolchain routes
|
||||||
|
// every SB reference whose symbol carries the STLSBSS type.
|
||||||
|
tlsSyms := riscvTLSSymbols(f)
|
||||||
|
// The pooled $i64 constants the wide MOV immediate loads refer to join
|
||||||
|
// the declared data as read-only symbols, deduplicated across the file
|
||||||
|
// (the toolchain synthesises the same symbols into its rodata).
|
||||||
|
litSeen := map[string]bool{}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
@@ -280,10 +390,23 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
code, labels, relocs, lines, spadj, err := assembleRISCV(t)
|
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t, tlsSyms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
}
|
}
|
||||||
|
for _, lit := range lits {
|
||||||
|
if litSeen[lit.Name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
litSeen[lit.Name] = true
|
||||||
|
dataSyms = append(dataSyms, dataSym{
|
||||||
|
name: lit.Name,
|
||||||
|
buf: lit.Data,
|
||||||
|
size: len(lit.Data),
|
||||||
|
rodata: true,
|
||||||
|
dupok: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
fl := FuncLayout{
|
fl := FuncLayout{
|
||||||
Name: t.Name.Name,
|
Name: t.Name.Name,
|
||||||
Pkg: t.Name.Pkg,
|
Pkg: t.Name.Pkg,
|
||||||
@@ -327,6 +450,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
Size: d.size,
|
Size: d.size,
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -399,6 +523,7 @@ func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
|||||||
Size: d.size,
|
Size: d.size,
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -429,6 +554,23 @@ func markExternals(img *Image, dataSyms []dataSym) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The declared data symbols carry the file's own relocations (the
|
||||||
|
// symbol-valued DATA fields); the layouts appended img.DataSyms in
|
||||||
|
// dataSyms order, so the indexes line up. The trailing entries (the
|
||||||
|
// pooled arm64 literals) have no source relocations.
|
||||||
|
for i := range img.DataSyms {
|
||||||
|
if i >= len(dataSyms) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, r := range dataSyms[i].relocs {
|
||||||
|
reloc := r
|
||||||
|
if !known[reloc.Name] {
|
||||||
|
reloc.External = true
|
||||||
|
externals[reloc.Name] = true
|
||||||
|
}
|
||||||
|
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
|
||||||
|
}
|
||||||
|
}
|
||||||
for name := range externals {
|
for name := range externals {
|
||||||
img.Externals = append(img.Externals, name)
|
img.Externals = append(img.Externals, name)
|
||||||
}
|
}
|
||||||
@@ -443,16 +585,91 @@ type dataSym struct {
|
|||||||
size int
|
size int
|
||||||
static bool
|
static bool
|
||||||
rodata bool
|
rodata bool
|
||||||
|
noptr bool
|
||||||
dupok bool
|
dupok bool
|
||||||
|
// relocs are the symbol-valued DATA fields, in declaration order; Off
|
||||||
|
// is relative to the symbol's data start.
|
||||||
|
relocs []Reloc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkDuplicateDecls rejects a symbol the file declares twice, the
|
||||||
|
// toolchain's OnList rule (cmd/internal/obj, InitTextSym and GloblPos,
|
||||||
|
// measured with go tool asm): the second declaration of a symbol is
|
||||||
|
// diagnosed as a redeclaration whether the pair is two TEXTs, two GLOBLs
|
||||||
|
// or one of each, and a second TEXT carries the other declaration's line
|
||||||
|
// the way the toolchain's note does. The DUPOK flag plays no part at
|
||||||
|
// assembly time: it legalises duplicate definitions across files
|
||||||
|
// (AttrDuplicateOK, which the linker resolves), never two declarations
|
||||||
|
// inside one file, so a DUPOK pair here is rejected exactly like a plain
|
||||||
|
// one. Symbol identity follows the object model: the package prefix and
|
||||||
|
// the <> static marker are part of the name (foo(SB), foo<>(SB) and
|
||||||
|
// other·foo(SB) are three symbols); the ABI selector is not, because
|
||||||
|
// outside the runtime package, where alone it is legal, foo<ABIInternal>(SB)
|
||||||
|
// resolves to foo(SB) and is a redeclaration.
|
||||||
|
func checkDuplicateDecls(f *ast.File) error {
|
||||||
|
type decl struct {
|
||||||
|
text bool
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
seen := make(map[string]decl)
|
||||||
|
symKey := func(s *ast.Symbol) string {
|
||||||
|
key := s.Pkg + "\x00" + s.Name
|
||||||
|
if s.Static {
|
||||||
|
key += "\x00<>"
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
switch t := d.(type) {
|
||||||
|
case *ast.Text:
|
||||||
|
if first, dup := seen[symKey(t.Name)]; dup {
|
||||||
|
if first.text {
|
||||||
|
return fmt.Errorf("symbol %q redeclared (other declaration on line %d)", t.Name.Name, first.line)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("symbol %q redeclared", t.Name.Name)
|
||||||
|
}
|
||||||
|
seen[symKey(t.Name)] = decl{text: true, line: t.Pos().Line}
|
||||||
|
case *ast.Globl:
|
||||||
|
if t.Name == nil || t.Name.Pseudo != "SB" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, dup := seen[symKey(t.Name)]; dup {
|
||||||
|
return fmt.Errorf("symbol %q redeclared", t.Name.Name)
|
||||||
|
}
|
||||||
|
seen[symKey(t.Name)] = decl{line: t.Pos().Line}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data section's size ceilings. The image materialises every GLOBL's
|
||||||
|
// bytes at assembly time, where the toolchain defers the cost to its linker,
|
||||||
|
// so gasm needs its own bound and a diagnostic in place of an allocation
|
||||||
|
// failure. The toolchain's own limit (obj's "symbol too large", 2,000,000,000
|
||||||
|
// bytes) would let a twenty-five byte input demand four gigabytes of resident
|
||||||
|
// memory (measured: `GLOBL d(SB), $2000000000` peaks at 3.92 GiB RSS), which
|
||||||
|
// starves the memory fence the test recipes run under when the fuzz workers
|
||||||
|
// share it. 64 MiB per symbol and 128 MiB per file sit two orders above any
|
||||||
|
// real assembly symbol (the runtime's largest GLOBL is KiB-scale) and keep a
|
||||||
|
// worker's worst-case peak near its fence share.
|
||||||
|
const (
|
||||||
|
maxSymbolSize = 64 << 20
|
||||||
|
maxDataSize = 128 << 20
|
||||||
|
)
|
||||||
|
|
||||||
// collectData gathers the file's static symbols (GLOBL) and their initial
|
// collectData gathers the file's static symbols (GLOBL) and their initial
|
||||||
// contents (DATA) into byte buffers. Two passes: the Plan 9 convention puts
|
// contents (DATA) into byte buffers. Two passes: the Plan 9 convention puts
|
||||||
// every DATA line before its symbol's GLOBL, so the symbols are registered
|
// every DATA line before its symbol's GLOBL, so the symbols are registered
|
||||||
// before the initialisers are applied.
|
// before the initialisers are applied. Every per-architecture entry point
|
||||||
|
// collects data before assembling text, so the duplicate-declaration check
|
||||||
|
// rides here and covers them all.
|
||||||
func collectData(f *ast.File) ([]dataSym, error) {
|
func collectData(f *ast.File) ([]dataSym, error) {
|
||||||
|
if err := checkDuplicateDecls(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
index := map[string]int{}
|
index := map[string]int{}
|
||||||
var syms []dataSym
|
var syms []dataSym
|
||||||
|
total := 0
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
gd, ok := d.(*ast.Globl)
|
gd, ok := d.(*ast.Globl)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -462,12 +679,19 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := gd.Name.Name
|
name := gd.Name.Name
|
||||||
if _, dup := index[name]; dup {
|
|
||||||
return nil, fmt.Errorf("duplicate GLOBL %q", name)
|
|
||||||
}
|
|
||||||
size := 0
|
size := 0
|
||||||
if gd.Size != nil && gd.Size.Imm.HasVal {
|
if gd.Size != nil && gd.Size.Imm.HasVal {
|
||||||
size = int(gd.Size.Imm.Val)
|
size = int(gd.Size.Imm.Val)
|
||||||
|
if gd.Size.Imm.Neg {
|
||||||
|
size = -size
|
||||||
|
}
|
||||||
|
if size < 0 || size > maxSymbolSize {
|
||||||
|
return nil, fmt.Errorf("GLOBL %q: symbol too large (%d bytes > %d bytes)", name, size, maxSymbolSize)
|
||||||
|
}
|
||||||
|
if total+size > maxDataSize {
|
||||||
|
return nil, fmt.Errorf("GLOBL %q: data section too large (%d bytes > %d bytes)", name, total+size, maxDataSize)
|
||||||
|
}
|
||||||
|
total += size
|
||||||
}
|
}
|
||||||
index[name] = len(syms)
|
index[name] = len(syms)
|
||||||
ds := dataSym{
|
ds := dataSym{
|
||||||
@@ -481,12 +705,14 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
switch f {
|
switch f {
|
||||||
case "RODATA":
|
case "RODATA":
|
||||||
ds.rodata = true
|
ds.rodata = true
|
||||||
|
case "NOPTR":
|
||||||
|
ds.noptr = true
|
||||||
case "DUPOK":
|
case "DUPOK":
|
||||||
ds.dupok = true
|
ds.dupok = true
|
||||||
default:
|
default:
|
||||||
// Legacy numeric flag constants (runtime/textflag.h):
|
// Legacy numeric flag constants (runtime/textflag.h):
|
||||||
// DUPOK is 2, RODATA is 8; combinations arrive as one
|
// DUPOK is 2, RODATA is 8, NOPTR is 16; combinations arrive
|
||||||
// number (e.g. 10 = RODATA|DUPOK).
|
// as one number (e.g. 10 = RODATA|DUPOK).
|
||||||
if n, err := strconv.Atoi(f); err == nil {
|
if n, err := strconv.Atoi(f); err == nil {
|
||||||
if n&2 != 0 {
|
if n&2 != 0 {
|
||||||
ds.dupok = true
|
ds.dupok = true
|
||||||
@@ -494,6 +720,9 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
if n&8 != 0 {
|
if n&8 != 0 {
|
||||||
ds.rodata = true
|
ds.rodata = true
|
||||||
}
|
}
|
||||||
|
if n&16 != 0 {
|
||||||
|
ds.noptr = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,20 +740,79 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
||||||
}
|
}
|
||||||
if dd.Value == nil || !dd.Value.Imm.HasVal {
|
if dd.Value == nil {
|
||||||
return nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name)
|
return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
|
||||||
}
|
}
|
||||||
w := dd.Width
|
w := dd.Width
|
||||||
switch w {
|
|
||||||
case 1, 2, 4, 8:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
|
||||||
}
|
|
||||||
off := dd.Name.Offset
|
off := dd.Name.Offset
|
||||||
buf := syms[i].buf
|
buf := syms[i].buf
|
||||||
if off < 0 || off+int64(w) > int64(len(buf)) {
|
if off < 0 || off+int64(w) > int64(len(buf)) {
|
||||||
return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
|
return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
|
||||||
}
|
}
|
||||||
|
// A symbol value ("DATA s+0(SB)/8, $other(SB)", the rt0 spelling)
|
||||||
|
// leaves the field zero and records a relocation against the named
|
||||||
|
// symbol: the linker patches the absolute address at this data
|
||||||
|
// offset. The toolchain emits the same shape, an R_ADDR of the
|
||||||
|
// DATA width with the value's offset as the addend, on every
|
||||||
|
// architecture.
|
||||||
|
if sym := dd.Value.Imm.Sym; !dd.Value.Imm.HasVal && sym != nil {
|
||||||
|
syms[i].relocs = append(syms[i].relocs, Reloc{
|
||||||
|
Off: int(off),
|
||||||
|
Name: sym.Name,
|
||||||
|
Addend: sym.Offset,
|
||||||
|
Kind: RelAddr,
|
||||||
|
Siz: uint8(w),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// A string or rune value ("DATA s+0(SB)/20, $"text"") writes its
|
||||||
|
// bytes into the field and leaves the rest zero, the toolchain's
|
||||||
|
// WriteString: the declared width must hold every byte, and any
|
||||||
|
// width is legal.
|
||||||
|
if s := dd.Value.Imm.Str; s != "" && !dd.Value.Imm.HasVal {
|
||||||
|
text, err := strconv.Unquote(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid string value %s", dd.Name.Name, s)
|
||||||
|
}
|
||||||
|
if len(text) > w {
|
||||||
|
return nil, fmt.Errorf("DATA %q: string of %d bytes does not fit width %d", dd.Name.Name, len(text), w)
|
||||||
|
}
|
||||||
|
copy(buf[off:], text)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// A floating-point value stores its IEEE-754 bits: /4 the float32
|
||||||
|
// rounding of the parsed double, /8 the full 64 bits, the
|
||||||
|
// toolchain's WriteFloat32 and WriteFloat64.
|
||||||
|
if f := dd.Value.Imm.Float; f != "" && !dd.Value.Imm.HasVal {
|
||||||
|
num, err := strconv.ParseFloat(f, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid floating-point value %q", dd.Name.Name, f)
|
||||||
|
}
|
||||||
|
if dd.Value.Imm.Neg {
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
var v uint64
|
||||||
|
switch w {
|
||||||
|
case 4:
|
||||||
|
v = uint64(math.Float32bits(float32(num)))
|
||||||
|
case 8:
|
||||||
|
v = math.Float64bits(num)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid width %d for a float (want 4 or 8)", dd.Name.Name, w)
|
||||||
|
}
|
||||||
|
for j := range w {
|
||||||
|
buf[off+int64(j)] = byte(v >> (8 * j))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !dd.Value.Imm.HasVal {
|
||||||
|
return nil, fmt.Errorf("DATA %q: value must be an integer immediate or a symbol address", dd.Name.Name)
|
||||||
|
}
|
||||||
|
switch w {
|
||||||
|
case 1, 2, 4, 8:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
||||||
|
}
|
||||||
v := dd.Value.Imm.Val
|
v := dd.Value.Imm.Val
|
||||||
if dd.Value.Imm.Neg {
|
if dd.Value.Imm.Neg {
|
||||||
v = -v
|
v = -v
|
||||||
|
|||||||
+409
-12
@@ -4,12 +4,115 @@
|
|||||||
package asm
|
package asm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestDuplicateDecls pins the duplicate-declaration rule against the
|
||||||
|
// toolchain's, measured with go tool asm (Go 1.27.1): a symbol declared
|
||||||
|
// twice in one file is rejected at the second declaration whether the pair
|
||||||
|
// is two TEXTs, two GLOBLs or one of each, and DUPOK never legalises it
|
||||||
|
// within a file (the toolchain's duperror.s testdata asserts the plain
|
||||||
|
// pair). DUPOK legalises duplicates across files, so each file of a pair
|
||||||
|
// assembles cleanly on its own, and symbol identity keeps the package
|
||||||
|
// prefix and the <> static marker: foo(SB), foo<>(SB) and other·foo(SB)
|
||||||
|
// are three symbols, not one. Every case runs through all four
|
||||||
|
// per-architecture entry points.
|
||||||
|
func TestDuplicateDecls(t *testing.T) {
|
||||||
|
targets := []struct {
|
||||||
|
goarch string
|
||||||
|
file string
|
||||||
|
asm func(*ast.File) (*Image, error)
|
||||||
|
}{
|
||||||
|
{"amd64", "dup_amd64.s", func(f *ast.File) (*Image, error) { return AssembleFile(f) }},
|
||||||
|
{"arm64", "dup_arm64.s", AssembleFileARM64},
|
||||||
|
{"riscv64", "dup_riscv64.s", AssembleFileRISCV},
|
||||||
|
{"loong64", "dup_loong64.s", AssembleFileLOONG64},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string // substring of the error, "" for no error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"plain TEXT duplicate rejected",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nTEXT foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared (other declaration on line 1)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DUPOK pair in one file rejected",
|
||||||
|
"TEXT foo(SB), DUPOK, $0\n\tRET\nTEXT foo(SB), DUPOK, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared (other declaration on line 1)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TEXT then GLOBL rejected",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nGLOBL foo(SB), NOPTR, $8\n",
|
||||||
|
`symbol "foo" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GLOBL then TEXT rejected",
|
||||||
|
"GLOBL foo(SB), NOPTR, $8\nTEXT foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"plain GLOBL duplicate rejected",
|
||||||
|
"GLOBL bar(SB), NOPTR, $8\nGLOBL bar(SB), NOPTR, $8\n",
|
||||||
|
`symbol "bar" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"static and package markers separate symbols",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nTEXT foo<>(SB), NOSPLIT, $0\n\tRET\nTEXT other\u00b7foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single DUPOK TEXT assembles",
|
||||||
|
"TEXT foo(SB), DUPOK, $0\n\tRET\n",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tg := range targets {
|
||||||
|
for _, c := range cases {
|
||||||
|
f, errs := parser.Parse(tg.file, c.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("%s/%s: parse: %v", tg.goarch, c.name, errs)
|
||||||
|
}
|
||||||
|
_, err := tg.asm(f)
|
||||||
|
if c.want == "" {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/%s: error %v, want none", tg.goarch, c.name, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil || !strings.Contains(err.Error(), c.want) {
|
||||||
|
t.Errorf("%s/%s: error %v, want substring %q", tg.goarch, c.name, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DUPOK pair across files is the case DUPOK exists for: the linker
|
||||||
|
// resolves it, the assembler never sees both sides, so each file of the
|
||||||
|
// pair assembles cleanly on its own.
|
||||||
|
for _, tg := range targets {
|
||||||
|
for _, name := range []string{"first", "second"} {
|
||||||
|
f, errs := parser.Parse(tg.file, "TEXT foo(SB), DUPOK, $0\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("%s/dupok cross-file %s: parse: %v", tg.goarch, name, errs)
|
||||||
|
}
|
||||||
|
if _, err := tg.asm(f); err != nil {
|
||||||
|
t.Errorf("%s/dupok cross-file %s: error %v, want none", tg.goarch, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAssembleFileStaticData checks the whole-image layout; code, padding
|
// TestAssembleFileStaticData checks the whole-image layout; code, padding
|
||||||
// and the data section; and that the RIP-relative displacements of static
|
// and the data section; and that the RIP-relative displacements of static
|
||||||
// symbol loads resolve to the right bytes.
|
// symbol loads resolve to the right bytes.
|
||||||
@@ -55,23 +158,15 @@ DATA small<>+0(SB)/4, $0x1234
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAssembleFileErrors checks the static-symbol error paths.
|
// TestAssembleFileErrors checks the static-symbol error paths. A reference
|
||||||
|
// to a static symbol no GLOBL defines defers to the linker exactly as the
|
||||||
|
// toolchain does (an external relocation), so it is not an error here.
|
||||||
func TestAssembleFileErrors(t *testing.T) {
|
func TestAssembleFileErrors(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
src string
|
src string
|
||||||
want string // substring of the error
|
want string // substring of the error
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
"undefined symbol",
|
|
||||||
`
|
|
||||||
#include "textflag.h"
|
|
||||||
TEXT ·f(SB), NOSPLIT, $0
|
|
||||||
VMOVDQU nope<>(SB), X0
|
|
||||||
RET
|
|
||||||
`,
|
|
||||||
"undefined symbol",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"DATA without GLOBL",
|
"DATA without GLOBL",
|
||||||
`
|
`
|
||||||
@@ -166,3 +261,305 @@ func TestCollectDataNumericFlags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCollectDataSymbolValue covers the symbol-valued DATA field ("DATA
|
||||||
|
// s+0(SB)/8, $other(SB)", the rt0 spelling): the field stays zero in the
|
||||||
|
// image and the relocation is recorded against the named symbol, whatever
|
||||||
|
// the file defines (a TEXT function, a GLOBL) or leaves external.
|
||||||
|
func TestCollectDataSymbolValue(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
||||||
|
MOVQ target+0(FP), AX
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $32
|
||||||
|
DATA holder+0(SB)/8, $·Keep(SB)
|
||||||
|
DATA holder+8(SB)/8, $·Keep+5(SB)
|
||||||
|
DATA holder+16(SB)/8, $holder(SB)
|
||||||
|
GLOBL spare(SB), NOPTR, $8
|
||||||
|
DATA spare+0(SB)/8, $extvar(SB)
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("f_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
byName := map[string]DataSymbol{}
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
byName[d.Name] = d
|
||||||
|
}
|
||||||
|
want := []struct {
|
||||||
|
sym string
|
||||||
|
off int
|
||||||
|
name string
|
||||||
|
addend int64
|
||||||
|
ext bool
|
||||||
|
}{
|
||||||
|
{"holder", 0, "Keep", 0, false},
|
||||||
|
{"holder", 8, "Keep", 5, false},
|
||||||
|
{"holder", 16, "holder", 0, false},
|
||||||
|
{"spare", 0, "extvar", 0, true},
|
||||||
|
}
|
||||||
|
var flat []struct {
|
||||||
|
sym string
|
||||||
|
r Reloc
|
||||||
|
}
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
for _, r := range d.Relocs {
|
||||||
|
flat = append(flat, struct {
|
||||||
|
sym string
|
||||||
|
r Reloc
|
||||||
|
}{d.Name, r})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(flat) != len(want) {
|
||||||
|
t.Fatalf("data relocations = %d, want %d", len(flat), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := flat[i]
|
||||||
|
r := g.r
|
||||||
|
if g.sym != w.sym {
|
||||||
|
t.Errorf("relocation %d sits on %q, want %q", i, g.sym, w.sym)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.Off != w.off || r.Name != w.name || r.Addend != w.addend || r.External != w.ext {
|
||||||
|
t.Errorf("relocation %d = {+%d %q addend %d ext %v}, want {+%d %q addend %d ext %v}",
|
||||||
|
i, r.Off, r.Name, r.Addend, r.External, w.off, w.name, w.addend, w.ext)
|
||||||
|
}
|
||||||
|
if r.Kind != RelAddr {
|
||||||
|
t.Errorf("relocation %d kind = %v, want RelAddr", i, r.Kind)
|
||||||
|
}
|
||||||
|
if r.Siz != 8 {
|
||||||
|
t.Errorf("relocation %d siz = %d, want 8", i, r.Siz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The fields themselves stay zero: only the linker fills them.
|
||||||
|
for _, b := range img.Data {
|
||||||
|
if b != 0 {
|
||||||
|
t.Fatal("data section is not all zero before relocation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(img.Externals) != 1 || img.Externals[0] != "extvar" {
|
||||||
|
t.Errorf("Externals = %v, want [extvar]", img.Externals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectDataSymbolReloc pins the GOOBJ record a symbol-valued DATA
|
||||||
|
// field produces, against the shape the toolchain emits for the same
|
||||||
|
// source: an R_ADDR of the DATA width at the field offset, pkgIdxNone plus
|
||||||
|
// the non-package definition index when the target is the file's own TEXT
|
||||||
|
// function (the rt0 lib entry spelling).
|
||||||
|
func TestGOObjectDataSymbolReloc(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("f_amd64.s", `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
||||||
|
RET
|
||||||
|
GLOBL holder(SB), NOPTR, $16
|
||||||
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.GOObject("main", "f_amd64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObject: %v", err)
|
||||||
|
}
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
// Walk every relocation record; the data record is the one of Siz 8
|
||||||
|
// and type R_ADDR.
|
||||||
|
var off, add int64
|
||||||
|
var pkg, sym uint32
|
||||||
|
found := false
|
||||||
|
for data := v.blk(blkReloc); len(data) >= 23; data = data[23:] {
|
||||||
|
if data[4] != 8 || binary.LittleEndian.Uint16(data[5:]) != relocAddr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
off = int64(int32(binary.LittleEndian.Uint32(data[0:])))
|
||||||
|
add = int64(binary.LittleEndian.Uint64(data[7:]))
|
||||||
|
pkg = binary.LittleEndian.Uint32(data[15:])
|
||||||
|
sym = binary.LittleEndian.Uint32(data[19:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("no data relocation record in the object")
|
||||||
|
}
|
||||||
|
if off != 0 || add != 5 {
|
||||||
|
t.Errorf("data reloc = {off %d addend %d}, want {off 0 addend 5}", off, add)
|
||||||
|
}
|
||||||
|
if pkg != pkgIdxNone {
|
||||||
|
t.Errorf("data reloc pkg = %#x, want pkgIdxNone (the TEXT function)", pkg)
|
||||||
|
}
|
||||||
|
// The function's non-package definition index: the four pc tables
|
||||||
|
// precede it, so index 4.
|
||||||
|
if sym != 4 {
|
||||||
|
t.Errorf("data reloc sym = %d, want 4", sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectDataSymbolLink is the end-to-end proof for symbol-valued DATA
|
||||||
|
// fields: the gasm object is substituted for the toolchain's and re-linked,
|
||||||
|
// then executed, and the linked data word must hold the real address of the
|
||||||
|
// function the DATA line named (runtime.FuncForPC identifies it).
|
||||||
|
func TestGOObjectDataSymbolLink(t *testing.T) {
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
dir := t.TempDir()
|
||||||
|
asmSrc := `#include "textflag.h"
|
||||||
|
GLOBL entry(SB), NOPTR, $8
|
||||||
|
DATA entry+0(SB)/8, $·keepme(SB)
|
||||||
|
|
||||||
|
TEXT ·keepme(SB), NOSPLIT, $0-0
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·entryptr(SB), NOSPLIT, $0-8
|
||||||
|
MOVQ entry+0(SB), AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main_amd64.s"), []byte(asmSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mainSrc := `package main
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
func keepme()
|
||||||
|
func entryptr() uintptr
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pc := entryptr()
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
panic("the entry word does not point at a function")
|
||||||
|
}
|
||||||
|
if fn.Name() != "main.keepme" {
|
||||||
|
panic("the entry word points at " + fn.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module dlink\n\ngo 1.21\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the build: the package archive's asm object and the link line.
|
||||||
|
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
|
||||||
|
build.Dir = dir
|
||||||
|
buildLog, err := build.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
|
}
|
||||||
|
st := parseBuildLog(t, buildLog, "main_amd64.s")
|
||||||
|
defer os.RemoveAll(st.work)
|
||||||
|
|
||||||
|
// Assemble the same source with gasm and substitute the object.
|
||||||
|
src, err := os.ReadFile(filepath.Join(dir, "main_amd64.s"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse("main_amd64.s", string(src))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
// The package path is "main": the linker resolves the Go code's
|
||||||
|
// references against main.<name>, so the object must define the symbols
|
||||||
|
// under that prefix whatever the module is called.
|
||||||
|
gasmObj, err := img.GOObject("main", "main_amd64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObject: %v", err)
|
||||||
|
}
|
||||||
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"), gasmObj)
|
||||||
|
|
||||||
|
// The linked program must run and find the right function behind the
|
||||||
|
// data word.
|
||||||
|
out, err := exec.Command(filepath.Join(dir, "prog2")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("linked program failed: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCollectDataFloatAndStringValues covers the non-integer DATA values the
|
||||||
|
// runtime's math and asm files use: floating-point initialisers store their
|
||||||
|
// IEEE-754 bits (/4 the float32 rounding, /8 the full double) and string
|
||||||
|
// initialisers write their bytes zero-padded within the declared width.
|
||||||
|
func TestCollectDataFloatAndStringValues(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
||||||
|
RET
|
||||||
|
GLOBL vals<>(SB), RODATA, $44
|
||||||
|
DATA vals<>+0(SB)/8, $0.5
|
||||||
|
DATA vals<>+8(SB)/8, $-1.0
|
||||||
|
DATA vals<>+16(SB)/4, $1.5
|
||||||
|
DATA vals<>+20(SB)/16, $"call frame too "
|
||||||
|
DATA vals<>+36(SB)/4, $"hi"
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("fvals_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
byName := map[string]DataSymbol{}
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
byName[d.Name] = d
|
||||||
|
}
|
||||||
|
d := byName["vals"]
|
||||||
|
if d.Size != 44 {
|
||||||
|
t.Fatalf("vals size = %d, want 44", d.Size)
|
||||||
|
}
|
||||||
|
buf := img.Data[d.Offset : d.Offset+44]
|
||||||
|
// 0.5 = 0x3FE0000000000000, -1.0 = 0xBFF0000000000000 (float64);
|
||||||
|
// 1.5 = 0x3FC00000 (float32).
|
||||||
|
for _, c := range []struct {
|
||||||
|
off int
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{0, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F}},
|
||||||
|
{8, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF}},
|
||||||
|
{16, []byte{0x00, 0x00, 0xC0, 0x3F}},
|
||||||
|
{20, []byte("call frame too ")},
|
||||||
|
{36, []byte{'h', 'i', 0x00, 0x00}},
|
||||||
|
} {
|
||||||
|
if string(buf[c.off:c.off+len(c.want)]) != string(c.want) {
|
||||||
|
t.Errorf("vals+%d: got % x, want % x", c.off, buf[c.off:c.off+len(c.want)], c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCollectDataValueErrors pins the value-kind width rules: a float needs
|
||||||
|
// width 4 or 8, a string must fit its declared width, and a bad float
|
||||||
|
// literal is diagnosed rather than stored.
|
||||||
|
func TestCollectDataValueErrors(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
`GLOBL v<>(SB), RODATA, $4
|
||||||
|
DATA v<>+0(SB)/1, $0.5`,
|
||||||
|
`GLOBL v<>(SB), RODATA, $2
|
||||||
|
DATA v<>+0(SB)/2, $"toolarge"`,
|
||||||
|
}
|
||||||
|
for i, src := range cases {
|
||||||
|
full := "#include \"textflag.h\"\nTEXT ·Keep(SB), NOSPLIT, $0-8\n\tRET\n" + src
|
||||||
|
f, errs := parser.Parse(fmt.Sprintf("verr%d_amd64.s", i), full)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("case %d parse: %v", i, errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFile(f); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+887
-67
File diff suppressed because it is too large.
Load diff
+509
-14
@@ -257,6 +257,37 @@ func l64WordsLE(ws ...uint32) []byte {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64firr14Words returns the words the 2RI14 families (LL/LLW/LLV, SC/SCW/
|
||||||
|
// SCV, MOVWP/MOVVP) encode to for a 4-aligned byte offset. The toolchain
|
||||||
|
// classifies the memory operand by span, with the class constants' -2 slop
|
||||||
|
// included verbatim: a signed 16-bit offset (C_SOREG_16) rides in si14 alone;
|
||||||
|
// a 32-bit one (C_LOREG_32) splits between addu16i.d (bits 31:16, materialised
|
||||||
|
// in R30, the assembler temp) and si14 (bits 15:2); anything wider
|
||||||
|
// (C_LOREG_64) builds the whole constant in R30 through lu12i.w + ori +
|
||||||
|
// lu32i.d + lu52i.d and folds the base into it. The caller adjusts the
|
||||||
|
// opcode for the 64-bit span's load-direction quirks before arriving here.
|
||||||
|
func l64firr14Words(op uint32, off, rj, rd int) []byte {
|
||||||
|
switch {
|
||||||
|
case off >= -32766 && off < 32766:
|
||||||
|
return l64wordLE(l64irr14(op, off>>2, rj, rd))
|
||||||
|
case off >= -2147483650 && off < 2147483646:
|
||||||
|
return l64WordsLE(
|
||||||
|
l64irr16(l64InstrTable["ADDV16"].op, off>>16, 0, 30),
|
||||||
|
l64rrr(l64DualTable["ADDV"].rrr, rj, 30, 30),
|
||||||
|
l64irr14(op, off>>2, 30, rd),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return l64WordsLE(
|
||||||
|
l64ir(l64InstrTable["LU12IW"].op, off>>12, 30),
|
||||||
|
l64irr(l64DualTable["OR"].imm, off&0xFFF, 30, 30),
|
||||||
|
l64ir(l64InstrTable["LU32ID"].op, off>>32, 30),
|
||||||
|
l64irr(l64InstrTable["LU52ID"].op, off>>52, 30, 30),
|
||||||
|
l64rrr(l64DualTable["ADDV"].rrr, 30, rj, rj),
|
||||||
|
l64irr14(op, 0, rj, rd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- instruction formats ----
|
// ---- instruction formats ----
|
||||||
|
|
||||||
type l64Format uint8
|
type l64Format uint8
|
||||||
@@ -278,6 +309,9 @@ const (
|
|||||||
l64Fpreld // preld (2RI12 + 5-bit hint)
|
l64Fpreld // preld (2RI12 + 5-bit hint)
|
||||||
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
|
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
|
||||||
l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc
|
l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc
|
||||||
|
l64Fvvvv // 4R vector shuffle: op | va<<15 | vk<<10 | vj<<5 | vd
|
||||||
|
l64Fllsc // acquire/release LL/SC (2R against a zero-offset memory operand)
|
||||||
|
l64Fscq // sc.q: op | middle<<10 | base<<5 | first against a zero-offset memory operand
|
||||||
)
|
)
|
||||||
|
|
||||||
// l64Enc is one instruction's encoding: its bit layout (format) and the
|
// l64Enc is one instruction's encoding: its bit layout (format) and the
|
||||||
@@ -336,8 +370,13 @@ var l64VecImmInfo = map[string]l64VecImmEnc{}
|
|||||||
// vpcnt.v).
|
// vpcnt.v).
|
||||||
var l64Vec2R = map[string]bool{}
|
var l64Vec2R = map[string]bool{}
|
||||||
|
|
||||||
// l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants (pre-shifted to bit
|
// l64Vec4R marks the four-operand vector mnemonics (INSTR va, vk, vj, vd,
|
||||||
// 15), read off `go tool objdump` of GOARCH=loong64 `go tool asm` kernels.
|
// such as vshuf.b).
|
||||||
|
var l64Vec4R = map[string]bool{}
|
||||||
|
|
||||||
|
// l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants, each pre-shifted to
|
||||||
|
// its exact bit range, read off `go tool objdump` of GOARCH=loong64
|
||||||
|
// `go tool asm` kernels and the toolchain's specialLsxMovInst table.
|
||||||
type l64VmovqEnc struct {
|
type l64VmovqEnc struct {
|
||||||
ld, st, ldx, stx uint32 // plain and indexed load/store
|
ld, st, ldx, stx uint32 // plain and indexed load/store
|
||||||
replB, replH, replW, replD uint32 // vldrepl: load and replicate element
|
replB, replH, replW, replD uint32 // vldrepl: load and replicate element
|
||||||
@@ -345,6 +384,11 @@ type l64VmovqEnc struct {
|
|||||||
ins uint32 // vinsgr2vr element insert
|
ins uint32 // vinsgr2vr element insert
|
||||||
dup uint32 // vreplgr2vr duplicate (width in [11:10])
|
dup uint32 // vreplgr2vr duplicate (width in [11:10])
|
||||||
move uint32 // vori.b/xvori.b $0 register move
|
move uint32 // vori.b/xvori.b $0 register move
|
||||||
|
rveiB, rveiH, rveiW, rveiD uint32 // vreplvei: broadcast one element (LSX)
|
||||||
|
rve0B, rve0H, rve0W uint32 // xvreplve0 broadcast of element zero (LASX)
|
||||||
|
rve0D, rve0Q uint32 // xvreplve0.{d,q}, ditto
|
||||||
|
xinsW, xinsD uint32 // xvinsve0: insert element zero (LASX)
|
||||||
|
xpickW, xpickD uint32 // xvpickve: extract element (LASX)
|
||||||
}
|
}
|
||||||
|
|
||||||
var l64VmovqTable = map[bool]l64VmovqEnc{
|
var l64VmovqTable = map[bool]l64VmovqEnc{
|
||||||
@@ -353,12 +397,17 @@ var l64VmovqTable = map[bool]l64VmovqEnc{
|
|||||||
replB: 0x6100 << 15, replH: 0x6080 << 15, replW: 0x6040 << 15, replD: 0x6020 << 15,
|
replB: 0x6100 << 15, replH: 0x6080 << 15, replW: 0x6040 << 15, replD: 0x6020 << 15,
|
||||||
pickS: 0xE5DF << 15, pickU: 0xE5E7 << 15,
|
pickS: 0xE5DF << 15, pickU: 0xE5E7 << 15,
|
||||||
ins: 0xE5D7 << 15, dup: 0xE53E << 15, move: 0xE65A << 15,
|
ins: 0xE5D7 << 15, dup: 0xE53E << 15, move: 0xE65A << 15,
|
||||||
|
rveiB: 0x01CBDE << 14, rveiH: 0x0397BE << 13, rveiW: 0x072F7E << 12, rveiD: 0x0E5EFE << 11,
|
||||||
},
|
},
|
||||||
true: { // XVMOVQ, the LASX (X) bank
|
true: { // XVMOVQ, the LASX (X) bank
|
||||||
ld: 0x5900 << 15, st: 0x5980 << 15, ldx: 0x7090 << 15, stx: 0x7098 << 15,
|
ld: 0x5900 << 15, st: 0x5980 << 15, ldx: 0x7090 << 15, stx: 0x7098 << 15,
|
||||||
replB: 0x6500 << 15, replH: 0x6480 << 15, replW: 0x6440 << 15, replD: 0x6420 << 15,
|
replB: 0x6500 << 15, replH: 0x6480 << 15, replW: 0x6440 << 15, replD: 0x6420 << 15,
|
||||||
pickS: 0xEDDF << 15, pickU: 0xEDE7 << 15,
|
pickS: 0xEDDF << 15, pickU: 0xEDE7 << 15,
|
||||||
ins: 0xEDD7 << 15, dup: 0xED3E << 15, move: 0xEE5A << 15,
|
ins: 0xEDD7 << 15, dup: 0xED3E << 15, move: 0xEE5A << 15,
|
||||||
|
rve0B: 0x1DC1C0 << 10, rve0H: 0x1DC1E0 << 10, rve0W: 0x1DC1F0 << 10,
|
||||||
|
rve0D: 0x1DC1F8 << 10, rve0Q: 0x1DC1FC << 10,
|
||||||
|
xinsW: 0x03B7FE << 13, xinsD: 0x076FFE << 12,
|
||||||
|
xpickW: 0x03B81E << 13, xpickD: 0x07703E << 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +417,7 @@ func init() {
|
|||||||
"ADD": 0x20 << 15, "ADDW": 0x20 << 15, "ADDV": 0x21 << 15, "ADDVU": 0x21 << 15,
|
"ADD": 0x20 << 15, "ADDW": 0x20 << 15, "ADDV": 0x21 << 15, "ADDVU": 0x21 << 15,
|
||||||
"SUB": 0x22 << 15, "SUBW": 0x22 << 15, "SUBV": 0x23 << 15, "SUBVU": 0x23 << 15,
|
"SUB": 0x22 << 15, "SUBW": 0x22 << 15, "SUBV": 0x23 << 15, "SUBVU": 0x23 << 15,
|
||||||
"SGT": 0x24 << 15, "SGTU": 0x25 << 15,
|
"SGT": 0x24 << 15, "SGTU": 0x25 << 15,
|
||||||
"MASKEQZ": 0x26 << 15, "MASKNEZ": 0x27 << 15, "SCQ": 0x070AE << 15,
|
"MASKEQZ": 0x26 << 15, "MASKNEZ": 0x27 << 15,
|
||||||
"NOR": 0x28 << 15, "AND": 0x29 << 15, "OR": 0x2a << 15, "XOR": 0x2b << 15,
|
"NOR": 0x28 << 15, "AND": 0x29 << 15, "OR": 0x2a << 15, "XOR": 0x2b << 15,
|
||||||
"ORN": 0x2c << 15, "ANDN": 0x2d << 15,
|
"ORN": 0x2c << 15, "ANDN": 0x2d << 15,
|
||||||
"SLL": 0x2e << 15, "SRL": 0x2f << 15, "SRA": 0x30 << 15,
|
"SLL": 0x2e << 15, "SRL": 0x2f << 15, "SRA": 0x30 << 15,
|
||||||
@@ -445,6 +494,14 @@ func init() {
|
|||||||
// bank is the FP registers (the toolchain spells it `FFINTDV F0, F1`),
|
// bank is the FP registers (the toolchain spells it `FFINTDV F0, F1`),
|
||||||
// so the entry stays on the 2R integer/FP format.
|
// so the entry stays on the 2R integer/FP format.
|
||||||
"FFINTDV": 0x474a << 10,
|
"FFINTDV": 0x474a << 10,
|
||||||
|
// The rest of the scalar conversions (all F-bank, 2R).
|
||||||
|
"FFINTFW": 0x4744 << 10, // ffint.s.w
|
||||||
|
"FFINTFV": 0x4746 << 10, // ffint.s.l
|
||||||
|
"FFINTDW": 0x4748 << 10, // ffint.d.w
|
||||||
|
"FTINTWF": 0x46c1 << 10, // ftint.w.s
|
||||||
|
"FTINTWD": 0x46c2 << 10, // ftint.w.d
|
||||||
|
"FTINTVF": 0x46c9 << 10, // ftint.l.s
|
||||||
|
"FTINTVD": 0x46ca << 10, // ftint.l.d
|
||||||
}
|
}
|
||||||
for m, op := range rr {
|
for m, op := range rr {
|
||||||
l64InstrTable[m] = l64Enc{format: l64Frr, op: op}
|
l64InstrTable[m] = l64Enc{format: l64Frr, op: op}
|
||||||
@@ -454,6 +511,20 @@ func init() {
|
|||||||
l64InstrTable["RDTIMEHW"] = l64Enc{format: l64Frdtime, op: 0x19 << 10}
|
l64InstrTable["RDTIMEHW"] = l64Enc{format: l64Frdtime, op: 0x19 << 10}
|
||||||
l64InstrTable["RDTIMED"] = l64Enc{format: l64Frdtime, op: 0x1a << 10}
|
l64InstrTable["RDTIMED"] = l64Enc{format: l64Frdtime, op: 0x1a << 10}
|
||||||
|
|
||||||
|
// Acquire/release LL/SC (2R against a zero-offset memory operand):
|
||||||
|
// LLACQV (Rj), Rd loads, SCRELV Rd, (Rj) stores, both encoding
|
||||||
|
// op | rj<<5 | rd. Opcodes from cmd/internal/obj/loong64/instOp.go
|
||||||
|
// (ll.acq.{w,d}, sc.rel.{w,d}).
|
||||||
|
l64InstrTable["LLACQW"] = l64Enc{format: l64Fllsc, op: 0x0E15E0 << 10}
|
||||||
|
l64InstrTable["SCRELW"] = l64Enc{format: l64Fllsc, op: 0x0E15E1 << 10}
|
||||||
|
l64InstrTable["LLACQV"] = l64Enc{format: l64Fllsc, op: 0x0E15E2 << 10}
|
||||||
|
l64InstrTable["SCRELV"] = l64Enc{format: l64Fllsc, op: 0x0E15E3 << 10}
|
||||||
|
|
||||||
|
// SCQ (sc.q first, middle, (base)) keeps its own operand order: the
|
||||||
|
// encoding is op | middle<<10 | base<<5 | first, the memory operand's
|
||||||
|
// base in the rj field, not the toolchain's generic 3R layout.
|
||||||
|
l64InstrTable["SCQ"] = l64Enc{format: l64Fscq, op: 0x070AE << 15}
|
||||||
|
|
||||||
// The dual-form arithmetic mnemonics (register 3R + immediate 2RI12),
|
// The dual-form arithmetic mnemonics (register 3R + immediate 2RI12),
|
||||||
// selected by the operand kind; the shift mnemonics pair the 3R form
|
// selected by the operand kind; the shift mnemonics pair the 3R form
|
||||||
// with a 5/6-bit shift immediate.
|
// with a 5/6-bit shift immediate.
|
||||||
@@ -568,6 +639,12 @@ func init() {
|
|||||||
"AMADDDBW": 0x070D4 << 15, "AMADDDBV": 0x070D5 << 15,
|
"AMADDDBW": 0x070D4 << 15, "AMADDDBV": 0x070D5 << 15,
|
||||||
"AMANDDBW": 0x070D6 << 15, "AMANDDBV": 0x070D7 << 15,
|
"AMANDDBW": 0x070D6 << 15, "AMANDDBV": 0x070D7 << 15,
|
||||||
"AMORDBW": 0x070D8 << 15, "AMORDBV": 0x070D9 << 15,
|
"AMORDBW": 0x070D8 << 15, "AMORDBV": 0x070D9 << 15,
|
||||||
|
// The remaining _dbar exchange variants (loong64enc1.s).
|
||||||
|
"AMXORDBW": 0x070DA << 15, "AMXORDBV": 0x070DB << 15,
|
||||||
|
"AMMAXDBW": 0x070DC << 15, "AMMAXDBV": 0x070DD << 15,
|
||||||
|
"AMMINDBW": 0x070DE << 15, "AMMINDBV": 0x070DF << 15,
|
||||||
|
"AMMAXDBWU": 0x070E0 << 15, "AMMAXDBVU": 0x070E1 << 15,
|
||||||
|
"AMMINDBWU": 0x070E2 << 15, "AMMINDBVU": 0x070E3 << 15,
|
||||||
}
|
}
|
||||||
for m, op := range am {
|
for m, op := range am {
|
||||||
l64InstrTable[m] = l64Enc{format: l64Fam, op: op}
|
l64InstrTable[m] = l64Enc{format: l64Fam, op: op}
|
||||||
@@ -590,6 +667,240 @@ func init() {
|
|||||||
"XVANDV": {0xEA4C << 15, true}, "XVXORV": {0xEA4E << 15, true},
|
"XVANDV": {0xEA4C << 15, true}, "XVXORV": {0xEA4E << 15, true},
|
||||||
"XVSEQB": {0xE800 << 15, true}, "XVSEQV": {0xE803 << 15, true},
|
"XVSEQB": {0xE800 << 15, true}, "XVSEQV": {0xE803 << 15, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The integer and FP add/subtract families: [X]VADD and [X]VSUB by lane
|
||||||
|
// width, plus the [X]VSADD/[X]VSSUB saturating pairs.
|
||||||
|
// Opcodes transcribed from the toolchain's loong64enc1.s.
|
||||||
|
addsub := map[string]l64Vec3Enc{
|
||||||
|
"VADDB": {0xE014 << 15, false}, "VADDH": {0xE015 << 15, false},
|
||||||
|
"VADDD": {0xE262 << 15, false}, "VADDF": {0xE261 << 15, false},
|
||||||
|
"VADDQ": {0xE25A << 15, false},
|
||||||
|
"VSUBB": {0xE018 << 15, false}, "VSUBH": {0xE019 << 15, false},
|
||||||
|
"VSUBW": {0xE01A << 15, false}, "VSUBV": {0xE01B << 15, false},
|
||||||
|
"VSUBQ": {0xE25B << 15, false},
|
||||||
|
"VSUBF": {0xE265 << 15, false}, "VSUBD": {0xE266 << 15, false},
|
||||||
|
"VSADDB": {0xE08C << 15, false}, "VSADDH": {0xE08D << 15, false},
|
||||||
|
"VSADDW": {0xE08E << 15, false}, "VSADDV": {0xE08F << 15, false},
|
||||||
|
"VSADDBU": {0xE094 << 15, false}, "VSADDHU": {0xE095 << 15, false},
|
||||||
|
"VSADDWU": {0xE096 << 15, false}, "VSADDVU": {0xE097 << 15, false},
|
||||||
|
"VSSUBB": {0xE090 << 15, false}, "VSSUBH": {0xE091 << 15, false},
|
||||||
|
"VSSUBW": {0xE092 << 15, false}, "VSSUBV": {0xE093 << 15, false},
|
||||||
|
"VSSUBBU": {0xE098 << 15, false}, "VSSUBHU": {0xE099 << 15, false},
|
||||||
|
"VSSUBWU": {0xE09A << 15, false}, "VSSUBVU": {0xE09B << 15, false},
|
||||||
|
"XVADDB": {0xE814 << 15, true}, "XVADDH": {0xE815 << 15, true},
|
||||||
|
"XVADDW": {0xE816 << 15, true},
|
||||||
|
"XVADDD": {0xEA62 << 15, true}, "XVADDF": {0xEA61 << 15, true},
|
||||||
|
"XVADDQ": {0xEA5A << 15, true},
|
||||||
|
"XVSUBB": {0xE818 << 15, true}, "XVSUBH": {0xE819 << 15, true},
|
||||||
|
"XVSUBW": {0xE81A << 15, true}, "XVSUBV": {0xE81B << 15, true},
|
||||||
|
"XVSUBQ": {0xEA5B << 15, true},
|
||||||
|
"XVSUBF": {0xEA65 << 15, true}, "XVSUBD": {0xEA66 << 15, true},
|
||||||
|
"XVSADDB": {0xE88C << 15, true}, "XVSADDH": {0xE88D << 15, true},
|
||||||
|
"XVSADDW": {0xE88E << 15, true}, "XVSADDV": {0xE88F << 15, true},
|
||||||
|
"XVSADDBU": {0xE894 << 15, true}, "XVSADDHU": {0xE895 << 15, true},
|
||||||
|
"XVSADDWU": {0xE896 << 15, true}, "XVSADDVU": {0xE897 << 15, true},
|
||||||
|
"XVSSUBB": {0xE890 << 15, true}, "XVSSUBH": {0xE891 << 15, true},
|
||||||
|
"XVSSUBW": {0xE892 << 15, true}, "XVSSUBV": {0xE893 << 15, true},
|
||||||
|
"XVSSUBBU": {0xE898 << 15, true}, "XVSSUBHU": {0xE899 << 15, true},
|
||||||
|
"XVSSUBWU": {0xE89A << 15, true}, "XVSSUBVU": {0xE89B << 15, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// The multiply families: plain and high-half [X]VMUL/[X]VMUH, the
|
||||||
|
// widening [X]VMULW{EV,OD} ladder and its accumulating [X]VMADDW twins,
|
||||||
|
// plus the [X]VMADD/[X]VMSUB fused multiply-add and the [X]VDIV/[X]VMOD
|
||||||
|
// divide and modulo pairs.
|
||||||
|
muldiv := map[string]l64Vec3Enc{
|
||||||
|
"VMULB": {0xE108 << 15, false}, "VMULH": {0xE109 << 15, false},
|
||||||
|
"VMULW": {0xE10A << 15, false}, "VMULV": {0xE10B << 15, false},
|
||||||
|
"VMUHB": {0xE10C << 15, false}, "VMUHH": {0xE10D << 15, false},
|
||||||
|
"VMUHW": {0xE10E << 15, false}, "VMUHV": {0xE10F << 15, false},
|
||||||
|
"VMUHBU": {0xE110 << 15, false}, "VMUHHU": {0xE111 << 15, false},
|
||||||
|
"VMUHWU": {0xE112 << 15, false}, "VMUHVU": {0xE113 << 15, false},
|
||||||
|
"VMULWEVHB": {0xE120 << 15, false}, "VMULWEVWH": {0xE121 << 15, false},
|
||||||
|
"VMULWEVVW": {0xE122 << 15, false}, "VMULWEVQV": {0xE123 << 15, false},
|
||||||
|
"VMULWODHB": {0xE124 << 15, false}, "VMULWODWH": {0xE125 << 15, false},
|
||||||
|
"VMULWODVW": {0xE126 << 15, false}, "VMULWODQV": {0xE127 << 15, false},
|
||||||
|
"VMULWEVHBU": {0xE130 << 15, false}, "VMULWEVWHU": {0xE131 << 15, false},
|
||||||
|
"VMULWEVVWU": {0xE132 << 15, false}, "VMULWEVQVU": {0xE133 << 15, false},
|
||||||
|
"VMULWODHBU": {0xE134 << 15, false}, "VMULWODWHU": {0xE135 << 15, false},
|
||||||
|
"VMULWODVWU": {0xE136 << 15, false}, "VMULWODQVU": {0xE137 << 15, false},
|
||||||
|
"VMULWEVHBUB": {0xE140 << 15, false}, "VMULWEVWHUH": {0xE141 << 15, false},
|
||||||
|
"VMULWEVVWUW": {0xE142 << 15, false}, "VMULWEVQVUV": {0xE143 << 15, false},
|
||||||
|
"VMULWODHBUB": {0xE144 << 15, false}, "VMULWODWHUH": {0xE145 << 15, false},
|
||||||
|
"VMULWODVWUW": {0xE146 << 15, false}, "VMULWODQVUV": {0xE147 << 15, false},
|
||||||
|
"VMADDB": {0xE150 << 15, false}, "VMADDH": {0xE151 << 15, false},
|
||||||
|
"VMADDW": {0xE152 << 15, false}, "VMADDV": {0xE153 << 15, false},
|
||||||
|
"VMSUBB": {0xE154 << 15, false}, "VMSUBH": {0xE155 << 15, false},
|
||||||
|
"VMSUBW": {0xE156 << 15, false}, "VMSUBV": {0xE157 << 15, false},
|
||||||
|
"VMADDWEVHB": {0xE158 << 15, false}, "VMADDWEVWH": {0xE159 << 15, false},
|
||||||
|
"VMADDWEVVW": {0xE15A << 15, false}, "VMADDWEVQV": {0xE15B << 15, false},
|
||||||
|
"VMADDWODHB": {0xE15C << 15, false}, "VMADDWODWH": {0xE15D << 15, false},
|
||||||
|
"VMADDWODVW": {0xE15E << 15, false}, "VMADDWODQV": {0xE15F << 15, false},
|
||||||
|
"VMADDWEVHBU": {0xE168 << 15, false}, "VMADDWEVWHU": {0xE169 << 15, false},
|
||||||
|
"VMADDWEVVWU": {0xE16A << 15, false}, "VMADDWEVQVU": {0xE16B << 15, false},
|
||||||
|
"VMADDWODHBU": {0xE16C << 15, false}, "VMADDWODWHU": {0xE16D << 15, false},
|
||||||
|
"VMADDWODVWU": {0xE16E << 15, false}, "VMADDWODQVU": {0xE16F << 15, false},
|
||||||
|
"VMADDWEVHBUB": {0xE178 << 15, false}, "VMADDWEVWHUH": {0xE179 << 15, false},
|
||||||
|
"VMADDWEVVWUW": {0xE17A << 15, false}, "VMADDWEVQVUV": {0xE17B << 15, false},
|
||||||
|
"VMADDWODHBUB": {0xE17C << 15, false}, "VMADDWODWHUH": {0xE17D << 15, false},
|
||||||
|
"VMADDWODVWUW": {0xE17E << 15, false}, "VMADDWODQVUV": {0xE17F << 15, false},
|
||||||
|
"VDIVB": {0xE1C0 << 15, false}, "VDIVH": {0xE1C1 << 15, false},
|
||||||
|
"VDIVW": {0xE1C2 << 15, false}, "VDIVV": {0xE1C3 << 15, false},
|
||||||
|
"VMODB": {0xE1C4 << 15, false}, "VMODH": {0xE1C5 << 15, false},
|
||||||
|
"VMODW": {0xE1C6 << 15, false}, "VMODV": {0xE1C7 << 15, false},
|
||||||
|
"VDIVBU": {0xE1C8 << 15, false}, "VDIVHU": {0xE1C9 << 15, false},
|
||||||
|
"VDIVWU": {0xE1CA << 15, false}, "VDIVVU": {0xE1CB << 15, false},
|
||||||
|
"VMODBU": {0xE1CC << 15, false}, "VMODHU": {0xE1CD << 15, false},
|
||||||
|
"VMODWU": {0xE1CE << 15, false}, "VMODVU": {0xE1CF << 15, false},
|
||||||
|
"VMULF": {0xE271 << 15, false}, "VMULD": {0xE272 << 15, false},
|
||||||
|
"VDIVF": {0xE275 << 15, false}, "VDIVD": {0xE276 << 15, false},
|
||||||
|
"XVMULB": {0xE908 << 15, true}, "XVMULH": {0xE909 << 15, true},
|
||||||
|
"XVMULW": {0xE90A << 15, true}, "XVMULV": {0xE90B << 15, true},
|
||||||
|
"XVMUHB": {0xE90C << 15, true}, "XVMUHH": {0xE90D << 15, true},
|
||||||
|
"XVMUHW": {0xE90E << 15, true}, "XVMUHV": {0xE90F << 15, true},
|
||||||
|
"XVMUHBU": {0xE910 << 15, true}, "XVMUHHU": {0xE911 << 15, true},
|
||||||
|
"XVMUHWU": {0xE912 << 15, true}, "XVMUHVU": {0xE913 << 15, true},
|
||||||
|
"XVMULWEVHB": {0xE920 << 15, true}, "XVMULWEVWH": {0xE921 << 15, true},
|
||||||
|
"XVMULWEVVW": {0xE922 << 15, true}, "XVMULWEVQV": {0xE923 << 15, true},
|
||||||
|
"XVMULWODHB": {0xE924 << 15, true}, "XVMULWODWH": {0xE925 << 15, true},
|
||||||
|
"XVMULWODVW": {0xE926 << 15, true}, "XVMULWODQV": {0xE927 << 15, true},
|
||||||
|
"XVMULWEVHBU": {0xE930 << 15, true}, "XVMULWEVWHU": {0xE931 << 15, true},
|
||||||
|
"XVMULWEVVWU": {0xE932 << 15, true}, "XVMULWEVQVU": {0xE933 << 15, true},
|
||||||
|
"XVMULWODHBU": {0xE934 << 15, true}, "XVMULWODWHU": {0xE935 << 15, true},
|
||||||
|
"XVMULWODVWU": {0xE936 << 15, true}, "XVMULWODQVU": {0xE937 << 15, true},
|
||||||
|
"XVMULWEVHBUB": {0xE940 << 15, true}, "XVMULWEVWHUH": {0xE941 << 15, true},
|
||||||
|
"XVMULWEVVWUW": {0xE942 << 15, true}, "XVMULWEVQVUV": {0xE943 << 15, true},
|
||||||
|
"XVMULWODHBUB": {0xE944 << 15, true}, "XVMULWODWHUH": {0xE945 << 15, true},
|
||||||
|
"XVMULWODVWUW": {0xE946 << 15, true}, "XVMULWODQVUV": {0xE947 << 15, true},
|
||||||
|
"XVMADDB": {0xE950 << 15, true}, "XVMADDH": {0xE951 << 15, true},
|
||||||
|
"XVMADDW": {0xE952 << 15, true}, "XVMADDV": {0xE953 << 15, true},
|
||||||
|
"XVMSUBB": {0xE954 << 15, true}, "XVMSUBH": {0xE955 << 15, true},
|
||||||
|
"XVMSUBW": {0xE956 << 15, true}, "XVMSUBV": {0xE957 << 15, true},
|
||||||
|
"XVMADDWEVHB": {0xE958 << 15, true}, "XVMADDWEVWH": {0xE959 << 15, true},
|
||||||
|
"XVMADDWEVVW": {0xE95A << 15, true}, "XVMADDWEVQV": {0xE95B << 15, true},
|
||||||
|
"XVMADDWODHB": {0xE95C << 15, true}, "XVMADDWODWH": {0xE95D << 15, true},
|
||||||
|
"XVMADDWODVW": {0xE95E << 15, true}, "XVMADDWODQV": {0xE95F << 15, true},
|
||||||
|
"XVMADDWEVHBU": {0xE968 << 15, true}, "XVMADDWEVWHU": {0xE969 << 15, true},
|
||||||
|
"XVMADDWEVVWU": {0xE96A << 15, true}, "XVMADDWEVQVU": {0xE96B << 15, true},
|
||||||
|
"XVMADDWODHBU": {0xE96C << 15, true}, "XVMADDWODWHU": {0xE96D << 15, true},
|
||||||
|
"XVMADDWODVWU": {0xE96E << 15, true}, "XVMADDWODQVU": {0xE96F << 15, true},
|
||||||
|
"XVMADDWEVHBUB": {0xE978 << 15, true}, "XVMADDWEVWHUH": {0xE979 << 15, true},
|
||||||
|
"XVMADDWEVVWUW": {0xE97A << 15, true}, "XVMADDWEVQVUV": {0xE97B << 15, true},
|
||||||
|
"XVMADDWODHBUB": {0xE97C << 15, true}, "XVMADDWODWHUH": {0xE97D << 15, true},
|
||||||
|
"XVMADDWODVWUW": {0xE97E << 15, true}, "XVMADDWODQVUV": {0xE97F << 15, true},
|
||||||
|
"XVDIVB": {0xE9C0 << 15, true}, "XVDIVH": {0xE9C1 << 15, true},
|
||||||
|
"XVDIVW": {0xE9C2 << 15, true}, "XVDIVV": {0xE9C3 << 15, true},
|
||||||
|
"XVMODB": {0xE9C4 << 15, true}, "XVMODH": {0xE9C5 << 15, true},
|
||||||
|
"XVMODW": {0xE9C6 << 15, true}, "XVMODV": {0xE9C7 << 15, true},
|
||||||
|
"XVDIVBU": {0xE9C8 << 15, true}, "XVDIVHU": {0xE9C9 << 15, true},
|
||||||
|
"XVDIVWU": {0xE9CA << 15, true}, "XVDIVVU": {0xE9CB << 15, true},
|
||||||
|
"XVMODBU": {0xE9CC << 15, true}, "XVMODHU": {0xE9CD << 15, true},
|
||||||
|
"XVMODWU": {0xE9CE << 15, true}, "XVMODVU": {0xE9CF << 15, true},
|
||||||
|
"XVMULF": {0xEA71 << 15, true}, "XVMULD": {0xEA72 << 15, true},
|
||||||
|
"XVDIVF": {0xEA75 << 15, true}, "XVDIVD": {0xEA76 << 15, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lane-wise shifts and rotates (three-register forms; the immediate
|
||||||
|
// forms live in l64VecImmInfo), the interleave families, the bit
|
||||||
|
// clear/set/rev register forms, the remaining logic and compare
|
||||||
|
// spellings, the widening add/subtract ladder and the vector FP
|
||||||
|
// arithmetic.
|
||||||
|
vecmisc := map[string]l64Vec3Enc{
|
||||||
|
"VSLLB": {0xE1D0 << 15, false}, "VSLLH": {0xE1D1 << 15, false},
|
||||||
|
"VSLLW": {0xE1D2 << 15, false}, "VSLLV": {0xE1D3 << 15, false},
|
||||||
|
"VSRLB": {0xE1D4 << 15, false}, "VSRLH": {0xE1D5 << 15, false},
|
||||||
|
"VSRLW": {0xE1D6 << 15, false}, "VSRLV": {0xE1D7 << 15, false},
|
||||||
|
"VSRAH": {0xE1D9 << 15, false}, "VSRAW": {0xE1DA << 15, false},
|
||||||
|
"VSRAV": {0xE1DB << 15, false},
|
||||||
|
"VROTRB": {0xE1DC << 15, false}, "VROTRH": {0xE1DD << 15, false},
|
||||||
|
"VROTRV": {0xE1DF << 15, false},
|
||||||
|
"VILVLB": {0xE234 << 15, false}, "VILVLH": {0xE235 << 15, false},
|
||||||
|
"VILVLW": {0xE236 << 15, false}, "VILVLV": {0xE237 << 15, false},
|
||||||
|
"VILVHB": {0xE238 << 15, false}, "VILVHH": {0xE239 << 15, false},
|
||||||
|
"VILVHW": {0xE23A << 15, false}, "VILVHV": {0xE23B << 15, false},
|
||||||
|
"VBITCLRB": {0xE218 << 15, false}, "VBITCLRH": {0xE219 << 15, false},
|
||||||
|
"VBITCLRW": {0xE21A << 15, false}, "VBITCLRV": {0xE21B << 15, false},
|
||||||
|
"VBITSETB": {0xE21C << 15, false}, "VBITSETH": {0xE21D << 15, false},
|
||||||
|
"VBITSETW": {0xE21E << 15, false}, "VBITSETV": {0xE21F << 15, false},
|
||||||
|
"VBITREVB": {0xE220 << 15, false}, "VBITREVH": {0xE221 << 15, false},
|
||||||
|
"VBITREVW": {0xE222 << 15, false}, "VBITREVV": {0xE223 << 15, false},
|
||||||
|
"VORV": {0xE24D << 15, false}, "VNORV": {0xE24F << 15, false},
|
||||||
|
"VANDNV": {0xE250 << 15, false}, "VORNV": {0xE251 << 15, false},
|
||||||
|
"VSEQH": {0xE001 << 15, false}, "VSEQW": {0xE002 << 15, false},
|
||||||
|
"VSLTB": {0xE00C << 15, false}, "VSLTH": {0xE00D << 15, false},
|
||||||
|
"VSLTW": {0xE00E << 15, false}, "VSLTV": {0xE00F << 15, false},
|
||||||
|
"VSLTBU": {0xE010 << 15, false}, "VSLTHU": {0xE011 << 15, false},
|
||||||
|
"VSLTWU": {0xE012 << 15, false}, "VSLTVU": {0xE013 << 15, false},
|
||||||
|
"VADDWEVHB": {0xE03C << 15, false}, "VADDWEVWH": {0xE03D << 15, false},
|
||||||
|
"VADDWEVVW": {0xE03E << 15, false}, "VADDWEVQV": {0xE03F << 15, false},
|
||||||
|
"VSUBWEVHB": {0xE040 << 15, false}, "VSUBWEVWH": {0xE041 << 15, false},
|
||||||
|
"VSUBWEVVW": {0xE042 << 15, false}, "VSUBWEVQV": {0xE043 << 15, false},
|
||||||
|
"VADDWODHB": {0xE044 << 15, false}, "VADDWODWH": {0xE045 << 15, false},
|
||||||
|
"VADDWODVW": {0xE046 << 15, false}, "VADDWODQV": {0xE047 << 15, false},
|
||||||
|
"VSUBWODHB": {0xE048 << 15, false}, "VSUBWODWH": {0xE049 << 15, false},
|
||||||
|
"VSUBWODVW": {0xE04A << 15, false}, "VSUBWODQV": {0xE04B << 15, false},
|
||||||
|
"VSUBWEVHBU": {0xE060 << 15, false}, "VSUBWEVWHU": {0xE061 << 15, false},
|
||||||
|
"VSUBWEVVWU": {0xE062 << 15, false}, "VSUBWEVQVU": {0xE063 << 15, false},
|
||||||
|
"VADDWEVHBU": {0xE05C << 15, false}, "VADDWEVWHU": {0xE05D << 15, false},
|
||||||
|
"VADDWEVVWU": {0xE05E << 15, false}, "VADDWEVQVU": {0xE05F << 15, false},
|
||||||
|
"VADDWODHBU": {0xE064 << 15, false}, "VADDWODWHU": {0xE065 << 15, false},
|
||||||
|
"VADDWODVWU": {0xE066 << 15, false}, "VADDWODQVU": {0xE067 << 15, false},
|
||||||
|
"VSUBWODHBU": {0xE068 << 15, false}, "VSUBWODWHU": {0xE069 << 15, false},
|
||||||
|
"VSUBWODVWU": {0xE06A << 15, false}, "VSUBWODQVU": {0xE06B << 15, false},
|
||||||
|
"VSHUFH": {0xE2F5 << 15, false}, "VSHUFW": {0xE2F6 << 15, false},
|
||||||
|
"VSHUFV": {0xE2F7 << 15, false},
|
||||||
|
"XVSLLB": {0xE9D0 << 15, true}, "XVSLLH": {0xE9D1 << 15, true},
|
||||||
|
"XVSLLW": {0xE9D2 << 15, true}, "XVSLLV": {0xE9D3 << 15, true},
|
||||||
|
"XVSRLB": {0xE9D4 << 15, true}, "XVSRLH": {0xE9D5 << 15, true},
|
||||||
|
"XVSRLW": {0xE9D6 << 15, true}, "XVSRLV": {0xE9D7 << 15, true},
|
||||||
|
"XVSRAB": {0xE9D8 << 15, true}, "XVSRAH": {0xE9D9 << 15, true},
|
||||||
|
"XVSRAW": {0xE9DA << 15, true}, "XVSRAV": {0xE9DB << 15, true},
|
||||||
|
"XVROTRB": {0xE9DC << 15, true}, "XVROTRH": {0xE9DD << 15, true},
|
||||||
|
"XVROTRW": {0xE9DE << 15, true}, "XVROTRV": {0xE9DF << 15, true},
|
||||||
|
"XVILVLB": {0xEA34 << 15, true}, "XVILVLH": {0xEA35 << 15, true},
|
||||||
|
"XVILVLW": {0xEA36 << 15, true}, "XVILVLV": {0xEA37 << 15, true},
|
||||||
|
"XVILVHB": {0xEA38 << 15, true}, "XVILVHH": {0xEA39 << 15, true},
|
||||||
|
"XVILVHW": {0xEA3A << 15, true}, "XVILVHV": {0xEA3B << 15, true},
|
||||||
|
"XVBITCLRB": {0xEA18 << 15, true}, "XVBITCLRH": {0xEA19 << 15, true},
|
||||||
|
"XVBITCLRW": {0xEA1A << 15, true}, "XVBITCLRV": {0xEA1B << 15, true},
|
||||||
|
"XVBITSETB": {0xEA1C << 15, true}, "XVBITSETH": {0xEA1D << 15, true},
|
||||||
|
"XVBITSETW": {0xEA1E << 15, true}, "XVBITSETV": {0xEA1F << 15, true},
|
||||||
|
"XVBITREVB": {0xEA20 << 15, true}, "XVBITREVH": {0xEA21 << 15, true},
|
||||||
|
"XVBITREVW": {0xEA22 << 15, true}, "XVBITREVV": {0xEA23 << 15, true},
|
||||||
|
"XVORV": {0xEA4D << 15, true}, "XVNORV": {0xEA4F << 15, true},
|
||||||
|
"XVANDNV": {0xEA50 << 15, true}, "XVORNV": {0xEA51 << 15, true},
|
||||||
|
"XVSEQH": {0xE801 << 15, true}, "XVSEQW": {0xE802 << 15, true},
|
||||||
|
"XVSLTB": {0xE80C << 15, true}, "XVSLTH": {0xE80D << 15, true},
|
||||||
|
"XVSLTW": {0xE80E << 15, true}, "XVSLTV": {0xE80F << 15, true},
|
||||||
|
"XVSLTBU": {0xE810 << 15, true}, "XVSLTHU": {0xE811 << 15, true},
|
||||||
|
"XVSLTWU": {0xE812 << 15, true}, "XVSLTVU": {0xE813 << 15, true},
|
||||||
|
"XVADDWEVHB": {0xE83C << 15, true}, "XVADDWEVWH": {0xE83D << 15, true},
|
||||||
|
"XVADDWEVVW": {0xE83E << 15, true}, "XVADDWEVQV": {0xE83F << 15, true},
|
||||||
|
"XVSUBWEVHB": {0xE840 << 15, true}, "XVSUBWEVWH": {0xE841 << 15, true},
|
||||||
|
"XVSUBWEVVW": {0xE842 << 15, true}, "XVSUBWEVQV": {0xE843 << 15, true},
|
||||||
|
"XVADDWODHB": {0xE844 << 15, true}, "XVADDWODWH": {0xE845 << 15, true},
|
||||||
|
"XVADDWODVW": {0xE846 << 15, true}, "XVADDWODQV": {0xE847 << 15, true},
|
||||||
|
"XVSUBWODHB": {0xE848 << 15, true}, "XVSUBWODWH": {0xE849 << 15, true},
|
||||||
|
"XVSUBWODVW": {0xE84A << 15, true}, "XVSUBWODQV": {0xE84B << 15, true},
|
||||||
|
"XVADDWEVHBU": {0xE85C << 15, true}, "XVADDWEVWHU": {0xE85D << 15, true},
|
||||||
|
"XVADDWEVVWU": {0xE85E << 15, true}, "XVADDWEVQVU": {0xE85F << 15, true},
|
||||||
|
"XVSUBWEVHBU": {0xE860 << 15, true}, "XVSUBWEVWHU": {0xE861 << 15, true},
|
||||||
|
"XVSUBWEVVWU": {0xE862 << 15, true}, "XVSUBWEVQVU": {0xE863 << 15, true},
|
||||||
|
"XVADDWODHBU": {0xE864 << 15, true}, "XVADDWODWHU": {0xE865 << 15, true},
|
||||||
|
"XVADDWODVWU": {0xE866 << 15, true}, "XVADDWODQVU": {0xE867 << 15, true},
|
||||||
|
"XVSUBWODHBU": {0xE868 << 15, true}, "XVSUBWODWHU": {0xE869 << 15, true},
|
||||||
|
"XVSUBWODVWU": {0xE86A << 15, true}, "XVSUBWODQVU": {0xE86B << 15, true},
|
||||||
|
"XVSHUFH": {0xEAF5 << 15, true}, "XVSHUFW": {0xEAF6 << 15, true},
|
||||||
|
"XVSHUFV": {0xEAF7 << 15, true},
|
||||||
|
}
|
||||||
|
for _, tab := range []map[string]l64Vec3Enc{addsub, muldiv, vecmisc} {
|
||||||
|
for m, e := range tab {
|
||||||
|
if _, dup := vec3[m]; dup {
|
||||||
|
panic("loong64: duplicate vector mnemonic " + m)
|
||||||
|
}
|
||||||
|
vec3[m] = e
|
||||||
|
}
|
||||||
|
}
|
||||||
for m, e := range vec3 {
|
for m, e := range vec3 {
|
||||||
l64InstrTable[m] = l64Enc{format: l64Fvvv, op: e.op}
|
l64InstrTable[m] = l64Enc{format: l64Fvvv, op: e.op}
|
||||||
l64VecBank[m] = e.lasx
|
l64VecBank[m] = e.lasx
|
||||||
@@ -597,21 +908,156 @@ func init() {
|
|||||||
|
|
||||||
// Immediate forms: INSTR $imm, vj, vd (or INSTR $imm, vd). The immediate
|
// Immediate forms: INSTR $imm, vj, vd (or INSTR $imm, vd). The immediate
|
||||||
// range, bias and field mask are the ones the toolchain encodes: vandi.b
|
// range, bias and field mask are the ones the toolchain encodes: vandi.b
|
||||||
// stores the raw 8-bit constant, vsrai.b stores imm+8 (byte-lane bias),
|
// stores the raw 8-bit constant, vsrari.b stores imm+8 (lane-width
|
||||||
// vseqi.b and vseqi.d store 5-bit and 7-bit two's-complement values.
|
// bias), the si5 compares store 5-bit two's-complement values and vseqi.d
|
||||||
// The mnemonics that also have a register form (VSEQB, VSEQV, VSRAB,
|
// a 7-bit field the toolchain range-checks down to si5.
|
||||||
// VROTRW) keep their three-register entry in l64InstrTable; the
|
// The mnemonics that also have a register form (the shifts, the bit
|
||||||
// dispatcher picks the immediate opcode from l64VecImmInfo by operand
|
// clear/set/rev families, VSEQ and the logic immediates) keep their
|
||||||
// kind, so the immediate entries must not overwrite the table.
|
// three-register entry in l64InstrTable; the dispatcher picks the
|
||||||
|
// immediate opcode from l64VecImmInfo by operand kind, so the immediate
|
||||||
|
// entries must not overwrite the table.
|
||||||
vecImm := map[string]l64VecImmEnc{
|
vecImm := map[string]l64VecImmEnc{
|
||||||
"VANDB": {0xE7A0 << 15, false, 0, 255, 0, 0xFF},
|
"VANDB": {0xE7A0 << 15, false, 0, 255, 0, 0xFF},
|
||||||
"XVANDB": {0xEFA0 << 15, true, 0, 255, 0, 0xFF},
|
"XVANDB": {0xEFA0 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VORB": {0xE7A8 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVORB": {0xEFA8 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VXORB": {0xE7B0 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVXORB": {0xEFB0 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VNORB": {0xE7B8 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVNORB": {0xEFB8 << 15, true, 0, 255, 0, 0xFF},
|
||||||
"VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F},
|
"VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F},
|
||||||
"XVSEQB": {0xE900 << 15, true, -16, 15, 0, 0x1F},
|
"XVSEQB": {0xED00 << 15, true, -16, 15, 0, 0x1F},
|
||||||
"VSEQV": {0xE503 << 15, false, -64, 63, 0, 0x7F},
|
// vseqi.h/w accept the same si5 window as vseqi.b; vseqi.d carries a
|
||||||
"XVSEQV": {0xE903 << 15, true, -64, 63, 0, 0x7F},
|
// 7-bit field, but the toolchain range-checks it down to si5 as well
|
||||||
|
// (GOARCH=loong64 go tool asm rejects VSEQV $32 and VSEQV $-64).
|
||||||
|
"VSEQH": {0xE501 << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSEQH": {0xED01 << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSEQW": {0xE502 << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSEQW": {0xED02 << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSEQV": {0xE503 << 15, false, -16, 15, 0, 0x7F},
|
||||||
|
"XVSEQV": {0xED03 << 15, true, -16, 15, 0, 0x7F},
|
||||||
|
// vslti compares against a signed (or, in the U spellings, unsigned)
|
||||||
|
// si5/ui5 constant.
|
||||||
|
"VSLTB": {0xE50C << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSLTB": {0xED0C << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSLTH": {0xE50D << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSLTH": {0xED0D << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSLTW": {0xE50E << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSLTW": {0xED0E << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSLTV": {0xE50F << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
"XVSLTV": {0xED0F << 15, true, -16, 15, 0, 0x1F},
|
||||||
|
"VSLTBU": {0xE510 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSLTBU": {0xED10 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSLTHU": {0xE511 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSLTHU": {0xED11 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSLTWU": {0xE512 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSLTWU": {0xED12 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSLTVU": {0xE513 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSLTVU": {0xED13 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
// vaddi/vsubi take ui5 constants for every width on this toolchain
|
||||||
|
// (VADDVU $32 is rejected by the oracle although the field is ui8).
|
||||||
|
"VADDBU": {0xE514 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVADDBU": {0xED14 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VADDHU": {0xE515 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVADDHU": {0xED15 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VADDWU": {0xE516 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVADDWU": {0xED16 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VADDVU": {0xE517 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVADDVU": {0xED17 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSUBBU": {0xE518 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSUBBU": {0xED18 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSUBHU": {0xE519 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSUBHU": {0xED19 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSUBWU": {0xE51A << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSUBWU": {0xED1A << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSUBVU": {0xE51B << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSUBVU": {0xED1B << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
// The shift/rotate immediates ride in a width-sized field whose upper
|
||||||
|
// bits carry the lane-width code: vslli.b stores ui3 at [12:0] with
|
||||||
|
// bits [14:13] inside the opcode, vslli.h ui4 under a 4 bit mask, and
|
||||||
|
// the .w/.d spellings a raw ui5/ui6.
|
||||||
|
"VSLLB": {0x732C2000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVSLLB": {0x772C2000, true, 0, 7, 0, 0x7},
|
||||||
|
"VSLLH": {0x732C4000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVSLLH": {0x772C4000, true, 0, 15, 0, 0xF},
|
||||||
|
"VSLLW": {0xE659 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSLLW": {0xEE59 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSLLV": {0xE65A << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVSLLV": {0xEE5A << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
"VSRLB": {0x73302000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVSRLB": {0x77302000, true, 0, 7, 0, 0x7},
|
||||||
|
"VSRLH": {0x73304000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVSRLH": {0x77304000, true, 0, 15, 0, 0xF},
|
||||||
|
"VSRLW": {0xE661 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSRLW": {0xEE61 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSRLV": {0xE662 << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVSRLV": {0xEE62 << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
// vsrari/vrotri bias the field so the lane-width code rides above the
|
||||||
|
// shift amount (.b adds 8, .h 16, .w 32; .d is a raw ui6).
|
||||||
"VSRAB": {0xE668 << 15, false, 0, 7, 8, 0x1F},
|
"VSRAB": {0xE668 << 15, false, 0, 7, 8, 0x1F},
|
||||||
|
"XVSRAB": {0xEE68 << 15, true, 0, 7, 8, 0x1F},
|
||||||
|
"VSRAH": {0x73344000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVSRAH": {0x77344000, true, 0, 15, 0, 0xF},
|
||||||
|
"VSRAW": {0xE669 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVSRAW": {0xEE69 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VSRAV": {0xE66A << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVSRAV": {0xEE6A << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
"VROTRB": {0x72A02000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVROTRB": {0x76A02000, true, 0, 7, 0, 0x7},
|
||||||
|
"VROTRH": {0x72A04000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVROTRH": {0x76A04000, true, 0, 15, 0, 0xF},
|
||||||
"VROTRW": {0xE541 << 15, false, 0, 31, 0, 0x1F},
|
"VROTRW": {0xE541 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVROTRW": {0xED41 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VROTRV": {0xE542 << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVROTRV": {0xED42 << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
// vbitclri/vbitseti/vbitrevi follow the same width-coded layout.
|
||||||
|
"VBITCLRB": {0x73102000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVBITCLRB": {0x77102000, true, 0, 7, 0, 0x7},
|
||||||
|
"VBITCLRH": {0x73104000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVBITCLRH": {0x77104000, true, 0, 15, 0, 0xF},
|
||||||
|
"VBITCLRW": {0xE621 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVBITCLRW": {0xEE21 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VBITCLRV": {0xE622 << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVBITCLRV": {0xEE22 << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
"VBITSETB": {0x73142000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVBITSETB": {0x77142000, true, 0, 7, 0, 0x7},
|
||||||
|
"VBITSETH": {0x73144000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVBITSETH": {0x77144000, true, 0, 15, 0, 0xF},
|
||||||
|
"VBITSETW": {0xE629 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVBITSETW": {0xEE29 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VBITSETV": {0xE62A << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVBITSETV": {0xEE2A << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
"VBITREVB": {0x73182000, false, 0, 7, 0, 0x7},
|
||||||
|
"XVBITREVB": {0x77182000, true, 0, 7, 0, 0x7},
|
||||||
|
"VBITREVH": {0x73184000, false, 0, 15, 0, 0xF},
|
||||||
|
"XVBITREVH": {0x77184000, true, 0, 15, 0, 0xF},
|
||||||
|
"VBITREVW": {0xE631 << 15, false, 0, 31, 0, 0x1F},
|
||||||
|
"XVBITREVW": {0xEE31 << 15, true, 0, 31, 0, 0x1F},
|
||||||
|
"VBITREVV": {0xE632 << 15, false, 0, 63, 0, 0x3F},
|
||||||
|
"XVBITREVV": {0xEE32 << 15, true, 0, 63, 0, 0x3F},
|
||||||
|
// The 4-bit-select shuffles and the byte-extract/insert permutations
|
||||||
|
// take ui8 (the .d shuffle ui4 range-checked to 0..15 by the
|
||||||
|
// toolchain) packing both position nibbles.
|
||||||
|
"VSHUF4IB": {0xE720 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVSHUF4IB": {0xEF20 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VSHUF4IH": {0xE728 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVSHUF4IH": {0xEF28 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VSHUF4IW": {0xE730 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVSHUF4IW": {0xEF30 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VSHUF4IV": {0xE738 << 15, false, 0, 15, 0, 0xFF},
|
||||||
|
"XVSHUF4IV": {0xEF38 << 15, true, 0, 15, 0, 0xFF},
|
||||||
|
"VPERMIW": {0xE7C8 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVPERMIW": {0xEFC8 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"XVPERMIV": {0xEFD0 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"XVPERMIQ": {0xEFD8 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VEXTRINSB": {0xE718 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVEXTRINSB": {0xEF18 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VEXTRINSH": {0xE710 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVEXTRINSH": {0xEF10 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VEXTRINSW": {0xE708 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVEXTRINSW": {0xEF08 << 15, true, 0, 255, 0, 0xFF},
|
||||||
|
"VEXTRINSV": {0xE700 << 15, false, 0, 255, 0, 0xFF},
|
||||||
|
"XVEXTRINSV": {0xEF00 << 15, true, 0, 255, 0, 0xFF},
|
||||||
}
|
}
|
||||||
for m, e := range vecImm {
|
for m, e := range vecImm {
|
||||||
l64VecImmInfo[m] = e
|
l64VecImmInfo[m] = e
|
||||||
@@ -625,22 +1071,71 @@ func init() {
|
|||||||
"VSETANYEQB": 0xE539<<15 | 8<<10, "XVSETANYEQB": 0xED39<<15 | 8<<10,
|
"VSETANYEQB": 0xE539<<15 | 8<<10, "XVSETANYEQB": 0xED39<<15 | 8<<10,
|
||||||
"VSETANYEQV": 0xE539<<15 | 11<<10, "XVSETANYEQV": 0xED39<<15 | 11<<10,
|
"VSETANYEQV": 0xE539<<15 | 11<<10, "XVSETANYEQV": 0xED39<<15 | 11<<10,
|
||||||
"VSETALLNEV": 0xE539<<15 | 15<<10, "XVSETALLNEV": 0xED39<<15 | 15<<10,
|
"VSETALLNEV": 0xE539<<15 | 15<<10, "XVSETALLNEV": 0xED39<<15 | 15<<10,
|
||||||
|
"VSETEQV": 0xE539<<15 | 6<<10, "XVSETEQV": 0xED39<<15 | 6<<10,
|
||||||
|
"VSETANYEQH": 0xE539<<15 | 9<<10, "XVSETANYEQH": 0xED39<<15 | 9<<10,
|
||||||
|
"VSETANYEQW": 0xE539<<15 | 10<<10, "XVSETANYEQW": 0xED39<<15 | 10<<10,
|
||||||
|
"VSETALLNEB": 0xE539<<15 | 12<<10, "XVSETALLNEB": 0xED39<<15 | 12<<10,
|
||||||
|
"VSETALLNEH": 0xE539<<15 | 13<<10, "XVSETALLNEH": 0xED39<<15 | 13<<10,
|
||||||
|
"VSETALLNEW": 0xE539<<15 | 14<<10, "XVSETALLNEW": 0xED39<<15 | 14<<10,
|
||||||
}
|
}
|
||||||
for m, op := range vecCf {
|
for m, op := range vecCf {
|
||||||
l64InstrTable[m] = l64Enc{format: l64Fvcf, op: op}
|
l64InstrTable[m] = l64Enc{format: l64Fvcf, op: op}
|
||||||
l64VecBank[m] = strings.HasPrefix(m, "XV")
|
l64VecBank[m] = strings.HasPrefix(m, "XV")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lane popcount: INSTR vj, vd (the 2R layout with the opcode extending
|
// Lane popcount and the two-operand vector FP/unary spellings: INSTR vj,
|
||||||
// over the unused vk field).
|
// vd (the 2R layout with the opcode extending over the unused vk field;
|
||||||
|
// the low byte of each constant is the instruction's own sub-op).
|
||||||
vec2r := map[string]l64Vec3Enc{
|
vec2r := map[string]l64Vec3Enc{
|
||||||
"VPCNTV": {0x1CA70B << 10, false}, "XVPCNTV": {0x1DA70B << 10, true},
|
"VPCNTV": {0x1CA70B << 10, false}, "XVPCNTV": {0x1DA70B << 10, true},
|
||||||
}
|
}
|
||||||
|
// The rest of the lane popcounts, the vector negations and the vector FP
|
||||||
|
// unary conversions (loong64enc1.s).
|
||||||
|
vec2rMore := map[string]l64Vec3Enc{
|
||||||
|
"VPCNTB": {0x1CA708 << 10, false}, "VPCNTH": {0x1CA709 << 10, false},
|
||||||
|
"VPCNTW": {0x1CA70A << 10, false},
|
||||||
|
"VNEGB": {0x1CA70C << 10, false}, "VNEGH": {0x1CA70D << 10, false},
|
||||||
|
"VNEGW": {0x1CA70E << 10, false}, "VNEGV": {0x1CA70F << 10, false},
|
||||||
|
"VFCLASSF": {0x1CA735 << 10, false}, "VFCLASSD": {0x1CA736 << 10, false},
|
||||||
|
"VFSQRTF": {0x1CA739 << 10, false}, "VFSQRTD": {0x1CA73A << 10, false},
|
||||||
|
"VFRECIPF": {0x1CA73D << 10, false}, "VFRECIPD": {0x1CA73E << 10, false},
|
||||||
|
"VFRSQRTF": {0x1CA741 << 10, false}, "VFRSQRTD": {0x1CA742 << 10, false},
|
||||||
|
"VFRINTF": {0x1CA74D << 10, false}, "VFRINTD": {0x1CA74E << 10, false},
|
||||||
|
"VFRINTRMF": {0x1CA751 << 10, false}, "VFRINTRMD": {0x1CA752 << 10, false},
|
||||||
|
"VFRINTRPF": {0x1CA755 << 10, false}, "VFRINTRPD": {0x1CA756 << 10, false},
|
||||||
|
"VFRINTRZF": {0x1CA759 << 10, false}, "VFRINTRZD": {0x1CA75A << 10, false},
|
||||||
|
"VFRINTRNEF": {0x1CA75D << 10, false}, "VFRINTRNED": {0x1CA75E << 10, false},
|
||||||
|
"XVPCNTB": {0x1DA708 << 10, true}, "XVPCNTH": {0x1DA709 << 10, true},
|
||||||
|
"XVPCNTW": {0x1DA70A << 10, true},
|
||||||
|
"XVNEGB": {0x1DA70C << 10, true}, "XVNEGH": {0x1DA70D << 10, true},
|
||||||
|
"XVNEGW": {0x1DA70E << 10, true}, "XVNEGV": {0x1DA70F << 10, true},
|
||||||
|
"XVFCLASSF": {0x1DA735 << 10, true}, "XVFCLASSD": {0x1DA736 << 10, true},
|
||||||
|
"XVFSQRTF": {0x1DA739 << 10, true}, "XVFSQRTD": {0x1DA73A << 10, true},
|
||||||
|
"XVFRECIPF": {0x1DA73D << 10, true}, "XVFRECIPD": {0x1DA73E << 10, true},
|
||||||
|
"XVFRSQRTF": {0x1DA741 << 10, true}, "XVFRSQRTD": {0x1DA742 << 10, true},
|
||||||
|
"XVFRINTF": {0x1DA74D << 10, true}, "XVFRINTD": {0x1DA74E << 10, true},
|
||||||
|
"XVFRINTRMF": {0x1DA751 << 10, true}, "XVFRINTRMD": {0x1DA752 << 10, true},
|
||||||
|
"XVFRINTRPF": {0x1DA755 << 10, true}, "XVFRINTRPD": {0x1DA756 << 10, true},
|
||||||
|
"XVFRINTRZF": {0x1DA759 << 10, true}, "XVFRINTRZD": {0x1DA75A << 10, true},
|
||||||
|
"XVFRINTRNEF": {0x1DA75D << 10, true}, "XVFRINTRNED": {0x1DA75E << 10, true},
|
||||||
|
}
|
||||||
|
maps.Copy(vec2r, vec2rMore)
|
||||||
for m, e := range vec2r {
|
for m, e := range vec2r {
|
||||||
l64InstrTable[m] = l64Enc{format: l64Frr, op: e.op}
|
l64InstrTable[m] = l64Enc{format: l64Frr, op: e.op}
|
||||||
l64VecBank[m] = e.lasx
|
l64VecBank[m] = e.lasx
|
||||||
l64Vec2R[m] = true
|
l64Vec2R[m] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The four-register byte shuffle: INSTR va, vk, vj, vd (the operand the
|
||||||
|
// table reads in each field position, va at bits [19:15]).
|
||||||
|
vec4r := map[string]l64Vec3Enc{
|
||||||
|
"VSHUFB": {0x0D50 << 16, false}, "XVSHUFB": {0x0D60 << 16, true},
|
||||||
|
}
|
||||||
|
for m, e := range vec4r {
|
||||||
|
l64InstrTable[m] = l64Enc{format: l64Fvvvv, op: e.op}
|
||||||
|
l64VecBank[m] = e.lasx
|
||||||
|
l64Vec4R[m] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// l64FpMovTable maps (mnemonic, from-class, to-class) to the 2R opcode of the
|
// l64FpMovTable maps (mnemonic, from-class, to-class) to the 2R opcode of the
|
||||||
|
|||||||
+558
-2
@@ -8,8 +8,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
||||||
@@ -507,6 +507,218 @@ TEXT ·v(SB), NOSPLIT, $0
|
|||||||
0x4C000020,
|
0x4C000020,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The integer and FP add/subtract families with their saturating pairs
|
||||||
|
// and immediate spellings (loong64enc1.s words).
|
||||||
|
t.Run("add and subtract families", func(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·v(SB), NOSPLIT, $0
|
||||||
|
VADDB V1, V2, V3
|
||||||
|
VADDF V1, V2, V3
|
||||||
|
VADDD V1, V2, V3
|
||||||
|
VSUBD V1, V2, V3
|
||||||
|
VSADDV V1, V2, V3
|
||||||
|
VSSUBVU V1, V2, V3
|
||||||
|
VADDBU $1, V2, V1
|
||||||
|
VADDBU $1, V2
|
||||||
|
VSUBVU $31, V2
|
||||||
|
XVSADDV X3, X2, X1
|
||||||
|
XVSUBD X1, X2, X3
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x700A0443, // vadd.b
|
||||||
|
0x71308443, // vadd.f
|
||||||
|
0x71310443, // vadd.d
|
||||||
|
0x71330443, // vsub.d
|
||||||
|
0x70478443, // vsadd.v
|
||||||
|
0x704D8443, // vssub.u.d
|
||||||
|
0x728A0441, // vaddi.bu v1, v2, 1
|
||||||
|
0x728A0442, // vaddi.bu v2, v2, 1 (two-operand form)
|
||||||
|
0x728DFC42, // vsubi.du v2, v2, 31 (two-operand form)
|
||||||
|
0x74478C41, // xvsadd.d x1, x2, x3
|
||||||
|
0x75330443, // xvsub.d x3, x2, x1
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// The multiply, divide and accumulate families.
|
||||||
|
t.Run("multiply and divide families", func(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·v(SB), NOSPLIT, $0
|
||||||
|
VMULV V1, V2, V3
|
||||||
|
VMUHHU V1, V2, V3
|
||||||
|
VDIVBU V1, V2, V3
|
||||||
|
VMODV V1, V2, V3
|
||||||
|
VMADDB V1, V2, V3
|
||||||
|
VMSUBV V1, V2, V3
|
||||||
|
VMULWEVHB V1, V2, V3
|
||||||
|
VMULWODQV V1, V2, V3
|
||||||
|
VMADDWEVHBUB V1, V2, V3
|
||||||
|
XVDIVD X1, X2, X3
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x70858443, // vmul.v
|
||||||
|
0x70888443, // vmuh.u.d
|
||||||
|
0x70E40443, // vdiv.u.b
|
||||||
|
0x70E38443, // vmod.d
|
||||||
|
0x70A80443, // vmadd.b
|
||||||
|
0x70AB8443, // vmsub.d
|
||||||
|
0x70900443, // vmulwev.h.b
|
||||||
|
0x70938443, // vmulwod.q.d
|
||||||
|
0x70BC0443, // vmaddwev.h.bu.b
|
||||||
|
0x753B0443, // xvdiv.d
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// The shift, bit and interleave families in register and immediate
|
||||||
|
// spellings, with the width-coded shift immediates.
|
||||||
|
t.Run("shift, bit and interleave families", func(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·v(SB), NOSPLIT, $0
|
||||||
|
VSLLV V1, V2, V3
|
||||||
|
VROTRB V1, V2, V3
|
||||||
|
VBITCLRV V1, V2, V3
|
||||||
|
VBITSETW V1, V2, V3
|
||||||
|
VBITREVV V1, V2, V3
|
||||||
|
VILVLB V1, V2, V3
|
||||||
|
VILVHV V1, V2, V3
|
||||||
|
VSLLB $7, V1, V2
|
||||||
|
VSLLB $5, V1
|
||||||
|
VSRLH $15, V1, V2
|
||||||
|
VSRAW $31, V1, V2
|
||||||
|
VSRAV $63, V1, V2
|
||||||
|
VROTRV $63, V1, V2
|
||||||
|
VBITCLRB $7, V2, V3
|
||||||
|
VBITREVV $63, V2, V3
|
||||||
|
VSEQH $-16, V2, V3
|
||||||
|
VSLTB $1, V2, V3
|
||||||
|
VSLTHU $31, V2, V3
|
||||||
|
XVILVLV X3, X2, X1
|
||||||
|
XVSLLB $7, X2, X1
|
||||||
|
XVSRAV $63, X2, X1
|
||||||
|
XVBITREVV $63, X2, X1
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x70E98443, // vsll.d
|
||||||
|
0x70EE0443, // vrotr.b
|
||||||
|
0x710D8443, // vbitclr.d
|
||||||
|
0x710F0443, // vbitset.w
|
||||||
|
0x71118443, // vbitrev.d
|
||||||
|
0x711A0443, // vilvl.b
|
||||||
|
0x711D8443, // vilvh.d
|
||||||
|
0x732C3C22, // vslli.b v2, v1, 7
|
||||||
|
0x732C3421, // vslli.b v1, v1, 5 (two-operand form)
|
||||||
|
0x73307C22, // vsrli.h v2, v1, 15
|
||||||
|
0x7334FC22, // vsrai.w v2, v1, 31
|
||||||
|
0x7335FC22, // vsrai.d v2, v1, 63
|
||||||
|
0x72A1FC22, // vrotri.d v2, v1, 63
|
||||||
|
0x73103C43, // vbitclri.b v3, v2, 7
|
||||||
|
0x7319FC43, // vbitrevi.d v3, v2, 63
|
||||||
|
0x7280C043, // vseqi.h v3, v2, -16
|
||||||
|
0x72860443, // vslti.b v3, v2, 1
|
||||||
|
0x7288FC43, // vslti.hu v3, v2, 31
|
||||||
|
0x751B8C41, // xvilvl.d x1, x2, x3
|
||||||
|
0x772C3C41, // xvslli.b x1, x2, 7
|
||||||
|
0x7735FC41, // xvsrai.d x1, x2, 63
|
||||||
|
0x7719FC41, // xvbitrevi.d x1, x2, 63
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// The shuffle, select and permutation families, including the
|
||||||
|
// four-register byte shuffle.
|
||||||
|
t.Run("shuffle and permutation families", func(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·v(SB), NOSPLIT, $0
|
||||||
|
VSHUFH V1, V2, V3
|
||||||
|
VSHUFW V1, V2, V3
|
||||||
|
VSHUFV V1, V2, V3
|
||||||
|
VSHUFB V1, V2, V3, V4
|
||||||
|
XVSHUFB X1, X2, X3, X4
|
||||||
|
VSHUF4IB $255, V2, V1
|
||||||
|
VSHUF4IV $15, V2, V1
|
||||||
|
XVSHUF4IV $15, X1, X2
|
||||||
|
VEXTRINSB $0x18, V1, V2
|
||||||
|
XVEXTRINSV $0x81, X1, X2
|
||||||
|
VPERMIW $0x1B, V1, V2
|
||||||
|
XVPERMIQ $0x4B, X1, X2
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x717A8443, // vshuf.h
|
||||||
|
0x717B0443, // vshuf.w
|
||||||
|
0x717B8443, // vshuf.d
|
||||||
|
0x0D508864, // vshuf.b v4, v3, v2, v1
|
||||||
|
0x0D608864, // xvshuf.b
|
||||||
|
0x7393FC41, // vshuf4i.b v1, v2, 255
|
||||||
|
0x739C3C41, // vshuf4i.d v1, v2, 15
|
||||||
|
0x779C3C22, // xvshuf4i.d x2, x1, 15
|
||||||
|
0x738C6022, // vextrins.b v2, v1, 0x18
|
||||||
|
0x77820422, // xvextrins.d x2, x1, 0x81
|
||||||
|
0x73E46C22, // vpermi.w v2, v1, 0x1b
|
||||||
|
0x77ED2C22, // xvpermi.q x2, x1, 0x4b
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// The vector FP families, the unary spellings, the compare-to-flag
|
||||||
|
// additions and the scalar int/float conversions.
|
||||||
|
t.Run("FP and conversion families", func(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·v(SB), NOSPLIT, $0
|
||||||
|
VADDF V1, V2, V3
|
||||||
|
VMULF V1, V2, V3
|
||||||
|
VFCLASSD V1, V2
|
||||||
|
VFSQRTF V1, V2
|
||||||
|
VFRECIPD V1, V2
|
||||||
|
VFRSQRTF V1, V2
|
||||||
|
VFRINTF V1, V2
|
||||||
|
VFRINTRNED V1, V2
|
||||||
|
VNEGB V1, V2
|
||||||
|
VPCNTB V1, V2
|
||||||
|
XVNEGV X2, X1
|
||||||
|
XVPCNTW X3, X2
|
||||||
|
XVFRINTRNEF X1, X2
|
||||||
|
VSETEQV V1, FCC0
|
||||||
|
VSETANYEQH V1, FCC0
|
||||||
|
VSETALLNEB V1, FCC0
|
||||||
|
XVSETALLNEW X1, FCC0
|
||||||
|
FFINTFW F0, F1
|
||||||
|
FTINTVD F0, F1
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x71308443, // vfadd.s
|
||||||
|
0x71388443, // vfmul.s
|
||||||
|
0x729CD822, // vfclass.d
|
||||||
|
0x729CE422, // vfsqrt.s
|
||||||
|
0x729CF822, // vfrecip.d
|
||||||
|
0x729D0422, // vfrsqrt.s
|
||||||
|
0x729D3422, // vfrint.s
|
||||||
|
0x729D7822, // vfrintne.s
|
||||||
|
0x729C3022, // vneg.b
|
||||||
|
0x729C2022, // vpcnt.b
|
||||||
|
0x769C3C41, // xvneg.d x1, x2
|
||||||
|
0x769C2862, // xvpcnt.w x2, x3
|
||||||
|
0x769D7422, // xvfrintne.s x2, x1
|
||||||
|
0x729C9820, // vseteqz.d fcc0, v1
|
||||||
|
0x729CA420, // vsetanyeqz.h
|
||||||
|
0x729CB020, // vsetallnez.b
|
||||||
|
0x769CB820, // xvsetallnez.w
|
||||||
|
0x011D1001, // ffint.s.w f1, f0
|
||||||
|
0x011B2801, // ftint.l.d f1, f0
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLOONG64_vectorErrors pins the register-class and range diagnostics of
|
// TestLOONG64_vectorErrors pins the register-class and range diagnostics of
|
||||||
@@ -545,6 +757,36 @@ func TestLOONG64_vectorErrors(t *testing.T) {
|
|||||||
`TEXT ·e(SB), NOSPLIT, $0
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
VROTRW $32, V1, V2
|
VROTRW $32, V1, V2
|
||||||
RET
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VADDVU $32, V2
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VSEQV $32, V2, V3
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VSHUF4IV $16, V2, V1
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VEXTRINSB $256, V1, V2
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VSLTV $-17, V2, V3
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
// VSHUFB wants four vector registers.
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VSHUFB V1, V2, V3
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
// The FCC forms still refuse vector registers.
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VSETEQV V1, V2
|
||||||
|
RET
|
||||||
`,
|
`,
|
||||||
// VSET* wants an FCC flag, not a vector register.
|
// VSET* wants an FCC flag, not a vector register.
|
||||||
`TEXT ·e(SB), NOSPLIT, $0
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
@@ -589,3 +831,317 @@ TEXT ·atoms(SB), NOSPLIT, $0
|
|||||||
0x4C000020,
|
0x4C000020,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_llacqScrel pins the acquire/release LL/SC pair. The oracle
|
||||||
|
// words come from GOARCH=loong64 go tool objdump and the toolchain's own
|
||||||
|
// loong64enc1.s golden bytes.
|
||||||
|
func TestLOONG64_llacqScrel(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·llsc(SB), NOSPLIT, $0
|
||||||
|
LLACQW (R5), R4
|
||||||
|
LLACQV (R5), R4
|
||||||
|
SCRELW R4, (R6)
|
||||||
|
SCRELV R4, (R6)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x385780A4, // ll.acq.w r4, r5
|
||||||
|
0x385788A4, // ll.acq.d r4, r5
|
||||||
|
0x385784C4, // sc.rel.w r4, r6
|
||||||
|
0x38578CC4, // sc.rel.d r4, r6
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The toolchain accepts the zero-offset memory form alone.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
LLACQW 4(R5), R4
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
SCRELV R4, 8(R6)
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_vmovqSuffixed pins the element-broadcast and element-move
|
||||||
|
// VMOVQ/XVMOVQ forms, with the oracle words lifted verbatim from the
|
||||||
|
// toolchain's loong64enc1.s.
|
||||||
|
func TestLOONG64_vmovqSuffixed(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·vmovq(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.B[3], V9.B16
|
||||||
|
VMOVQ V2.H[2], V8.H8
|
||||||
|
VMOVQ V3.W[1], V7.W4
|
||||||
|
VMOVQ V4.V[0], V6.V2
|
||||||
|
XVMOVQ X0, X31.B32
|
||||||
|
XVMOVQ X1, X30.H16
|
||||||
|
XVMOVQ X2, X29.W8
|
||||||
|
XVMOVQ X3, X28.V4
|
||||||
|
XVMOVQ X3, X27.Q2
|
||||||
|
XVMOVQ X0, X31.W[7]
|
||||||
|
XVMOVQ X1, X29.W[0]
|
||||||
|
XVMOVQ X3, X28.V[3]
|
||||||
|
XVMOVQ X4, X27.V[0]
|
||||||
|
XVMOVQ X31.W[7], X0
|
||||||
|
XVMOVQ X29.W[0], X1
|
||||||
|
XVMOVQ X28.V[3], X8
|
||||||
|
XVMOVQ X27.V[0], X9
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x72F78C29, // vreplvei.b v9, v1, 3
|
||||||
|
0x72F7C848, // vreplvei.h v8, v2, 2
|
||||||
|
0x72F7E467, // vreplvei.w v7, v3, 1
|
||||||
|
0x72F7F086, // vreplvei.d v6, v4, 0
|
||||||
|
0x7707001F, // xvreplve0.b x31, x0
|
||||||
|
0x7707803E, // xvreplve0.h x30, x1
|
||||||
|
0x7707C05D, // xvreplve0.w x29, x2
|
||||||
|
0x7707E07C, // xvreplve0.d x28, x3
|
||||||
|
0x7707F07B, // xvreplve0.q x27, x3
|
||||||
|
0x76FFDC1F, // xvinsve0.w x31, x0, 7
|
||||||
|
0x76FFC03D, // xvinsve0.w x29, x1, 0
|
||||||
|
0x76FFEC7C, // xvinsve0.d x28, x3, 3
|
||||||
|
0x76FFE09B, // xvinsve0.d x27, x4, 0
|
||||||
|
0x7703DFE0, // xvpickve.w x0, x31, 7
|
||||||
|
0x7703C3A1, // xvpickve.w x1, x29, 0
|
||||||
|
0x7703EF88, // xvpickve.d x8, x28, 3
|
||||||
|
0x7703E369, // xvpickve.d x9, x27, 0
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The rejected shapes: a width mismatch between the element and the
|
||||||
|
// arrangement, an element index past the lane count, a wrong-bank
|
||||||
|
// vreplvei and an arrangement the LASX bank does not spell.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.H[3], V9.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.B[16], V9.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X1.B[3], X9.B32
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X0, X31.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X0, X31.W[8]
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_parityFixes pins the operand forms whose encodings were found
|
||||||
|
// diverging from the toolchain by the loong64enc1.s differential: the
|
||||||
|
// $off(reg) address immediate (addi.d), the SCQ operand order, the scaled
|
||||||
|
// vldrepl offsets (with their field masks), the XVSEQB/XVSEQV immediate
|
||||||
|
// opcodes and the zero-register base the toolchain gives FP-relative
|
||||||
|
// VMOVQ/XVMOVQ memory operands. Golden words from loong64enc1.s.
|
||||||
|
func TestLOONG64_parityFixes(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·parity(SB), NOSPLIT, $0-32
|
||||||
|
MOVW $4(R4), R5
|
||||||
|
MOVV $4(R4), R5
|
||||||
|
MOVW $65536(R4), R5
|
||||||
|
MOVW $-4096(R4), R5
|
||||||
|
SCQ R4, R5, (R6)
|
||||||
|
VMOVQ 2(R4), V1.H8
|
||||||
|
VMOVQ -6(R4), V1.H8
|
||||||
|
VMOVQ -12(R4), V2.W4
|
||||||
|
VMOVQ -16(R4), V3.V2
|
||||||
|
XVMOVQ -10(R4), X1.H16
|
||||||
|
XVSEQB $0, X2, X4
|
||||||
|
XVSEQH $3, X2, X4
|
||||||
|
XVSEQW $12, X2, X4
|
||||||
|
XVSEQV $15, X2, X4
|
||||||
|
XVSEQV $-15, X2, X4
|
||||||
|
VMOVQ V2, y+16(FP)
|
||||||
|
VMOVQ y+16(FP), V2
|
||||||
|
VMOVQ V2, x+2030(FP)
|
||||||
|
XVMOVQ X6, y+16(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x02C01085, // addi.d $4, r4, r5
|
||||||
|
0x02C01085, // addi.d $4, r4, r5 (MOVW keeps the 64-bit addi.d)
|
||||||
|
0x1400021E, // lu12i.w $16, r30
|
||||||
|
0x038003DE, // ori $0, r30, r30
|
||||||
|
0x0010F885, // add.d r5, r4, r30
|
||||||
|
0x15FFFFFE, // lu12i.w $-1, r30
|
||||||
|
0x038003DE, // ori $0, r30, r30
|
||||||
|
0x0010F885, // add.d r5, r4, r30
|
||||||
|
0x385714C4, // sc.q r4, r5, (r6): middle<<10 | base<<5 | first
|
||||||
|
0x30400481, // vldrepl.h v1, 2(r4)
|
||||||
|
0x305FF481, // vldrepl.h v1, -6(r4)
|
||||||
|
0x302FF482, // vldrepl.w v2, -12(r4)
|
||||||
|
0x3017F883, // vldrepl.d v3, -16(r4)
|
||||||
|
0x325FEC81, // xvldrepl.h x1, -10(r4)
|
||||||
|
0x76800044, // xvseqi.b x4, x2, 0
|
||||||
|
0x76808C44, // xvseqi.h x4, x2, 3
|
||||||
|
0x76813044, // xvseqi.w x4, x2, 12
|
||||||
|
0x7681BC44, // xvseqi.d x4, x2, 15
|
||||||
|
0x7681C444, // xvseqi.d x4, x2, -15
|
||||||
|
0x2C406002, // vst v2, 24(r0): FP-relative keeps the zero base
|
||||||
|
0x2C006002, // vld v2, 24(r0)
|
||||||
|
0x2C5FD802, // vst v2, 2038(r0)
|
||||||
|
0x2CC06006, // xvst x6, 24(r0)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// Misaligned vldrepl offsets are rejected, as the toolchain does.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ 3(R4), V1.H8
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
MOVW $4(R4), F1
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_bytePseudo pins the BYTE literal-data pseudo-op, which the
|
||||||
|
// loong64 toolchain does not spell but the arm64 and riscv64 encoders of
|
||||||
|
// this package already accept for byte-exact data layout (a superset
|
||||||
|
// spelling, shippable via the goobj path).
|
||||||
|
func TestLOONG64_bytePseudo(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·bytes(SB), NOSPLIT, $0
|
||||||
|
BYTE $2
|
||||||
|
BYTE $1; BYTE $0
|
||||||
|
BYTE $255
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
// Four literal bytes, then RET (jirl r0, r1, 0); the trailing bytes pad
|
||||||
|
// the final word the way any sub-word tail does.
|
||||||
|
want := []byte{2, 1, 0, 0xFF, 0x20, 0x00, 0x00, 0x4C}
|
||||||
|
if !bytes.Equal(code[:len(want)], want) {
|
||||||
|
t.Errorf("bytes = % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
for _, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
BYTE $256
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
BYTE $-1
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%q: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64PairSugar pins the toolchain's register-pair spellings: a
|
||||||
|
// top-level colon between two registers splits the operand in two with the
|
||||||
|
// halves swapped, so INSTR R4:R5, R6 encodes exactly as INSTR R5, R4, R6.
|
||||||
|
// Each row is pinned two ways: against the word GOARCH=loong64 go tool asm
|
||||||
|
// emits for the pair spelling, and against gasm's own encoding of the
|
||||||
|
// direct spelling, which must be the same bytes.
|
||||||
|
func TestLOONG64PairSugar(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
pair, direct string
|
||||||
|
want uint32 // the word go tool asm emits for the pair
|
||||||
|
}{
|
||||||
|
{"MULV R4:R5, R6", "MULV R5, R4, R6", 0x001D9486},
|
||||||
|
{"MULV R4:R5", "MULV R5, R4", 0x001D9484},
|
||||||
|
{"MULV R6, R4:R5", "MULV R6, R5, R4", 0x001D98A4},
|
||||||
|
{"DIVV R4:R5, R6", "DIVV R5, R4, R6", 0x00221486},
|
||||||
|
{"MULHV R4:R5, R6", "MULHV R5, R4, R6", 0x001E1486},
|
||||||
|
{"ADDV R4:R5, R6", "ADDV R5, R4, R6", 0x00109486},
|
||||||
|
{"AND R4:R5, R6", "AND R5, R4, R6", 0x00149486},
|
||||||
|
{"MOVV R4:R5", "MOVV R5, R4", 0x001500A4},
|
||||||
|
{"MULD F4:F5, F6", "MULD F5, F4, F6", 0x01051486},
|
||||||
|
{"VADDW V1:V2, V3", "VADDW V2, V1, V3", 0x700B0823},
|
||||||
|
} {
|
||||||
|
t.Run(tt.pair, func(t *testing.T) {
|
||||||
|
pair := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·p(SB), NOSPLIT, $0
|
||||||
|
`+tt.pair+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
direct := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·d(SB), NOSPLIT, $0
|
||||||
|
`+tt.direct+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
if len(pair) < 4 {
|
||||||
|
t.Fatalf("pair spelling produced %d bytes", len(pair))
|
||||||
|
}
|
||||||
|
got := binary.LittleEndian.Uint32(pair)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("pair word = %08x, want %08x (go tool asm)", got, tt.want)
|
||||||
|
}
|
||||||
|
if len(direct) < 4 || !bytes.Equal(pair[:4], direct[:4]) {
|
||||||
|
t.Errorf("pair bytes % x differ from the direct spelling's % x", pair[:4], direct[:4])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_addv16 pins the ADDV16 immediate family, the toolchain's
|
||||||
|
// assembler name for the addu16i.d hardware instruction (loong64enc1.s
|
||||||
|
// spells it ADDV16; the spelling ADDU16I.D is the decoder's name for the
|
||||||
|
// encoding and no assembler input at all). The immediate must be a
|
||||||
|
// multiple of 65536 and rides the instruction field shifted right by 16;
|
||||||
|
// the oracle words are the toolchain's own loong64enc1.s rows, each also
|
||||||
|
// confirmed against GOARCH=loong64 go tool asm.
|
||||||
|
func TestLOONG64_addv16(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·a16(SB), NOSPLIT, $0
|
||||||
|
ADDV16 $(-32768<<16), R4, R5
|
||||||
|
ADDV16 $(0<<16), R4, R5
|
||||||
|
ADDV16 $(8<<16), R4, R5
|
||||||
|
ADDV16 $(32767<<16), R4, R5
|
||||||
|
ADDV16 $(16<<16), R4
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x12000085, // addu16i.d r5, r4, -32768
|
||||||
|
0x10000085, // addu16i.d r5, r4, 0
|
||||||
|
0x10002085, // addu16i.d r5, r4, 8
|
||||||
|
0x11FFFC85, // addu16i.d r5, r4, 32767
|
||||||
|
0x10004084, // addu16i.d r4, r4, 16 (destination-only form)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The non-multiples the loong64error.s catalogue pins (its rows 3 and
|
||||||
|
// 4): $1 and $65535 both refuse.
|
||||||
|
for _, src := range []string{
|
||||||
|
"ADDV16 $1, R4, R5",
|
||||||
|
"ADDV16 $65535, R4, R5",
|
||||||
|
"ADDV16 $(16<<16), F4",
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, "#include \"textflag.h\"\nTEXT ·e(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%q: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loong64AcceptedErrorShapes lists the toolchain's loong64error.s spellings
|
||||||
|
// gasm still accepts, each an acceptance superset with a known shape. The
|
||||||
|
// list only shrinks: every tightening of the encoder moves spellings out of
|
||||||
|
// it, and a spelling reappearing here means a regression.
|
||||||
|
var loong64AcceptedErrorShapes = []string{}
|
||||||
|
|
||||||
|
// TestLoong64ToolchainErrorParity walks the toolchain's loong64error.s (Go
|
||||||
|
// 1.27, loong64) and requires gasm to reject every case the toolchain rejects
|
||||||
|
// with an equivalent diagnostic, the documented acceptance supersets above
|
||||||
|
// excepted. A live Go toolchain is needed for the source file; the test
|
||||||
|
// skips without one or in -short.
|
||||||
|
func TestLoong64ToolchainErrorParity(t *testing.T) {
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
path := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata", "loong64error.s")
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
allowed := map[string]bool{}
|
||||||
|
for _, s := range loong64AcceptedErrorShapes {
|
||||||
|
allowed[s] = true
|
||||||
|
}
|
||||||
|
for raw := range strings.SplitSeq(string(data), "\n") {
|
||||||
|
line := strings.TrimSpace(raw)
|
||||||
|
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "TEXT") || !strings.Contains(line, "ERROR") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body := line
|
||||||
|
if i := strings.Index(body, "//"); i >= 0 {
|
||||||
|
body = strings.TrimSpace(body[:i])
|
||||||
|
}
|
||||||
|
want := ""
|
||||||
|
if m := regexp.MustCompile(`ERROR "([^"]*)"`).FindStringSubmatch(line); m != nil {
|
||||||
|
want = m[1]
|
||||||
|
}
|
||||||
|
body = strings.ReplaceAll(body, "\t", " ")
|
||||||
|
body = strings.Join(strings.Fields(body), " ")
|
||||||
|
// Label-relative and non-operand lines need their function context.
|
||||||
|
if strings.Contains(body, "(PC)") || strings.Contains(body, "(SB)") || strings.Contains(body, "PCALIGN") ||
|
||||||
|
strings.HasPrefix(body, "RET") || strings.HasPrefix(body, "NOP") || strings.HasPrefix(body, "BRK") ||
|
||||||
|
strings.Contains(body, "again") || strings.Contains(body, "next") || strings.Contains(body, "loop") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("errorparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr == nil {
|
||||||
|
if !allowed[body] {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", body)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if want != "" && !loong64DiagEquivalent(want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", body, want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagEquivalent answers whether gasm's rejection carries the same
|
||||||
|
// information as the toolchain's expected message. The two word the same
|
||||||
|
// facts differently: the toolchain writes "operand out of range 0 to 15"
|
||||||
|
// where gasm writes "immediate out of range [0, 15]", and both terminate
|
||||||
|
// sentences the toolchain punctuates. Canonicalisation drops the subject
|
||||||
|
// word and the connectives and compares the remaining token sequence.
|
||||||
|
func loong64DiagEquivalent(want, got string) bool {
|
||||||
|
return loong64DiagSubseq(loong64DiagTokens(want), loong64DiagTokens(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagTokens lower-cases a diagnostic, strips punctuation and the
|
||||||
|
// connective tokens, and returns its words. "to" survives only between
|
||||||
|
// digits, where the toolchain's range wording uses it; gasm's bracketed
|
||||||
|
// form never produces it.
|
||||||
|
func loong64DiagTokens(msg string) []string {
|
||||||
|
msg = strings.ToLower(msg)
|
||||||
|
for _, r := range []string{"[", "]", ",", ".", ":", "\n"} {
|
||||||
|
msg = strings.ReplaceAll(msg, r, " ")
|
||||||
|
}
|
||||||
|
fields := strings.Fields(msg)
|
||||||
|
out := make([]string, 0, len(fields))
|
||||||
|
for i, w := range fields {
|
||||||
|
switch w {
|
||||||
|
case "the", "a", "operand", "immediate":
|
||||||
|
// subjects and articles carry no constraint
|
||||||
|
case "to":
|
||||||
|
if i > 0 && i+1 < len(fields) && isDigits(fields[i-1]) && isDigits(fields[i+1]) {
|
||||||
|
continue // the range connective
|
||||||
|
}
|
||||||
|
out = append(out, w)
|
||||||
|
default:
|
||||||
|
out = append(out, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagSubseq answers whether want is a subsequence of got.
|
||||||
|
func loong64DiagSubseq(want, got []string) bool {
|
||||||
|
i := 0
|
||||||
|
for _, w := range got {
|
||||||
|
if i < len(want) && w == want[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i == len(want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigits(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(s) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ForeignErrorCorpus lists the corpus directories whose .s files the
|
||||||
|
// loong64 build attempts: the toolchain's own assembler testdata (the
|
||||||
|
// foreign-architecture files the corpus audit indicts on loong64) and the
|
||||||
|
// go/build malformed-source probes.
|
||||||
|
var loong64ForeignErrorCorpus = []string{
|
||||||
|
filepath.Join("cmd", "asm", "internal", "asm", "testdata"),
|
||||||
|
filepath.Join("go", "build", "testdata", "bads"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ForeignAcceptedLines lists toolchain rejections gasm still
|
||||||
|
// accepts, keyed by corpus-relative path and line, each with its reason.
|
||||||
|
// The list only shrinks: every tightening moves lines out of it, and a line
|
||||||
|
// reappearing here means a regression.
|
||||||
|
var loong64ForeignAcceptedLines = map[string]string{
|
||||||
|
// duperror.s rejections are whole-file semantics: the toolchain indicts
|
||||||
|
// the second declaration against the first in the same file, and a
|
||||||
|
// single TEXT or GLOBL line is legal on its own. gasm rejects the
|
||||||
|
// whole file with the same redeclaration diagnostic.
|
||||||
|
"cmd/asm/internal/asm/testdata/duperror.s:7": "symbol foo redeclared is a whole-file judgement",
|
||||||
|
"cmd/asm/internal/asm/testdata/duperror.s:11": "symbol bar redeclared is a whole-file judgement",
|
||||||
|
// The toolchain deletes the R22 spelling from the loong64 register map
|
||||||
|
// (its comment: avoid unintentionally clobbering g) and keeps g alone;
|
||||||
|
// gasm resolves R22 to register 22 as part of its deliberate ABI-alias
|
||||||
|
// superset (A0, SP, T0, ... share the table), so the three mips64.s
|
||||||
|
// spellings that name R22 assemble here.
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:135": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:138": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:218": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64ForeignErrorParity takes go tool asm's own per-line rejections
|
||||||
|
// on GOARCH=loong64 as the ground truth over the foreign corpus files and
|
||||||
|
// requires gasm to reject every one of those lines too, the documented
|
||||||
|
// acceptance supersets above excepted. The toolchain is run once per file;
|
||||||
|
// the test skips without a live toolchain or in -short.
|
||||||
|
func TestLoong64ForeignErrorParity(t *testing.T) {
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
files := loong64CorpusFiles(t, goroot)
|
||||||
|
if len(files) == 0 {
|
||||||
|
t.Skip("no corpus files")
|
||||||
|
}
|
||||||
|
for _, path := range files {
|
||||||
|
if filepath.Base(path) == "loong64error.s" {
|
||||||
|
continue // the native catalogue above walks it deeply
|
||||||
|
}
|
||||||
|
rel := loong64CorpusRel(goroot, path)
|
||||||
|
rejected := loong64ToolchainRejects(t, goroot, path)
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read %s: %v", path, err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, ln := range rejected {
|
||||||
|
if ln < 1 || ln > len(lines) {
|
||||||
|
t.Fatalf("%s: diagnostic names line %d beyond the file", rel, ln)
|
||||||
|
}
|
||||||
|
body := strings.TrimSpace(lines[ln-1])
|
||||||
|
if body == "" || strings.HasPrefix(body, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i := strings.Index(body, "//"); i >= 0 {
|
||||||
|
body = strings.TrimSpace(body[:i])
|
||||||
|
}
|
||||||
|
body = strings.Join(strings.Fields(strings.ReplaceAll(body, "\t", " ")), " ")
|
||||||
|
key := rel + ":" + strconv.Itoa(ln)
|
||||||
|
if _, ok := loong64ForeignAcceptedLines[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if loong64ProbeAssembles(body) {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects at %s: %s", key, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64RejectedNames pins the names the toolchain's loong64 table carries
|
||||||
|
// but refuses to encode: each is an "illegal combination" under every
|
||||||
|
// operand shape GOARCH=loong64 go tool asm accepts. gasm rejects them too,
|
||||||
|
// and the pair of rejections is the parity this catalogue asserts.
|
||||||
|
var loong64RejectedNames = []string{"RFE", "DUFFCOPY", "DUFFZERO", "PCALIGNMAX"}
|
||||||
|
|
||||||
|
// loong64PairErrorShapes walks the register-pair sugar's negative shapes,
|
||||||
|
// every one measured against GOARCH=loong64 go tool asm. want carries the
|
||||||
|
// toolchain's diagnostic where gasm's wording matches it (checked through
|
||||||
|
// the same equivalence as the catalogue above); an empty want pins the
|
||||||
|
// rejection alone, and the row's comment records the toolchain's own words
|
||||||
|
// for the divergence.
|
||||||
|
var loong64PairErrorShapes = []struct {
|
||||||
|
body string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// A pair never splits inside an address; the toolchain's parse stage
|
||||||
|
// refuses it outright.
|
||||||
|
{"MULV (R4:R5), R6", "indirect through register pair"},
|
||||||
|
// The reordered list fails the instruction's operand shape. The
|
||||||
|
// toolchain words these "illegal combination ..." at the class match;
|
||||||
|
// gasm words them at the operand count.
|
||||||
|
{"MOVV R4:R5, R6", ""},
|
||||||
|
{"BEQ R4:R5, R6", ""},
|
||||||
|
{"JMP R4:R5", ""},
|
||||||
|
{"MULV R4:R5, R7:R8, R6", ""},
|
||||||
|
{"MULV R4:R5, (R6)", ""},
|
||||||
|
{"BEQ R4:R5, 2(PC)", ""},
|
||||||
|
// A right half outside the register table; the toolchain's parse stage
|
||||||
|
// again ("illegal or missing addressing mode for symbol label").
|
||||||
|
{"MULV R4:label", "illegal or missing addressing mode for symbol label"},
|
||||||
|
// An immediate half on either side. The toolchain reorders the pair
|
||||||
|
// first and rejects at the class match ("illegal combination MULV
|
||||||
|
// U3CON ...", "MULV: expected register; found $4"); gasm rejects the
|
||||||
|
// shape without the reorder.
|
||||||
|
{"MOVV $4:R5", ""},
|
||||||
|
{"MULV R4:$5", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64PairErrorParity requires gasm to refuse every register-pair
|
||||||
|
// spelling the toolchain refuses, with an equivalent diagnostic where the
|
||||||
|
// row carries one. The toolchain's accept side of the sugar is pinned
|
||||||
|
// byte for byte by TestLOONG64PairSugar; the empty-want rows are documented
|
||||||
|
// wording divergences, not acceptance divergences.
|
||||||
|
func TestLoong64PairErrorParity(t *testing.T) {
|
||||||
|
for _, tt := range loong64PairErrorShapes {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + tt.body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("pairparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr == nil {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", tt.body)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.want != "" && !loong64DiagEquivalent(tt.want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", tt.body, tt.want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64BacklogNameParity walks the audit's known-but-unencodable
|
||||||
|
// backlog: the names go tool asm recognises on loong64 yet refuses to
|
||||||
|
// encode must be refused by gasm as well, and the one name the audit
|
||||||
|
// misses because its probe battery lacks the shape (SCQ, whose only form
|
||||||
|
// is the three-operand store-conditional) must encode to the toolchain's
|
||||||
|
// own bytes. The refusal claim is walked under the full operand-shape
|
||||||
|
// battery below, against the live toolchain and against gasm alike.
|
||||||
|
func TestLoong64BacklogNameParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live toolchain backlog: skipped in -short mode")
|
||||||
|
}
|
||||||
|
for _, name := range loong64RejectedNames {
|
||||||
|
if loong64ProbeAssembles(name) {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The shapes the plain audit probes, walked here so the backlog's
|
||||||
|
// claim holds under any operand shape, not only the bare mnemonic:
|
||||||
|
// the toolchain must refuse every combination, and gasm must refuse
|
||||||
|
// it too.
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
for _, name := range loong64RejectedNames {
|
||||||
|
for _, shape := range loong64BacklogShapes {
|
||||||
|
body := strings.TrimSpace(name + " " + shape)
|
||||||
|
if loong64ProbeAssembles(body) {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", body)
|
||||||
|
}
|
||||||
|
// The toolchain side of the same row: the file holds the one
|
||||||
|
// instruction, and the live assembler must report it. A
|
||||||
|
// spelling the toolchain accepts would open the name for
|
||||||
|
// encoding and close nothing here but the parity.
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\np2:\n\t" + body + "\n\tRET\n"
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "backlogshape.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatalf("write: %v", err)
|
||||||
|
}
|
||||||
|
if lines := loong64ToolchainRejects(t, goroot, path); len(lines) == 0 {
|
||||||
|
t.Errorf("the toolchain assembles %q, against the backlog's claim", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sc.q: loong64enc1.s pins the encoding as c4145738
|
||||||
|
// (SCQ R4, R5, (R6): sc.q rd, rk, (rj)).
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tSCQ R4, R5, (R6)\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("backlog.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
t.Fatalf("parse SCQ: %v", perr[0])
|
||||||
|
}
|
||||||
|
img, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr != nil {
|
||||||
|
t.Fatalf("assemble SCQ: %v", aerr)
|
||||||
|
}
|
||||||
|
want := []byte{0xc4, 0x14, 0x57, 0x38, 0x20, 0x00, 0x00, 0x4c}
|
||||||
|
if !slices.Equal(img.Code, want) {
|
||||||
|
t.Errorf("SCQ encodes to % X, want % X (RET included)", img.Code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64BacklogShapes is the operand-shape battery the backlog walk runs
|
||||||
|
// every rejected name through: the register, immediate, memory, vector and
|
||||||
|
// label positions the loong64 grammar has, mirroring the plain audit's
|
||||||
|
// probe battery (cmd/gasm's probeShapes) so the two answer the same
|
||||||
|
// question.
|
||||||
|
var loong64BacklogShapes = []string{
|
||||||
|
"", "R4", "R4, R5", "R4, R5, R6", "$1, R4", "R4, (R5)", "(R4), R5",
|
||||||
|
"F0, F1", "F0, F1, F2", "V1, V2", "X1, X2", "V1, FCC0", "$65536, R4",
|
||||||
|
"R4, R5, p2", "p2(SB)", "R5, (R4), R6", "$1, $0", "R1, R5, 0",
|
||||||
|
"0(R7), $5, $0", "V1, V2, V3, V4", "R4, R5, (R6)", "$16", "(R4)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ProbeAssembles answers whether the single corpus line, wrapped in
|
||||||
|
// its own function, parses and assembles on loong64.
|
||||||
|
func loong64ProbeAssembles(body string) bool {
|
||||||
|
src := "#include \"textflag.h\"\n\n"
|
||||||
|
if strings.HasPrefix(body, "TEXT ") {
|
||||||
|
src += body + "\n\tRET\n"
|
||||||
|
} else if strings.HasPrefix(body, "DATA ") || strings.HasPrefix(body, "GLOBL ") {
|
||||||
|
src += body + "\n\nTEXT ·probe(SB), NOSPLIT, $0-0\n\tRET\n"
|
||||||
|
} else {
|
||||||
|
src += "TEXT ·probe(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
}
|
||||||
|
f, perr := parser.Parse("foreignparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
return false // the parser already rejects the line
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
return aerr == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64Goroot resolves the live toolchain root, the environment's own
|
||||||
|
// value first, `go env GOROOT` as the fallback.
|
||||||
|
func loong64Goroot(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
if goroot := os.Getenv("GOROOT"); goroot != "" {
|
||||||
|
return goroot
|
||||||
|
}
|
||||||
|
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("no GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64CorpusFiles lists every .s file under the corpus roots, the
|
||||||
|
// testdata tree walked recursively.
|
||||||
|
func loong64CorpusFiles(t *testing.T, goroot string) []string {
|
||||||
|
t.Helper()
|
||||||
|
var files []string
|
||||||
|
for _, rel := range loong64ForeignErrorCorpus {
|
||||||
|
root := filepath.Join(goroot, "src", rel)
|
||||||
|
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() && strings.HasSuffix(path, ".s") {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("walk %s: %v", root, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(files)
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64CorpusRel renders a corpus file's path relative to GOROOT/src, the
|
||||||
|
// key the accepted-lines catalogue uses.
|
||||||
|
func loong64CorpusRel(goroot, path string) string {
|
||||||
|
rel, err := filepath.Rel(filepath.Join(goroot, "src"), path)
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// loong64ParseDiag matches the parse-stage "file:line: message".
|
||||||
|
loong64ParseDiag = regexp.MustCompile(`(?m)^(?:\S*asm: )?([^:\s]+\.s):(\d+):`)
|
||||||
|
// loong64EncodeDiag matches the encode-stage
|
||||||
|
// "asm: message: 003c (file.s:61)".
|
||||||
|
loong64EncodeDiag = regexp.MustCompile(`\(([^:\s()]+\.s):(\d+)\)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// loong64ToolchainRejects runs go tool asm on the file for GOARCH=loong64
|
||||||
|
// and returns the line numbers it rejects, both diagnostic shapes
|
||||||
|
// collected. An infrastructure failure fails the test: without the ground
|
||||||
|
// truth there is no parity to assert.
|
||||||
|
func loong64ToolchainRejects(t *testing.T, goroot, path string) []int {
|
||||||
|
t.Helper()
|
||||||
|
obj := filepath.Join(t.TempDir(), "probe.o")
|
||||||
|
cmd := exec.Command("go", "tool", "asm", "-I", filepath.Join(goroot, "pkg", "include"), "-o", obj, path)
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=loong64")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
var lines []int
|
||||||
|
seen := map[int]bool{}
|
||||||
|
for _, m := range loong64ParseDiag.FindAllStringSubmatch(string(out), -1) {
|
||||||
|
if strings.HasSuffix(m[1], ".s") {
|
||||||
|
ln, perr := strconv.Atoi(m[2])
|
||||||
|
if perr == nil && !seen[ln] {
|
||||||
|
seen[ln] = true
|
||||||
|
lines = append(lines, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range loong64EncodeDiag.FindAllStringSubmatch(string(out), -1) {
|
||||||
|
ln, perr := strconv.Atoi(m[2])
|
||||||
|
if perr == nil && !seen[ln] {
|
||||||
|
seen[ln] = true
|
||||||
|
lines = append(lines, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(lines)
|
||||||
|
if err != nil && len(lines) == 0 {
|
||||||
|
t.Fatalf("go tool asm %s: %v\n%s", filepath.Base(path), err, out)
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loong64 frame mapping, matching the Go toolchain's loong64 backend.
|
// Loong64 frame mapping, matching the Go toolchain's loong64 backend.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestLOONG64_sys exercises the no-operand system instructions and the
|
// TestLOONG64_sys exercises the no-operand system instructions and the
|
||||||
@@ -131,6 +131,98 @@ TEXT ·ptr(SB), NOSPLIT, $0
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_firr14Spans pins the 2RI14 offset spans of the LL/SC/MOVWP
|
||||||
|
// families against the toolchain's operand classes: an unaligned offset is
|
||||||
|
// rejected ("offset must be a multiple of 4"), a signed 16-bit offset rides
|
||||||
|
// in si14 alone, and anything wider materialises its high half in R30 with
|
||||||
|
// addu16i.d before the base folds in, with si14 keeping bits 15:2. The
|
||||||
|
// pinned words are GOARCH=loong64 go tool asm's own bytes for the same
|
||||||
|
// sources, boundaries included (-32766 and 32766 are the class edges).
|
||||||
|
func TestLOONG64_firr14Spans(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·spans(SB), NOSPLIT, $0
|
||||||
|
SC R4, 32764(R5)
|
||||||
|
SC R4, -32764(R5)
|
||||||
|
SC R4, 32768(R5)
|
||||||
|
SC R4, -32768(R5)
|
||||||
|
SC R4, -32772(R5)
|
||||||
|
LL 65540(R5), R4
|
||||||
|
LLV 65540(R5), R4
|
||||||
|
SCV R4, 65540(R5)
|
||||||
|
MOVWP R4, 65540(R5)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x217FFCA4, // sc.w r4, 8191(r5) — inside si14
|
||||||
|
0x218004A4, // sc.w r4, -8191(r5)
|
||||||
|
0x1000001E, // addu16i.d r30, r0, 0
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x218003C4, // sc.w r4, 8192(r30)
|
||||||
|
0x13FFFC1E, // addu16i.d r30, r0, -1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x218003C4, // sc.w r4, 8192(r30)
|
||||||
|
0x13FFFC1E, // addu16i.d r30, r0, -1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x217FFFC4, // sc.w r4, 8191(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x200007C4, // ll.w r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x220007C4, // ldptr.d r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x230007C4, // sc.d r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x250007C4, // stptr.w r4, 1(r30)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
|
||||||
|
// The expansion participates in layout: the function size carries the
|
||||||
|
// six three-word forms plus the single-word pair and the RET.
|
||||||
|
f, errs := parser.Parse("spans_loong64.s", `#include "textflag.h"
|
||||||
|
TEXT ·spans(SB), NOSPLIT, $0
|
||||||
|
SC R4, 32768(R5)
|
||||||
|
LL 65540(R5), R4
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileLOONG64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileLOONG64: %v", err)
|
||||||
|
}
|
||||||
|
if img.Funcs[0].Size != 8*3+4 {
|
||||||
|
t.Errorf("function size = %d, want %d", img.Funcs[0].Size, 8*3+4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_firr14Alignment checks the LL/SC/MOVWP alignment rule the
|
||||||
|
// toolchain enforces as "offset must be a multiple of 4": the LoongArch
|
||||||
|
// ll/sc family requires a naturally scaled offset, and si14 cannot express
|
||||||
|
// the remainder. GOARCH=loong64 go tool asm rejects every case here.
|
||||||
|
func TestLOONG64_firr14Alignment(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
"SC R4, 1(R5)",
|
||||||
|
"SCW R4, -1(R5)",
|
||||||
|
"SCV R4, 2(R5)",
|
||||||
|
"LL 1(R5), R4",
|
||||||
|
"LLW 3(R5), R4",
|
||||||
|
"LLV 6(R5), R4",
|
||||||
|
"MOVWP R4, 1(R5)",
|
||||||
|
"MOVVP R4, 2(R5)",
|
||||||
|
}
|
||||||
|
for _, src := range cases {
|
||||||
|
fn := firstTextLOONG64(t, "#include \"textflag.h\"\nTEXT ·e(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%s: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestLOONG64_atomics exercises the AM* read-modify-write forms and
|
// TestLOONG64_atomics exercises the AM* read-modify-write forms and
|
||||||
// RDTIME, plus the MOVV FP→GP move.
|
// RDTIME, plus the MOVV FP→GP move.
|
||||||
func TestLOONG64_atomics(t *testing.T) {
|
func TestLOONG64_atomics(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative
|
// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative
|
||||||
|
|||||||
+129
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package asm
|
package asm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// Operand is an instruction operand: a Reg, a Mem reference or an Imm value.
|
// Operand is an instruction operand: a Reg, a Mem reference or an Imm value.
|
||||||
type Operand interface {
|
type Operand interface {
|
||||||
isOperand()
|
isOperand()
|
||||||
@@ -14,6 +16,51 @@ type Imm int64
|
|||||||
|
|
||||||
func (Imm) isOperand() {}
|
func (Imm) isOperand() {}
|
||||||
|
|
||||||
|
// RegList is a bracketed register range, [Z0-Z3]: the four-register source
|
||||||
|
// of the 4FMAPS and 4VNNIW families. The EVEX emit path carries the list's
|
||||||
|
// low register through the inverted 5-bit V'VVVV field; the three higher
|
||||||
|
// registers are implied by the instruction, so only the pair travels here.
|
||||||
|
type RegList struct {
|
||||||
|
Lo Reg
|
||||||
|
Hi Reg // implied by the encoding; Lo.idx+3 by construction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RegList) isOperand() {}
|
||||||
|
|
||||||
|
// FloatImm is a floating-point immediate ($-1.0). The SSE mnemonics whose
|
||||||
|
// encoding takes an XMM/memory source at that position rewrite it as a read
|
||||||
|
// from a read-only pool constant ($f64.<hex> or $f32.<hex>), the toolchain's
|
||||||
|
// own behaviour; every other instruction rejects it.
|
||||||
|
type FloatImm struct {
|
||||||
|
Text string // the numeric text as written, sign excluded
|
||||||
|
Neg bool // a leading minus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FloatImm) isOperand() {}
|
||||||
|
|
||||||
|
// TLSMem is a thread-local access, the source form off(base)(TLS*1) with the
|
||||||
|
// base dropped: the toolchain's one-instruction TLS rewrite assembles it as
|
||||||
|
// the segment-prefixed absolute whose disp32 carries an R_TLS_LE patch site
|
||||||
|
// (the linker fills the TLS slot offset).
|
||||||
|
type TLSMem struct {
|
||||||
|
Disp int64
|
||||||
|
Size int
|
||||||
|
Seg byte // the segment override: FS (0x64) or GS (0x65) on windows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (TLSMem) isOperand() {}
|
||||||
|
|
||||||
|
// SegAbs is a segment-absolute access, 0x30(GS): the segment override
|
||||||
|
// prefixes a disp32 absolute reference with no relocation. The base
|
||||||
|
// register spellings GS and FS produce it.
|
||||||
|
type SegAbs struct {
|
||||||
|
Disp int64
|
||||||
|
Size int
|
||||||
|
Seg byte // 0x64 FS, 0x65 GS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SegAbs) isOperand() {}
|
||||||
|
|
||||||
// Mem is a memory operand of the form disp(base)(index*scale).
|
// Mem is a memory operand of the form disp(base)(index*scale).
|
||||||
type Mem struct {
|
type Mem struct {
|
||||||
Base Reg
|
Base Reg
|
||||||
@@ -23,6 +70,7 @@ type Mem struct {
|
|||||||
Size int // operand width in bytes
|
Size int // operand width in bytes
|
||||||
HasBase bool
|
HasBase bool
|
||||||
HasIndex bool
|
HasIndex bool
|
||||||
|
Seg byte // segment override prefix (0x64 FS, 0x65 GS); 0 = none
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Mem) isOperand() {}
|
func (Mem) isOperand() {}
|
||||||
@@ -61,3 +109,84 @@ func isX86Mem(o Operand) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// byteReg reports whether r is a general-purpose register whose name spells
|
||||||
|
// a byte width (AL, CL, DL, BL, AH-DH, SPL-DIL, R8B-R15B): the name itself
|
||||||
|
// fixes an 8-bit access, unlike the size-agnostic spellings (AX, EAX, RAX,
|
||||||
|
// R11) whose width the mnemonic supplies. The vector, opmask, x87, MMX,
|
||||||
|
// control and segment registers never name a byte access.
|
||||||
|
func byteReg(r Reg) bool {
|
||||||
|
return r.size == 1 && !r.isVec() && !r.mask && !r.fp && !r.mmx && r.ctl == 0 && r.seg == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// byteFormBase lists the scalar families that own an 8-bit encoding beside
|
||||||
|
// the 16/32/64-bit ones: the arithmetic and logic group, TEST, the moves,
|
||||||
|
// the unary group, the shifts, the exchanges and atomics, the one-operand
|
||||||
|
// IMUL and CRC32. Families without a byte form (LEA, BT, the bit scans,
|
||||||
|
// PUSH/POP, ADCX/ADOX, BSWAP) stay out, so their width comes from the
|
||||||
|
// mnemonic alone and a byte register in them is the handler's own error to
|
||||||
|
// make.
|
||||||
|
var byteFormBase = map[string]bool{
|
||||||
|
"MOV": true,
|
||||||
|
"ADD": true, "OR": true, "ADC": true, "SBB": true,
|
||||||
|
"AND": true, "SUB": true, "XOR": true, "CMP": true,
|
||||||
|
"TEST": true,
|
||||||
|
"INC": true, "DEC": true, "NEG": true, "NOT": true,
|
||||||
|
"MUL": true, "DIV": true, "IDIV": true,
|
||||||
|
"SHL": true, "SAL": true, "SHR": true, "SAR": true,
|
||||||
|
"ROL": true, "ROR": true, "RCL": true, "RCR": true,
|
||||||
|
"XCHG": true, "CMPXCHG": true, "XADD": true,
|
||||||
|
"CRC32": true,
|
||||||
|
"IMUL": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// operandWidth settles the operand width of a suffixed scalar instruction
|
||||||
|
// against the widths its register operands spell. A byte-spelled register
|
||||||
|
// operand (AL, DL, R8B, ...) forces the 8-bit form whatever the mnemonic's
|
||||||
|
// L suffix or lack of one says: that is the text the toolchain's own
|
||||||
|
// disassembly prints for the byte encodings (the rendered suffix rides the
|
||||||
|
// operand-size attribute, so an L or no suffix at all can name a byte
|
||||||
|
// form), and every register operand joins the form at its low byte, the way
|
||||||
|
// go tool asm reads the classic names there (TESTL R11, DL encodes TESTB
|
||||||
|
// R11B, DL). A W or Q suffix never rides a byte form, so that mix is a
|
||||||
|
// conflict rejected the way the toolchain rejects it (MOVQ AL, AX). With
|
||||||
|
// no byte operand the mnemonic decides alone and 0 returns, keeping the
|
||||||
|
// caller's width: the classic names are size-agnostic at every width.
|
||||||
|
func operandWidth(mnem string, suffix int, ops []Operand) (int, error) {
|
||||||
|
for _, o := range ops {
|
||||||
|
r, ok := o.(Reg)
|
||||||
|
if !ok || !byteReg(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if suffix == 2 || suffix == 8 {
|
||||||
|
kind := "quad"
|
||||||
|
if suffix == 2 {
|
||||||
|
kind = "word"
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("%s: byte register cannot take the %s form", mnem, kind)
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// widthOperands selects the operands that carry the instruction's data
|
||||||
|
// width for base: every operand of the scalar families but a shift's count,
|
||||||
|
// which names the CL register without narrowing the shifted value (RCLW CL,
|
||||||
|
// 0(R11) stays 16-bit), and nothing of IMUL's two- and three-operand forms,
|
||||||
|
// which have no byte encoding at all.
|
||||||
|
func widthOperands(base string, ops []Operand) []Operand {
|
||||||
|
switch base {
|
||||||
|
case "SHL", "SAL", "SHR", "SAR", "ROL", "ROR", "RCL", "RCR":
|
||||||
|
if len(ops) > 1 {
|
||||||
|
return ops[1:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "IMUL":
|
||||||
|
if len(ops) == 1 {
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ops
|
||||||
|
}
|
||||||
+30
-1
@@ -17,13 +17,28 @@ import "strings"
|
|||||||
// size. The high flag marks the legacy high-byte registers AH/CH/DH/BH, which
|
// size. The high flag marks the legacy high-byte registers AH/CH/DH/BH, which
|
||||||
// occupy indices 4-7 yet take no REX prefix, unlike SPL/BPL/SIL/DIL that share
|
// occupy indices 4-7 yet take no REX prefix, unlike SPL/BPL/SIL/DIL that share
|
||||||
// those indices but require one. The mask flag marks the AVX-512 opmask
|
// those indices but require one. The mask flag marks the AVX-512 opmask
|
||||||
// registers K0-K7, the fp flag the x87 stack registers F0-F7.
|
// registers K0-K7, the fp flag the x87 stack registers F0-F7, the mmx flag the
|
||||||
|
// MMX registers M0-M7, the seg field a bare segment register (FS, GS) and the
|
||||||
|
// ctl field the control and debug registers, whose number rides an
|
||||||
|
// instruction's reg field rather than r/m.
|
||||||
type Reg struct {
|
type Reg struct {
|
||||||
idx int
|
idx int
|
||||||
size int // informational width implied by the name; the mnemonic decides
|
size int // informational width implied by the name; the mnemonic decides
|
||||||
high bool // AH/CH/DH/BH
|
high bool // AH/CH/DH/BH
|
||||||
mask bool // K0-K7 opmask register
|
mask bool // K0-K7 opmask register
|
||||||
fp bool // F0-F7 x87 stack register
|
fp bool // F0-F7 x87 stack register
|
||||||
|
mmx bool // M0-M7 MMX register
|
||||||
|
seg int // segment register number plus one (ES=1..GS=6); 0 = not one
|
||||||
|
ctl byte // 0 none, 1 CRn control register, 2 DRn debug register
|
||||||
|
}
|
||||||
|
|
||||||
|
// segNumber returns the segment register number (ES=0..GS=5) when r names a
|
||||||
|
// bare segment register.
|
||||||
|
func (r Reg) segNumber() (int, bool) {
|
||||||
|
if r.seg == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return r.seg - 1, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns the register number (0-15 for GPRs, 0-31 for vectors).
|
// Index returns the register number (0-15 for GPRs, 0-31 for vectors).
|
||||||
@@ -149,6 +164,20 @@ func buildRegByName() map[string]Reg {
|
|||||||
for i := 0; i <= 7; i++ {
|
for i := 0; i <= 7; i++ {
|
||||||
m["F"+itoa(i)] = Reg{idx: i, size: 8, fp: true}
|
m["F"+itoa(i)] = Reg{idx: i, size: 8, fp: true}
|
||||||
}
|
}
|
||||||
|
// MMX: M0..M7.
|
||||||
|
for i := 0; i <= 7; i++ {
|
||||||
|
m["M"+itoa(i)] = Reg{idx: i, size: 8, mmx: true}
|
||||||
|
}
|
||||||
|
// Bare segment registers: ES, CS, SS, DS, FS, GS (the memory-base and
|
||||||
|
// index spellings of FS and GS are handled before register lookup).
|
||||||
|
for i, n := range []string{"ES", "CS", "SS", "DS", "FS", "GS"} {
|
||||||
|
m[n] = Reg{idx: i, size: 2, seg: i + 1}
|
||||||
|
}
|
||||||
|
// Control and debug registers: CR0..CR15, DR0..DR15.
|
||||||
|
for i := 0; i <= 15; i++ {
|
||||||
|
m["CR"+itoa(i)] = Reg{idx: i, size: 8, ctl: 1}
|
||||||
|
m["DR"+itoa(i)] = Reg{idx: i, size: 8, ctl: 2}
|
||||||
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRenderedTextEncodes pins the encoder against the disassembler's
|
||||||
|
// renderings: every line here is a text the toolchain's own decoder prints
|
||||||
|
// for bytes its assembler produced (the disasm package's parity fixtures),
|
||||||
|
// so assembling the printed text must reproduce those bytes. Each row was
|
||||||
|
// read back through the toolchain's decoder; the hex is the fixture's.
|
||||||
|
func TestRenderedTextEncodes(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
text string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// The segment-absolute rendering lowers to the FS-prefixed disp32
|
||||||
|
// absolute, exactly what the 0(FS) spelling encodes.
|
||||||
|
{"MOVQ FS:0, DX", "64488b142500000000"},
|
||||||
|
// The bank-crossing quadword move takes the prefix by direction: the
|
||||||
|
// MMX source carries F3, the XMM source F2.
|
||||||
|
{"MOVQ2DQ M2, X11", "f3440fd6da"},
|
||||||
|
} {
|
||||||
|
src := "TEXT \u00b7k(SB), NOSPLIT, $0\n\t" + tt.text + "\n\tRET\n"
|
||||||
|
f, errs := parser.Parse("k.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Errorf("%s: parse: %v", tt.text, errs[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: assemble: %v", tt.text, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||||
|
// The no-fallthrough rule appends the RET; the pinned instruction is
|
||||||
|
// the prefix.
|
||||||
|
got := hex.EncodeToString(code)
|
||||||
|
if !strings.HasPrefix(got, tt.want) {
|
||||||
|
t.Errorf("%s: bytes %s, want prefix %s", tt.text, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4038
-414
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRISCVBitManipDifferential proves the Zba, Zbb, Zbc and Zbs families
|
||||||
|
// against the toolchain: the toolchain's own testdata spellings (every
|
||||||
|
// operand form each section carries) assembled by gasm and by go tool asm
|
||||||
|
// must agree byte for byte. The lines are the oracle's own, so a wrong
|
||||||
|
// funct6, a swapped operand pair or a missed two-operand collapse names
|
||||||
|
// itself through the first differing word.
|
||||||
|
func TestRISCVBitManipDifferential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·bitmanip(SB), NOSPLIT, $0
|
||||||
|
ADDUW X10, X11, X12
|
||||||
|
ADDUW X10, X11
|
||||||
|
SH1ADD X11, X12, X13
|
||||||
|
SH1ADD X11, X12
|
||||||
|
SH1ADDUW X12, X13, X14
|
||||||
|
SH1ADDUW X12, X13
|
||||||
|
SH2ADD X13, X14, X15
|
||||||
|
SH2ADD X13, X14
|
||||||
|
SH2ADDUW X14, X15, X16
|
||||||
|
SH2ADDUW X14, X15
|
||||||
|
SH3ADD X15, X16, X17
|
||||||
|
SH3ADD X15, X16
|
||||||
|
SH3ADDUW X16, X17, X18
|
||||||
|
SH3ADDUW X16, X17
|
||||||
|
SLLIUW $31, X17, X18
|
||||||
|
SLLIUW $63, X17
|
||||||
|
SLLIUW $63, X17, X18
|
||||||
|
SLLIUW $1, X18, X19
|
||||||
|
ANDN X19, X20, X21
|
||||||
|
ANDN X19, X20
|
||||||
|
CLZ X20, X21
|
||||||
|
CLZW X21, X22
|
||||||
|
CPOP X22, X23
|
||||||
|
CPOPW X23, X24
|
||||||
|
CTZ X24, X25
|
||||||
|
CTZW X25, X26
|
||||||
|
MAX X26, X28, X29
|
||||||
|
MAX X26, X28
|
||||||
|
MAXU X28, X29, X30
|
||||||
|
MAXU X28, X29
|
||||||
|
MIN X29, X30, X5
|
||||||
|
MIN X29, X30
|
||||||
|
MINU X30, X5, X6
|
||||||
|
MINU X30, X5
|
||||||
|
ORN X6, X7, X8
|
||||||
|
ORN X6, X7
|
||||||
|
SEXTB X16, X17
|
||||||
|
SEXTH X17, X18
|
||||||
|
XNOR X18, X19, X20
|
||||||
|
XNOR X18, X19
|
||||||
|
ZEXTH X19, X20
|
||||||
|
ROL X8, X9, X10
|
||||||
|
ROL X8, X9
|
||||||
|
ROLW X9, X10, X11
|
||||||
|
ROLW X9, X10
|
||||||
|
ROR X10, X11, X12
|
||||||
|
ROR X10, X11
|
||||||
|
ROR $63, X11
|
||||||
|
RORI $63, X11, X12
|
||||||
|
RORI $1, X12, X13
|
||||||
|
RORIW $31, X13, X14
|
||||||
|
RORIW $1, X14, X15
|
||||||
|
RORW X15, X16, X17
|
||||||
|
RORW X15, X16
|
||||||
|
RORW $31, X13
|
||||||
|
ORCB X5, X6
|
||||||
|
REV8 X7, X8
|
||||||
|
CLMUL X5, X6, X7
|
||||||
|
CLMUL X5, X6
|
||||||
|
CLMULH X5, X6, X7
|
||||||
|
CLMULH X5, X6
|
||||||
|
CLMULR X5, X6, X7
|
||||||
|
CLMULR X5, X6
|
||||||
|
BCLR X23, X24, X25
|
||||||
|
BCLR $63, X24
|
||||||
|
BCLRI $1, X25, X26
|
||||||
|
BEXT X26, X28, X29
|
||||||
|
BEXT $63, X28
|
||||||
|
BEXTI $1, X29, X30
|
||||||
|
BINV X30, X5, X6
|
||||||
|
BINV $63, X6
|
||||||
|
BINVI $1, X7, X8
|
||||||
|
BSET X8, X9, X10
|
||||||
|
BSET $63, X9
|
||||||
|
BSETI $1, X10, X11
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "bitmanip_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "bitmanip")
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRISCVCompressedDifferential proves the explicit compressed-instruction
|
||||||
|
// mnemonics against the toolchain: the "C" extension block of the toolchain's
|
||||||
|
// own testdata (every stack, register, control-transfer, constant-generation,
|
||||||
|
// shift and register-register spelling it carries) assembled by gasm and by
|
||||||
|
// go tool asm must agree halfword for halfword. The lines are the oracle's
|
||||||
|
// own, so a wrong bit pattern, scale or register field names itself through
|
||||||
|
// the first differing halfword.
|
||||||
|
func TestRISCVCompressedDifferential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·compressed(SB), NOSPLIT, $0
|
||||||
|
CLWSP 20(SP), X10
|
||||||
|
CLDSP 24(SP), X10
|
||||||
|
CFLDSP 32(SP), F10
|
||||||
|
CSWSP X10, 20(SP)
|
||||||
|
CSDSP X10, 24(SP)
|
||||||
|
CFSDSP F10, 32(SP)
|
||||||
|
CLW 20(X10), X11
|
||||||
|
CLD 24(X10), X11
|
||||||
|
CFLD 32(X10), F11
|
||||||
|
CSW X11, 20(X10)
|
||||||
|
CSD X11, 24(X10)
|
||||||
|
CFSD F11, 32(X10)
|
||||||
|
CJ 1(PC)
|
||||||
|
CJR X5
|
||||||
|
CJALR X5
|
||||||
|
CBEQZ X10, 1(PC)
|
||||||
|
CBNEZ X10, 1(PC)
|
||||||
|
CLI $-32, X5
|
||||||
|
CLI $31, X5
|
||||||
|
CLUI $-32, X5
|
||||||
|
CLUI $31, X5
|
||||||
|
CADD $-32, X5
|
||||||
|
CADD $31, X5
|
||||||
|
CADDI $-32, X5
|
||||||
|
CADDI $31, X5
|
||||||
|
CADDW $-32, X5
|
||||||
|
CADDW $31, X5
|
||||||
|
CADDIW $-32, X5
|
||||||
|
CADDIW $31, X5
|
||||||
|
CADDI16SP $-512, SP
|
||||||
|
CADDI16SP $496, SP
|
||||||
|
CADDI4SPN $4, SP, X10
|
||||||
|
CADDI4SPN $1020, SP, X10
|
||||||
|
CSLLI $63, X5
|
||||||
|
CSRLI $63, X10
|
||||||
|
CSRAI $63, X10
|
||||||
|
CAND $-32, X10
|
||||||
|
CAND $31, X10
|
||||||
|
CANDI $-32, X10
|
||||||
|
CANDI $31, X10
|
||||||
|
CMV X6, X5
|
||||||
|
CADD X9, X8
|
||||||
|
CAND X9, X8
|
||||||
|
COR X9, X8
|
||||||
|
CXOR X9, X8
|
||||||
|
CSUB X9, X8
|
||||||
|
CADDW X9, X8
|
||||||
|
CSUBW X9, X8
|
||||||
|
CNOP
|
||||||
|
CEBREAK
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "compressed_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "compressed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCVCompressedRange pins the compressed immediate and offset ranges at
|
||||||
|
// the toolchain's own boundaries: a stack load off the scale or range, a
|
||||||
|
// non-prime register in the CL/CS and CA shapes, a zero immediate where the
|
||||||
|
// toolchain forbids one and a CLUI into SP are all rejected on sight.
|
||||||
|
func TestRISCVCompressedRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) error {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·c(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"CLWSP $0(SP), X10", // never spelled; the parser rejects the shape
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err == nil {
|
||||||
|
t.Errorf("%s must be rejected", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"CLWSP 21(SP), X10", // not a multiple of 4
|
||||||
|
"CLWSP 256(SP), X10", // out of range
|
||||||
|
"CLDSP 25(SP), X10", // not a multiple of 8
|
||||||
|
"CFLDSP 33(SP), F10", // not a multiple of 8
|
||||||
|
"CLWSP 20(X10), X10", // base must be SP
|
||||||
|
"CLW 22(X10), X11", // not a multiple of 4
|
||||||
|
"CLW 128(X10), X11", // out of range
|
||||||
|
"CLW 20(X5), X11", // base must be prime
|
||||||
|
"CLW 20(X10), X5", // rd must be prime
|
||||||
|
"CLI $32, X5", // out of range
|
||||||
|
"CLI $-33, X5", // out of range
|
||||||
|
"CLUI $0, X5", // zero
|
||||||
|
"CLUI $3, X2", // SP as destination
|
||||||
|
"CSLLI $0, X5", // zero shift
|
||||||
|
"CSLLI $64, X5", // out of range
|
||||||
|
"CSRLI $63, X5", // rd must be prime
|
||||||
|
"CANDI $63, X10", // out of range
|
||||||
|
"CMV X0, X5", // X0 in rd
|
||||||
|
"CMV X5, X0", // X0 in rs2
|
||||||
|
"CADD X5, X0", // X0 in rs2
|
||||||
|
"CSUB X5, X5", // X0-free but rd prime required
|
||||||
|
"CADDI4SPN $4, X5, X10", /* base must be SP */
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err == nil {
|
||||||
|
t.Errorf("%s must be rejected, as go tool asm rejects it", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"CLWSP 20(SP), X10", "CLDSP 24(SP), X10",
|
||||||
|
"CLW 20(X10), X11", "CSD X11, 24(X10)",
|
||||||
|
"CLI $-32, X5", "CLUI $-32, X5",
|
||||||
|
"CADD $-32, X5", "CADDIW $31, X5",
|
||||||
|
"CSLLI $63, X5", "CSRLI $63, X10", "CANDI $-32, X10",
|
||||||
|
"CMV X6, X5", "CADD X9, X8", "CSUB X9, X8", "CADDW X9, X8",
|
||||||
|
"CADDI16SP $496, SP", "CADDI4SPN $1020, SP, X10",
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err != nil {
|
||||||
|
t.Errorf("%s must assemble: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRISCVCSRDifferential proves the whole CSR name table against the
|
||||||
|
// toolchain at once: one TEXT whose body reads every register the table
|
||||||
|
// carries (CSRRW X0, NAME, X5), assembled by gasm and by go tool asm, must
|
||||||
|
// agree byte for byte. A single wrong address names its register through
|
||||||
|
// the first differing word: the CSR address occupies the instruction's top
|
||||||
|
// twelve bits, so 329 names spread over distinct words identify themselves.
|
||||||
|
func TestRISCVCSRDifferential(t *testing.T) {
|
||||||
|
names := make([]string, 0, len(riscvCSRNames))
|
||||||
|
for name := range riscvCSRNames {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
slices.Sort(names)
|
||||||
|
|
||||||
|
var body strings.Builder
|
||||||
|
for _, name := range names {
|
||||||
|
body.WriteString(fmt.Sprintf("\tCSRRW X0, %s, X5\n", name))
|
||||||
|
}
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·csrs(SB), NOSPLIT, $0\n" + body.String() + "\tRET\n"
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "csrs_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "csrs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCVCSRPseudosDifferential pins the CSR pseudo spellings against the
|
||||||
|
// oracle: the write-only forms (source first, CSR second), their immediate
|
||||||
|
// variants and the CSRR read pseudo, one word each.
|
||||||
|
func TestRISCVCSRPseudosDifferential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·csrps(SB), NOSPLIT, $0
|
||||||
|
CSRS X5, TIME
|
||||||
|
CSRC X5, CYCLE
|
||||||
|
CSRW X5, INSTRET
|
||||||
|
CSRSI $1, TIME
|
||||||
|
CSRCI $2, CYCLE
|
||||||
|
CSRWI $3, INSTRET
|
||||||
|
CSRR VL, X10
|
||||||
|
CSRRW X0, TIME, X11
|
||||||
|
CSRRS X0, CYCLE, X12
|
||||||
|
CSRRC X0, INSTRET, X13
|
||||||
|
CSRRWI $4, TIME, X14
|
||||||
|
CSRRSI $5, CYCLE, X15
|
||||||
|
CSRRCI $6, INSTRET, X16
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "csrps_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "csrps")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertRISCVDifferential assembles the same source with gasm and with the
|
||||||
|
// toolchain for riscv64 and requires the named function's code bytes to
|
||||||
|
// agree. The live oracle is a deliberate-run comparison, so -short skips it
|
||||||
|
// (the push pipeline's mode); the golden bytes of the individual encoders
|
||||||
|
// are pinned separately in every mode.
|
||||||
|
func assertRISCVDifferential(t *testing.T, path, src, fn string) {
|
||||||
|
t.Helper()
|
||||||
|
oracle := oracleFuncCode(t, toolAsmObject(t, path, "riscv64"))
|
||||||
|
|
||||||
|
// The oracle keys its functions by the qualified object name
|
||||||
|
// (pkg.name); match on the local part.
|
||||||
|
want := map[string][]byte{}
|
||||||
|
for name, code := range oracle {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
want[after] = code
|
||||||
|
} else {
|
||||||
|
want[name] = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if want[fn] == nil {
|
||||||
|
t.Fatalf("the oracle object carries no function %q (has %v)", fn, keysOf(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
f, perrs := parser.Parse(path, src)
|
||||||
|
if len(perrs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", perrs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileRISCV(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||||
|
}
|
||||||
|
got := trimTrailingZeroWords(img.Code)
|
||||||
|
wantB := trimTrailingZeroWords(want[fn])
|
||||||
|
if !slices.Equal(got, wantB) {
|
||||||
|
t.Errorf("%s: gasm and go tool asm disagree:\n gasm % x\n go % x", fn, got, wantB)
|
||||||
|
}
|
||||||
|
}
|
||||||
+761
-158
File diff suppressed because it is too large.
Load diff
+323
-25
@@ -7,11 +7,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
||||||
@@ -33,7 +35,7 @@ func firstTextRISCV(t *testing.T, src string) *ast.Text {
|
|||||||
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
code, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,34 @@ TEXT ·imm(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_utypeImmediate pins the U-type immediate semantics byte for byte
|
||||||
|
// against go tool asm (riscv64.s lines 76 to 85): the source immediate is the
|
||||||
|
// raw 20-bit field, not a byte address to divide, and both sign extremes
|
||||||
|
// encode. The range beyond the signed 20 bits is the toolchain's own error.
|
||||||
|
func TestRISCV_utypeImmediate(t *testing.T) {
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·utype(SB), NOSPLIT, $0
|
||||||
|
AUIPC $524287, X10
|
||||||
|
LUI $524287, X15
|
||||||
|
LUI $167, X15
|
||||||
|
AUIPC $-524288, X15
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleRISCVHelper(t, fn)
|
||||||
|
want := "17f5ff7f" + "b7f7ff7f" + "b7770a00" + "97070080"
|
||||||
|
if got := hex.EncodeToString(code[:16]); got != want {
|
||||||
|
t.Errorf("U-type immediates: %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·wide(SB), NOSPLIT, $0
|
||||||
|
AUIPC $524288, X10
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
|
t.Error("AUIPC $524288: expected the 20-bit range error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRISCV_branches(t *testing.T) {
|
func TestRISCV_branches(t *testing.T) {
|
||||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
TEXT ·br(SB), NOSPLIT, $0
|
TEXT ·br(SB), NOSPLIT, $0
|
||||||
@@ -785,16 +815,184 @@ TEXT ·sys(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRISCV_MOV_sym_FP_error(t *testing.T) {
|
// TestRISCV_privilegedWords pins the privileged ISA slice. The toolchain's
|
||||||
// MOV $sym(FP), rd should return an error (unsupported).
|
// object table carries the encodings (its assembler accepts no mnemonic for
|
||||||
|
// them), so the golden vectors come from the privileged and debug
|
||||||
|
// specifications: SFENCEVMA X10, X11 is 0x12b50073, SRET 0x10200073,
|
||||||
|
// MRET 0x30200073, WFI 0x10500073 and DRET 0x7b200073.
|
||||||
|
func TestRISCV_privilegedWords(t *testing.T) {
|
||||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
TEXT ·badfp(SB), NOSPLIT, $0
|
TEXT ·priv(SB), NOSPLIT, $0
|
||||||
|
SFENCEVMA X10, X11
|
||||||
|
SRET
|
||||||
|
MRET
|
||||||
|
WFI
|
||||||
|
DRET
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleRISCVHelper(t, fn)
|
||||||
|
riscvWants(t, code,
|
||||||
|
0x12B50073, // sfence.vma x10, x11
|
||||||
|
0x10200073, // sret
|
||||||
|
0x30200073, // mret
|
||||||
|
0x10500073, // wfi
|
||||||
|
0x7B200073, // dret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCV_privilegedAliases checks the supervisor spellings SCALL and
|
||||||
|
// SBREAK against the toolchain: both alias ECALL and EBREAK and must come
|
||||||
|
// out byte-identical.
|
||||||
|
func TestRISCV_privilegedAliases(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·alias(SB), NOSPLIT, $0
|
||||||
|
SCALL
|
||||||
|
SBREAK
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "alias_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "alias")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
||||||
|
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
||||||
|
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
||||||
|
// 8-byte link slot, compressed to C.ADDI4SPN.
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·argfp(SB), NOSPLIT, $0
|
||||||
MOV $arg(FP), X10
|
MOV $arg(FP), X10
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
_, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Error("expected error for MOV $arg(FP), got nil")
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
// prologue (0: leaf, zero frame) + C.ADDI4SPN (2) + RET (4) = 6
|
||||||
|
want := []byte{0x28, 0x00, 0x67, 0x80, 0x00, 0x00}
|
||||||
|
if string(code) != string(want) {
|
||||||
|
t.Errorf("got % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRISCV_Bookkeeping(t *testing.T) {
|
||||||
|
// FUNCDATA and PCDATA contribute no bytes; UNDEF is the toolchain's
|
||||||
|
// ebreak, compressed to C.EBREAK under RVC.
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·book(SB), NOSPLIT, $0-8
|
||||||
|
FUNCDATA $0, marks<>(SB)
|
||||||
|
PCDATA $1, $1
|
||||||
|
UNDEF
|
||||||
|
MOV $1, X10
|
||||||
|
MOV X10, ret+0(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
// C.EBREAK (2) + C.LI X10, 1 (2) + C.SWSP (2) + RET (4) = 10: the
|
||||||
|
// FUNCDATA and PCDATA statements contribute nothing.
|
||||||
|
want := []byte{0x02, 0x90, 0x05, 0x45, 0x2a, 0xe4, 0x67, 0x80, 0x00, 0x00}
|
||||||
|
if string(code) != string(want) {
|
||||||
|
t.Errorf("got % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRISCV_JMPPCRel(t *testing.T) {
|
||||||
|
// JMP N(PC): the displacement tracks the instruction N source slots
|
||||||
|
// away in the final layout (0 the jump itself, negative backwards).
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·slots(SB), NOSPLIT, $0-0
|
||||||
|
JMP 2(PC)
|
||||||
|
MOV $1, X11
|
||||||
|
MOV $2, X12
|
||||||
|
MOV X12, X11
|
||||||
|
JMP -3(PC)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
// JMP 2(PC) lands on the C.MV six bytes ahead; JMP -3(PC) lands back on
|
||||||
|
// the first C.LI, six bytes behind.
|
||||||
|
want := []byte{
|
||||||
|
0x6f, 0x00, 0x60, 0x00, // JAL X0, 6
|
||||||
|
0x85, 0x45, // C.LI X11, 1
|
||||||
|
0x09, 0x46, // C.LI X12, 2
|
||||||
|
0xb2, 0x85, // C.MV X11, X12
|
||||||
|
0x6f, 0xf0, 0xbf, 0xff, // JAL X0, -6
|
||||||
|
0x67, 0x80, 0x00, 0x00, // RET
|
||||||
|
}
|
||||||
|
if string(code) != string(want) {
|
||||||
|
t.Errorf("got % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRISCV_MOVWideImm(t *testing.T) {
|
||||||
|
// Shift-sequence constants compress like the toolchain's expansion.
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·wide(SB), NOSPLIT, $0-0
|
||||||
|
MOV $0x8000000000000000, X5
|
||||||
|
MOV $0x100000000, X5
|
||||||
|
MOV $0x000fffffffffffda, X5
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
// C.LI -1, C.SLLI 63; C.LI 1, C.SLLI 32; C.LI -19, C.SLLI 13, SRLI 12.
|
||||||
|
want := []byte{
|
||||||
|
0xfd, 0x52, 0xfe, 0x12,
|
||||||
|
0x85, 0x42, 0x82, 0x12,
|
||||||
|
0xb5, 0x52, 0xb6, 0x02, 0x93, 0xd2, 0xc2, 0x00,
|
||||||
|
0x67, 0x80, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
if string(code) != string(want) {
|
||||||
|
t.Errorf("got % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRISCV_MOVImmPool(t *testing.T) {
|
||||||
|
// A constant outside the shift shapes loads from the pooled $i64 data
|
||||||
|
// symbol via AUIPC+LD, named like the toolchain's pool.
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
TEXT ·pool(SB), NOSPLIT, $0-8
|
||||||
|
MOV $0x0101010101010101, X16
|
||||||
|
MOV X16, ret+0(FP)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("pool_riscv64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileRISCV(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||||
|
}
|
||||||
|
// AUIPC X16, 0 + LD X16, 0(X16): the relocation pair carries the symbol.
|
||||||
|
wantCode := []byte{0x17, 0x08, 0x00, 0x00, 0x03, 0x38, 0x08, 0x00}
|
||||||
|
if string(img.Code[0:8]) != string(wantCode) {
|
||||||
|
t.Errorf("pool load: got % x", img.Code[0:8])
|
||||||
|
}
|
||||||
|
var lit *DataSymbol
|
||||||
|
for i := range img.DataSyms {
|
||||||
|
if img.DataSyms[i].Name == "$i64.0101010101010101" {
|
||||||
|
lit = &img.DataSyms[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lit == nil {
|
||||||
|
t.Fatalf("pool symbol missing: %v", img.DataSyms)
|
||||||
|
}
|
||||||
|
wantData := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}
|
||||||
|
if string(img.Data[lit.Offset:lit.Offset+8]) != string(wantData) {
|
||||||
|
t.Errorf("pool bytes: got % x", img.Data[lit.Offset:lit.Offset+8])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,7 +1003,7 @@ TEXT ·calltest(SB), NOSPLIT, $0
|
|||||||
CALL ext(SB)
|
CALL ext(SB)
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, relocs, _, _, err := assembleRISCV(fn)
|
code, _, relocs, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -834,7 +1032,7 @@ TEXT ·calllocal(SB), NOSPLIT, $0
|
|||||||
sub:
|
sub:
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
_, _, _, _, _, err := assembleRISCV(fn)
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for CALL to local label, got nil")
|
t.Error("expected error for CALL to local label, got nil")
|
||||||
}
|
}
|
||||||
@@ -868,7 +1066,7 @@ func encodeOneInstrRISCV(t *testing.T, src string, pc int, offsets map[string]in
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
||||||
instr := fn.Body[0].(*ast.Instr)
|
instr := fn.Body[0].(*ast.Instr)
|
||||||
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil)
|
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
||||||
@@ -905,9 +1103,10 @@ func TestRISCVBranchJumpRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRISCVBranchFarBody drives the range check through the full two-pass
|
// TestRISCVBranchFarBody drives the relaxation pass through the full
|
||||||
// assembler: a forward branch over a body larger than the B-type span must
|
// assembler: a forward branch over a body larger than the B-type span is
|
||||||
// error rather than wrap.
|
// rewritten as an inverted branch over an inserted JMP, the same layout the
|
||||||
|
// toolchain produces, instead of wrapping to a wrong target.
|
||||||
func TestRISCVBranchFarBody(t *testing.T) {
|
func TestRISCVBranchFarBody(t *testing.T) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("#include \"textflag.h\"\nTEXT ·far(SB), NOSPLIT, $0\n\tBEQ X10, X11, done\n")
|
sb.WriteString("#include \"textflag.h\"\nTEXT ·far(SB), NOSPLIT, $0\n\tBEQ X10, X11, done\n")
|
||||||
@@ -916,8 +1115,20 @@ func TestRISCVBranchFarBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
sb.WriteString("done:\n\tRET\n")
|
sb.WriteString("done:\n\tRET\n")
|
||||||
fn := firstTextRISCV(t, sb.String())
|
fn := firstTextRISCV(t, sb.String())
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
out, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
t.Error("expected a branch-out-of-range error, got none")
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// The relaxed branch at offset 0 targets the inserted JMP at 4 (bne
|
||||||
|
// x10, x11, +4); the JMP at 4 carries the far forward displacement.
|
||||||
|
wantBranch := wordLE(riscvBType(riscvEnc{0x63, 0x1, 0x00}, 10, 11, 4))
|
||||||
|
if !bytes.Equal(out[0:4], wantBranch) {
|
||||||
|
t.Errorf("relaxed branch = %x, want %x", out[0:4], wantBranch)
|
||||||
|
}
|
||||||
|
// done sits after 1100 ADDs: 4 + 4400, i.e. offset 4404 from the JMP at 4.
|
||||||
|
wantJmp := wordLE(riscvJType(0, 4404))
|
||||||
|
if !bytes.Equal(out[4:8], wantJmp) {
|
||||||
|
t.Errorf("inserted JMP = %x, want %x", out[4:8], wantJmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +1141,7 @@ TEXT ·csrhi(SB), NOSPLIT, $0
|
|||||||
CSRRW $4096, X10, X11
|
CSRRW $4096, X10, X11
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
t.Error("expected an out-of-range error for CSR $4096, got none")
|
t.Error("expected an out-of-range error for CSR $4096, got none")
|
||||||
}
|
}
|
||||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
@@ -938,25 +1149,24 @@ TEXT ·csrmax(SB), NOSPLIT, $0
|
|||||||
CSRRW $4095, X10, X11
|
CSRRW $4095, X10, X11
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
t.Errorf("CSR $4095 must assemble: %v", err)
|
t.Errorf("CSR $4095 must assemble: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
||||||
// span are diagnosed instead of silently truncated to their low 32 bits (the
|
// span are diagnosed instead of silently truncated to their low 32 bits for
|
||||||
// toolchain materialises such constants via SLLI expansion, which this
|
// the I-type arithmetic; the MOV forms materialise the wide constant instead
|
||||||
// assembler does not implement).
|
// (shift sequence or pooled load), like the toolchain.
|
||||||
func TestRISCV_Imm64Rejected(t *testing.T) {
|
func TestRISCV_Imm64Rejected(t *testing.T) {
|
||||||
cases := []string{
|
cases := []string{
|
||||||
"MOV $0x123456789, X10",
|
|
||||||
"ADDI $0x100000000, X10, X11",
|
"ADDI $0x100000000, X10, X11",
|
||||||
"ANDI $-0x800000001, X10, X11",
|
"ANDI $-0x800000001, X10, X11",
|
||||||
"SUB $0x100000000, X10, X11",
|
"SUB $0x100000000, X10, X11",
|
||||||
}
|
}
|
||||||
for _, src := range cases {
|
for _, src := range cases {
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
t.Errorf("%s: expected an out-of-range error, got none", src)
|
t.Errorf("%s: expected an out-of-range error, got none", src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -969,9 +1179,19 @@ TEXT ·edge(SB), NOSPLIT, $0
|
|||||||
SUB $0x80000000, X12, X13
|
SUB $0x80000000, X12, X13
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
t.Errorf("int32-span immediates must assemble: %v", err)
|
t.Errorf("int32-span immediates must assemble: %v", err)
|
||||||
}
|
}
|
||||||
|
// Beyond the span the MOV forms materialise the constant like the
|
||||||
|
// toolchain instead of diagnosing it.
|
||||||
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·pool(SB), NOSPLIT, $0
|
||||||
|
MOV $0x123456789, X10
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
|
t.Errorf("MOV with a 64-bit immediate must assemble: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// riscvWants decodes code as little-endian words and pins each one; the
|
// riscvWants decodes code as little-endian words and pins each one; the
|
||||||
@@ -1194,3 +1414,81 @@ TEXT ·v(SB), NOSPLIT, $0
|
|||||||
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_rawDataRange pins the WORD and BYTE immediate ranges at the
|
||||||
|
// toolchain's own boundaries: WORD takes [0, 0xffffffff] and BYTE [0, 0xff],
|
||||||
|
// and a value the source spells wider must reach the check whole. The read
|
||||||
|
// once truncated through int32, which rejected WORD $0xffffffff as -1 while
|
||||||
|
// accepting WORD $0x100000000 as 0.
|
||||||
|
func TestRISCV_rawDataRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) ([]byte, error) {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·w(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
t.Run("word bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "WORD $0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\x00\x00\x00\x00" {
|
||||||
|
t.Errorf("WORD $0 = % x", code[:4])
|
||||||
|
}
|
||||||
|
code, err = asmOne(t, "WORD $0xffffffff")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0xffffffff must assemble, as go tool asm accepts it: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\xff\xff\xff\xff" {
|
||||||
|
t.Errorf("WORD $0xffffffff = % x", code[:4])
|
||||||
|
}
|
||||||
|
for _, w := range []string{"$-1", "$0x100000000", "$-4294967296"} {
|
||||||
|
if _, err := asmOne(t, "WORD "+w); err == nil {
|
||||||
|
t.Errorf("WORD %s must be rejected, as go tool asm rejects it", w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("byte bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "BYTE $255")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BYTE $255: %v", err)
|
||||||
|
}
|
||||||
|
if code[0] != 0xff {
|
||||||
|
t.Errorf("BYTE $255 = % x", code[:1])
|
||||||
|
}
|
||||||
|
for _, b := range []string{"$256", "$0x100000001", "$-1"} {
|
||||||
|
if _, err := asmOne(t, "BYTE "+b); err == nil {
|
||||||
|
t.Errorf("BYTE %s must be rejected: out of the byte range", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCV_shiftImmediateRange pins the shift immediate at the toolchain's
|
||||||
|
// validation boundary: 0-63 on the doubleword forms, 0-31 on the word forms,
|
||||||
|
// values beyond rejected on sight rather than masked into the field.
|
||||||
|
func TestRISCV_shiftImmediateRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) error {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·s(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"SLLI $63, X5, X6", "SLLI $0, X5, X6", "SRLI $63, X5", "SRAI $1, X5, X6",
|
||||||
|
"SLLIW $31, X5, X6", "SRLIW $31, X5", "SRAIW $1, X5, X6",
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err != nil {
|
||||||
|
t.Errorf("%s must assemble: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"SLLI $64, X5, X6", "SLLI $-1, X5", "SLLI $0x100000000, X5, X6",
|
||||||
|
"SRLI $64, X5", "SRAI $-1, X5, X6",
|
||||||
|
"SLLIW $32, X5, X6", "SRLIW $-1, X5", "SRAIW $32, X5, X6",
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err == nil {
|
||||||
|
t.Errorf("%s must be rejected, as go tool asm rejects it", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 353 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user