Compare commits
| 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 | ||
|
|
0629f5e2df | ||
|
|
ecb203dcf5 | ||
|
|
6c672567f3 | ||
|
|
cc6e416c59 | ||
|
|
c66a47973a | ||
|
|
9629897202 | ||
|
|
5399a8a724 | ||
|
|
de5d9f358e | ||
|
|
ca3fdce0e0 | ||
|
|
fc2d92eabd | ||
|
|
39d2e80145 |
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 ./...
|
||||
+67
-117
@@ -1,20 +1,28 @@
|
||||
# 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
|
||||
# module, measured on go1.27.1, so a build of a module in a subdirectory reports (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.
|
||||
# No gate runs at the tag: the tagged tree was tested on every push to development,
|
||||
# the full suite is the suite workflow's business, and race never runs in CI at all.
|
||||
# This pipeline publishes and nothing else. The version contract has no injection
|
||||
# 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 tag into the binary's build information, so the build simply has to happen at the
|
||||
# tag, which the trigger guarantees.
|
||||
# The module sits at the repository root: the toolchain records a version only for a
|
||||
# root module, measured on go1.27.1, so a build of a module in a subdirectory reports
|
||||
# (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
|
||||
# never runs on a push path or a tag, and the local gate raced this tree before the tag
|
||||
# was cut. Putting the gates inside the matrix would run the whole suite once per target
|
||||
# on the box that also hosts the forge. 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.
|
||||
# The matrix carries the platforms the project ships: Linux on amd64, arm64,
|
||||
# loong64 and riscv64, and FreeBSD on amd64 and arm64. Go cross-compiles
|
||||
# FreeBSD natively and the tree builds under CGO_ENABLED=0, which the
|
||||
# suite's cross-build gates prove for the whole module; the ptrace suite
|
||||
# itself still needs a FreeBSD machine, so nothing FreeBSD runs here.
|
||||
# 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
|
||||
|
||||
on:
|
||||
@@ -22,111 +30,16 @@ on:
|
||||
tags: ["v*"]
|
||||
|
||||
env:
|
||||
# The box is shared with the forge, so parallelism is bounded on purpose. The gates job
|
||||
# needs it most; the build jobs inherit it for their parallel compilation.
|
||||
# The box is shared with the forge, so parallelism is bounded on purpose.
|
||||
GOFLAGS: -p=1
|
||||
GOMAXPROCS: "2"
|
||||
|
||||
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:
|
||||
runs-on: fedora
|
||||
timeout-minutes: 25
|
||||
needs: gates
|
||||
strategy:
|
||||
fail-fast: false
|
||||
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:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
@@ -136,6 +49,10 @@ jobs:
|
||||
goarch: loong64
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
- goos: freebsd
|
||||
goarch: amd64
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
@@ -144,9 +61,6 @@ jobs:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Install Perl
|
||||
run: dnf install -y perl
|
||||
|
||||
- name: Validate the tag
|
||||
id: version
|
||||
env:
|
||||
@@ -209,7 +123,6 @@ jobs:
|
||||
|
||||
release:
|
||||
runs-on: fedora
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
permissions:
|
||||
# contents: read is required for the checkout: a job that declares any
|
||||
@@ -226,9 +139,6 @@ jobs:
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Install Perl
|
||||
run: dnf install -y perl
|
||||
|
||||
- name: Extract the CHANGELOG section
|
||||
env:
|
||||
VERSION: ${{ gitea.ref_name }}
|
||||
@@ -325,13 +235,27 @@ jobs:
|
||||
chomp $id;
|
||||
my @files = grep { -f $_ } glob(q{dist/*/*});
|
||||
@files or die qq{ERROR: no assets under dist/\n};
|
||||
# A file that arrived empty from the artifact step would be uploaded as an
|
||||
# empty attachment, every status would still be 201, and the run would go
|
||||
# green over a release nobody can install. Refuse it here, before the
|
||||
# upload, and verify what was stored afterwards.
|
||||
my %size;
|
||||
for my $path (@files) {
|
||||
my $n = -s $path // 0;
|
||||
(my $name = $path) =~ s{.*/}{};
|
||||
$n > 0 or die qq{ERROR: $path is empty, so there is nothing to upload\n};
|
||||
$size{$name} = $n;
|
||||
}
|
||||
my $bad = 0;
|
||||
for my $path (@files) {
|
||||
(my $name = $path) =~ s{.*/}{};
|
||||
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}, 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});
|
||||
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
||||
my $code = <$curl>;
|
||||
@@ -346,5 +270,31 @@ jobs:
|
||||
printf qq{%s: HTTP %s\n}, $name, $code;
|
||||
$bad = 1 if $code ne q{201};
|
||||
}
|
||||
# Read every asset back through the release download route and require the
|
||||
# served length to be the file that was sent: stored but empty is a broken
|
||||
# release however green the run looks.
|
||||
open(my $v, q{<}, q{version-no-v.txt}) or die qq{version-no-v.txt: $!};
|
||||
my $v = <$v>;
|
||||
close($v);
|
||||
chomp $v;
|
||||
for my $name (sort keys %size) {
|
||||
my $url = qq{$ENV{GITEA_SERVER_URL}/$ENV{GITEA_REPOSITORY}/releases/download/v$v/$name};
|
||||
my @head = (q{curl}, q{-sS}, q{-I}, q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}}, $url);
|
||||
open(my $h, q{-|}, @head) or die qq{curl: $!};
|
||||
my $len;
|
||||
my $status;
|
||||
while (my $l = <$h>) {
|
||||
$status = $1 if $l =~ m{^HTTP/\S+\s+(\d+)};
|
||||
$len = $1 if $l =~ m{^content-length:\s*(\d+)}i;
|
||||
}
|
||||
my $ok = close($h);
|
||||
$len = defined $len ? $len : 0;
|
||||
if (!$ok || $status != 200 || $len != $size{$name}) {
|
||||
printf qq{ERROR: %s serves %s bytes, expected %d\n}, $name, $len, $size{$name};
|
||||
$bad = 1;
|
||||
next;
|
||||
}
|
||||
printf qq{%s: serves %d bytes\n}, $name, $len;
|
||||
}
|
||||
exit($bad ? 1 : 0);
|
||||
'
|
||||
@@ -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.
|
||||
#
|
||||
# The gates are the ones the justfile's `gates` recipe runs, minus race: the shared
|
||||
# runner box cannot afford the race detector on every push, so it lives in race.yml.
|
||||
# The box is one core and 2 GB beside Gitea, so parallelism is bounded on purpose and
|
||||
# everything runs in one job. Extra jobs would duplicate the checkout, the Go setup and
|
||||
# the dependency download three times without buying any parallelism.
|
||||
# The push path owns a two-minute budget end to end, so it carries exactly the gates
|
||||
# that fit it: the format check, go vet, the suite's short layer and the coverage
|
||||
# floor. There is no build step (go test compiles what it runs) and the modernisation
|
||||
# gate (`go fix -diff`) belongs to the suite workflow, which is dispatched by hand.
|
||||
# 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
|
||||
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and
|
||||
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over
|
||||
# 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
|
||||
# assumed present.
|
||||
# The live ptrace sessions and the other deliberate-run categories skip under
|
||||
# testing.Short: they need the machine to themselves and belong to the suite workflow
|
||||
# and to the local `just test`, never to every push.
|
||||
#
|
||||
# Every step is one command, so the step that fails is the gate that failed, and no
|
||||
# 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
|
||||
|
||||
on:
|
||||
@@ -36,7 +39,6 @@ concurrency:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: fedora
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
@@ -46,16 +48,6 @@ jobs:
|
||||
go-version-file: go.mod
|
||||
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
|
||||
run: |
|
||||
perl -e '
|
||||
@@ -69,33 +61,19 @@ jobs:
|
||||
- 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 suite must be fast: a push pipeline that cannot finish in a few minutes moves
|
||||
# its heavy part behind a dispatch. The inner timeout matches the job's, so a
|
||||
# hanging test reports its own goroutine dump rather than a silent job kill.
|
||||
# The pattern is `packages` in the project's justfile: the logic packages, since a
|
||||
# thin cmd/ would drag the total under the floor. release.yml runs the same
|
||||
# command, so the floor is the same number everywhere. ./verify/... carries the
|
||||
# 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/...
|
||||
# The pattern is `packages` in the project's justfile, so the floor is the
|
||||
# same number the local gate reports: the logic packages, since a thin cmd/
|
||||
# and a ptrace-bound debug package would drag the total under it. No timeout
|
||||
# anywhere: `-timeout 0` disables go test's own ten-minute default, because a
|
||||
# run that does not finish is a defect to find and a timeout only hides it.
|
||||
run: go test -short -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 CLI and the debugger sit outside `packages` because a thin main and a
|
||||
# ptrace-bound package pull the total under the floor, but their tests guard
|
||||
# shipped surfaces: the command exit codes, the manual pages against the
|
||||
# binary's own help, and the debugger's architecture-neutral units. They run
|
||||
# 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/...
|
||||
# The CLI's exit codes and manual-page guard and the debugger's
|
||||
# architecture-neutral units, still tested but outside the profile the floor
|
||||
# is computed from. `-short` skips the debugger's live ptrace sessions.
|
||||
run: go test -short -count=1 -timeout 0 ./cmd/... ./debug/...
|
||||
|
||||
- name: Coverage floor
|
||||
run: |
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
coverage.out
|
||||
*.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
|
||||
core
|
||||
core.*
|
||||
|
||||
+395
-2
@@ -1,15 +1,403 @@
|
||||
# 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [development]
|
||||
|
||||
## [0.36.0] - 2026-10-07
|
||||
|
||||
### Added
|
||||
|
||||
-
|
||||
- **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,
|
||||
byte-verified against `go tool asm`: on amd64 the carry ALU, the
|
||||
atomics (CMPXCHG, XADD, XCHG), AES-NI, SHA-1/256, PCLMULQDQ, CRC32,
|
||||
GFNI, ADX, BMI, the string primitives, the system set (CPUID, RDTSC,
|
||||
SYSCALL, fences, MXCSR) and the SSE/AVX/EVEX gaps; on arm64 the pair
|
||||
loads and stores (LDP/STP), acquire/release and LSE atomics, AES and
|
||||
SHA, the system operations, the bit ops and the NEON slice including
|
||||
structure loads and the literal-pool moves; on riscv64 the RV64A AMO
|
||||
family with aq/rl ordering, the Zbb pseudos with their RVC
|
||||
compressions, the FMA forms and the RVV slice with `vsetvli`/
|
||||
`vsetivli`; on loong64 the AM atomics with acquire/release forms, the
|
||||
LSX/LASX slice, the `VMOVQ`/`XVMOVQ` transfer family and FSEL.
|
||||
Also fixed on the way: arm64 `CASD`/`CASW` lacked an opcode bit, and
|
||||
riscv64 `VSETVLI` with an immediate length now canonicalises to
|
||||
`vsetivli` as the toolchain does.
|
||||
- **Encoder coverage: quad-register AVX-512 and floating-point
|
||||
immediates.** The encoder gains the
|
||||
quad-register AVX-512 families (4FMAPS, 4FNMADD, 4VNNIW, VP4DPWSSD,
|
||||
VP4DPWSSDS) with the register list riding the inverted V'VVVV field,
|
||||
floating-point immediates on the SSE scalar moves and arithmetic
|
||||
(the constant lands in a synthesised read-only pool, a positive zero
|
||||
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
|
||||
|
||||
@@ -106,6 +494,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
+10
-9
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
## Contributor terms
|
||||
@@ -29,8 +29,8 @@ compiler (gcc), because `just gates` includes `just race` and the race
|
||||
detector needs cgo.
|
||||
|
||||
```sh
|
||||
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
||||
cd gasm-devkit
|
||||
git clone https://sourcedock.dev/petrbalvin/gasm-sdk.git
|
||||
cd gasm-sdk
|
||||
just build
|
||||
just gates
|
||||
```
|
||||
@@ -117,16 +117,17 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
|
||||
|
||||
| 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` |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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
|
||||
race detector also has its own workflow, dispatched by hand; it never runs on a push or a
|
||||
tag, where it would double the time and the memory a shared runner cannot spare.
|
||||
The local equivalent is `just gates`, which is the same set plus the race detector. Race
|
||||
never runs in CI, on a push or a tag: it would double the time and the memory a shared
|
||||
runner cannot spare, so the local `just gates` runs it before the tag is cut.
|
||||
|
||||
## 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,
|
||||
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,
|
||||
> 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
|
||||
@@ -13,7 +13,7 @@
|
||||
there is no formatter, no linter and no debugger for `.s` files, and no
|
||||
assembler that works without a Go installation. Developers write
|
||||
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.
|
||||
|
||||
- **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 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.
|
||||
|
||||
## Features
|
||||
@@ -81,7 +81,10 @@ to give that syntax the tooling it deserves.
|
||||
GOOBJ format, which needs the installed toolchain and which `go build`
|
||||
consumes in place of the toolchain's output. Framed functions get 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
|
||||
offsets after assembling, or disassembles raw bytes from a file or stdin.
|
||||
- **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
|
||||
breakpoints (optionally conditional), hardware watchpoints, register 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,
|
||||
push and pull diagnostics, semantic-token highlighting, go-to-definition,
|
||||
find references, rename, formatting, inlay hints, code actions, signature
|
||||
help, document highlights, workspace symbol search, #include document
|
||||
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
|
||||
assembly files byte-for-byte, `gasm profile` shows basic-block structure,
|
||||
`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 |
|
||||
|--------------|-------------|--------------|---------------------------------------------|
|
||||
| AMD64 | `amd64` | `_amd64.s` | 1600 + common opcodes + traditional aliases |
|
||||
| ARM64 | `arm64` | `_arm64.s` | 538 + common opcodes |
|
||||
| RISC-V | `riscv64` | `_riscv64.s` | 961 + common opcodes |
|
||||
| LoongArch | `loong64` | `_loong64.s` | 799 + common opcodes |
|
||||
| ARM64 | `arm64` | `_arm64.s` | 645 + common opcodes |
|
||||
| RISC-V | `riscv64` | `_riscv64.s` | 992 + common opcodes |
|
||||
| LoongArch | `loong64` | `_loong64.s` | 808 + common opcodes |
|
||||
|
||||
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
||||
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, ...). AMD64 additionally
|
||||
@@ -124,9 +130,13 @@ can emit today is narrower, and a recognised but unencodable instruction is
|
||||
reported as an explicit error, never as a wrong byte.
|
||||
|
||||
The same measurement runs over GOROOT's whole assembly corpus:
|
||||
`gasm audit-instructions --corpus` reports 127 of 627 files (20.3 %)
|
||||
assembling for every target architecture today, with the top failure
|
||||
reasons per architecture; the number moves with every release.
|
||||
`gasm audit-instructions --corpus` reports every real-code GOROOT assembly
|
||||
file (the tree without testdata) assembling for every target its build
|
||||
admits: 250 of 250, 100 %. Over the whole tree including testdata the
|
||||
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
|
||||
|
||||
@@ -141,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) |
|
||||
| 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
|
||||
CPU: instruction semantics are implemented in software and can differ
|
||||
@@ -152,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
|
||||
between the claim and the evidence. The debugger is the weakest case: on
|
||||
the three emulated architectures its per-architecture ptrace code has
|
||||
been compiled and read, never executed. Its architecture-neutral units
|
||||
run under `go test ./...`, which the race workflow and a manual run
|
||||
perform; the default `just test` gate does not sweep `./debug/...`.
|
||||
been compiled and read, never executed. Its units run under
|
||||
`go test ./...`, which `just test` sweeps in a second invocation beside
|
||||
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
|
||||
|
||||
@@ -178,21 +222,23 @@ The plan, in the order it is being worked:
|
||||
toolchain itself does not support; through ELF, Plan 9 assembly becomes
|
||||
usable outside Go entirely.
|
||||
- **Platforms: Linux and FreeBSD.** Linux is supported today on all four
|
||||
architectures and is where the binary builds. FreeBSD follows: the
|
||||
JIT's executable-memory mapping and the ptrace debugger layer are the
|
||||
two pieces of porting work. Other unix systems may follow those two.
|
||||
architectures and is where the binary builds. FreeBSD follows on amd64
|
||||
and arm64: the JIT's executable-memory mapping and the ptrace debugger
|
||||
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.
|
||||
No others are planned.
|
||||
|
||||
## Install
|
||||
|
||||
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and
|
||||
linux/loong64 are on the
|
||||
[releases page](https://sourcedock.dev/petrbalvin/gasm-devkit/releases).
|
||||
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64,
|
||||
linux/loong64, freebsd/amd64 and freebsd/arm64 are on the
|
||||
[releases page](https://sourcedock.dev/petrbalvin/gasm-sdk/releases).
|
||||
From source (Go 1.27.1):
|
||||
|
||||
```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:
|
||||
@@ -281,6 +327,8 @@ recipe.
|
||||
~/.local/share/man (MANDIR overrides); `just uninstall-man` removes
|
||||
them
|
||||
- [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
|
||||
- [CHANGELOG.md](CHANGELOG.md): release history
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ releases do not receive them.
|
||||
|
||||
| Version | Supported |
|
||||
|---|---|
|
||||
| 0.34.0 | yes |
|
||||
| 0.35.0 | yes |
|
||||
| older releases | no |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
+105
-7
@@ -5,9 +5,13 @@
|
||||
// toolchain's own assembler source. Go's Plan 9 assembler defines the exact,
|
||||
// complete set of mnemonics it accepts for each architecture in
|
||||
// $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.
|
||||
//
|
||||
// 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):
|
||||
//
|
||||
// just gen
|
||||
@@ -26,9 +30,12 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"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 {
|
||||
arch string
|
||||
sub string
|
||||
@@ -39,11 +46,30 @@ var archDirs = []struct {
|
||||
{"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() {
|
||||
goroot := strings.TrimSpace(runGoEnvGOROOT())
|
||||
if goroot == "" {
|
||||
fatal("could not determine GOROOT")
|
||||
}
|
||||
version := strings.TrimSpace(runGoEnv("GOVERSION"))
|
||||
// The common opcodes shared by every architecture (RET, JMP, NOP, CALL,
|
||||
// TEXT, FUNCDATA, …) live in 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))
|
||||
|
||||
names := map[string][]string{}
|
||||
for _, a := range archDirs {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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.
|
||||
func writeCommon(names []string) error {
|
||||
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("//\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.
|
||||
func writeGen(arch, sub string, names []string) error {
|
||||
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("//\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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||
if err != nil {
|
||||
@@ -180,6 +269,15 @@ func runGoEnvGOROOT() string {
|
||||
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) {
|
||||
fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -70,6 +70,10 @@ func amd64Registers() []Register {
|
||||
for i := 0; i <= 7; i++ {
|
||||
add(fmt.Sprintf("K%d", i), Mask, "AVX-512 mask register")
|
||||
}
|
||||
// x87 stack registers (FMOVD and the other x87 moves).
|
||||
for i := 0; i <= 7; i++ {
|
||||
add(fmt.Sprintf("F%d", i), Float, "x87 stack register")
|
||||
}
|
||||
return regs
|
||||
}
|
||||
|
||||
|
||||
+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.
|
||||
//
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
|
||||
+31
-10
@@ -24,20 +24,41 @@ const (
|
||||
Unknown Arch = ""
|
||||
)
|
||||
|
||||
// FromFilename guesses the target architecture from a source file name. Go
|
||||
// assembly files conventionally carry a GOARCH suffix such as "_amd64.s",
|
||||
// "_arm64.s", "_riscv64.s" or "_loong64.s". It returns Unknown when no suffix
|
||||
// matches.
|
||||
// FromFilename guesses the target architecture from a source file name, by
|
||||
// go/build's goodOSArchFile rule: directories are stripped, the name is cut
|
||||
// at its first dot, everything before the first underscore is ignored and a
|
||||
// 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 {
|
||||
lower := strings.ToLower(name)
|
||||
switch {
|
||||
case strings.Contains(lower, "_amd64"):
|
||||
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
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
|
||||
case strings.Contains(lower, "_arm64"):
|
||||
case "arm64":
|
||||
return ARM64
|
||||
case strings.Contains(lower, "_riscv64"), strings.Contains(lower, "_riscv"):
|
||||
case "riscv64":
|
||||
return RISCV
|
||||
case strings.Contains(lower, "_loong64"), strings.Contains(lower, "_loong"):
|
||||
case "loong64":
|
||||
return LOONG64
|
||||
default:
|
||||
return Unknown
|
||||
|
||||
+29
-2
@@ -7,13 +7,40 @@ import "testing"
|
||||
|
||||
func TestFromFilename(t *testing.T) {
|
||||
cases := map[string]Arch{
|
||||
// Positive: the final underscore segment is the architecture.
|
||||
"avx2_amd64.s": AMD64,
|
||||
"foo_arm64.s": ARM64,
|
||||
"portable.s": Unknown,
|
||||
"decode_ARM64.S": ARM64,
|
||||
"kernels_amd64.s": AMD64,
|
||||
"kernel_riscv64.s": RISCV,
|
||||
"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 {
|
||||
if got := FromFilename(name); got != want {
|
||||
|
||||
@@ -33,6 +33,7 @@ func arm64Registers() []Register {
|
||||
for i := 0; i <= 30; i++ {
|
||||
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("SP", Special, "stack pointer")
|
||||
add("LR", Special, "link register (alias of R30)")
|
||||
@@ -150,6 +151,33 @@ func arm64Curated() []Instr {
|
||||
t = append(t, i(op, "Atomic memory operation"))
|
||||
}
|
||||
|
||||
// Register-pair loads and stores.
|
||||
for _, op := range []string{"LDP", "STP", "LDPW", "STPW", "FLDPD", "FSTPD"} {
|
||||
t = append(t, ic(op, "Register-pair load or store", 2, 2))
|
||||
}
|
||||
|
||||
// Cache maintenance and prefetch.
|
||||
t = append(t, i("DC", "Data cache maintenance"))
|
||||
t = append(t, i("PRFM", "Memory prefetch"))
|
||||
for _, op := range []string{"LDADDAL", "LDCLRAL", "LDORAL", "SWPAL"} {
|
||||
t = append(t, i(op, "Atomic memory operation with acquire and release semantics"))
|
||||
}
|
||||
|
||||
// Cryptographic extensions.
|
||||
for _, op := range []string{"AESE", "AESD", "AESMC", "AESIMC"} {
|
||||
t = append(t, i(op, "AES round"))
|
||||
}
|
||||
for _, op := range []string{
|
||||
"SHA1C", "SHA1P", "SHA1M", "SHA1H", "SHA1SU0", "SHA1SU1",
|
||||
"SHA256H", "SHA256H2", "SHA256SU0", "SHA256SU1",
|
||||
"SHA512H", "SHA512H2", "SHA512SU0", "SHA512SU1",
|
||||
} {
|
||||
t = append(t, i(op, "SHA round"))
|
||||
}
|
||||
for _, op := range []string{"VEOR3", "VBCAX", "VXAR", "VRAX1"} {
|
||||
t = append(t, i(op, "Three-way XOR / rotate crypto vector operation"))
|
||||
}
|
||||
|
||||
// Floating-point scalar.
|
||||
for _, op := range []string{
|
||||
"FADD", "FSUB", "FMUL", "FDIV", "FNEG", "FABS", "FSQRT", "FMIN", "FMAX",
|
||||
|
||||
+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.
|
||||
//
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
@@ -364,6 +364,8 @@ var arm64GeneratedInstrs = []string{
|
||||
"REVW",
|
||||
"ROR",
|
||||
"RORW",
|
||||
"RPRFM",
|
||||
"SB",
|
||||
"SBC",
|
||||
"SBCS",
|
||||
"SBCSW",
|
||||
@@ -477,23 +479,68 @@ var arm64GeneratedInstrs = []string{
|
||||
"UXTH",
|
||||
"UXTHW",
|
||||
"UXTW",
|
||||
"VABS",
|
||||
"VADD",
|
||||
"VADDP",
|
||||
"VADDV",
|
||||
"VAND",
|
||||
"VBCAX",
|
||||
"VBIC",
|
||||
"VBIF",
|
||||
"VBIT",
|
||||
"VBSL",
|
||||
"VCLS",
|
||||
"VCLZ",
|
||||
"VCMEQ",
|
||||
"VCMGE",
|
||||
"VCMGT",
|
||||
"VCMHI",
|
||||
"VCMHS",
|
||||
"VCMLE",
|
||||
"VCMLT",
|
||||
"VCMTST",
|
||||
"VCNT",
|
||||
"VDUP",
|
||||
"VEOR",
|
||||
"VEOR3",
|
||||
"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",
|
||||
"VFMLS",
|
||||
"VFMUL",
|
||||
"VFNEG",
|
||||
"VFRINTM",
|
||||
"VFRINTN",
|
||||
"VFRINTP",
|
||||
"VFRINTZ",
|
||||
"VFSQRT",
|
||||
"VFSUB",
|
||||
"VLD1",
|
||||
"VLD1R",
|
||||
"VLD2",
|
||||
@@ -502,11 +549,17 @@ var arm64GeneratedInstrs = []string{
|
||||
"VLD3R",
|
||||
"VLD4",
|
||||
"VLD4R",
|
||||
"VMLA",
|
||||
"VMLS",
|
||||
"VMOV",
|
||||
"VMOVD",
|
||||
"VMOVI",
|
||||
"VMOVQ",
|
||||
"VMOVS",
|
||||
"VMUL",
|
||||
"VNEG",
|
||||
"VNOT",
|
||||
"VORN",
|
||||
"VORR",
|
||||
"VPMULL",
|
||||
"VPMULL2",
|
||||
@@ -515,14 +568,47 @@ var arm64GeneratedInstrs = []string{
|
||||
"VREV16",
|
||||
"VREV32",
|
||||
"VREV64",
|
||||
"VSCVTF",
|
||||
"VSHADD",
|
||||
"VSHL",
|
||||
"VSHRN",
|
||||
"VSHRN2",
|
||||
"VSLI",
|
||||
"VSMAX",
|
||||
"VSMAXP",
|
||||
"VSMAXV",
|
||||
"VSMIN",
|
||||
"VSMINP",
|
||||
"VSMINV",
|
||||
"VSMLAL",
|
||||
"VSMLAL2",
|
||||
"VSMLSL",
|
||||
"VSMLSL2",
|
||||
"VSMULL",
|
||||
"VSMULL2",
|
||||
"VSQABS",
|
||||
"VSQADD",
|
||||
"VSQNEG",
|
||||
"VSQSHL",
|
||||
"VSQSUB",
|
||||
"VSQXTN",
|
||||
"VSQXTN2",
|
||||
"VSQXTUN",
|
||||
"VSQXTUN2",
|
||||
"VSRHADD",
|
||||
"VSRI",
|
||||
"VSRSHR",
|
||||
"VSSHL",
|
||||
"VSSHLL",
|
||||
"VSSHLL2",
|
||||
"VSSHR",
|
||||
"VST1",
|
||||
"VST2",
|
||||
"VST3",
|
||||
"VST4",
|
||||
"VSUB",
|
||||
"VSXTL",
|
||||
"VSXTL2",
|
||||
"VTBL",
|
||||
"VTBX",
|
||||
"VTRN1",
|
||||
@@ -530,8 +616,27 @@ var arm64GeneratedInstrs = []string{
|
||||
"VUADDLV",
|
||||
"VUADDW",
|
||||
"VUADDW2",
|
||||
"VUCVTF",
|
||||
"VUHADD",
|
||||
"VUMAX",
|
||||
"VUMAXP",
|
||||
"VUMAXV",
|
||||
"VUMIN",
|
||||
"VUMINP",
|
||||
"VUMINV",
|
||||
"VUMLAL",
|
||||
"VUMLAL2",
|
||||
"VUMLSL",
|
||||
"VUMLSL2",
|
||||
"VUMULL",
|
||||
"VUMULL2",
|
||||
"VUQADD",
|
||||
"VUQSHL",
|
||||
"VUQSUB",
|
||||
"VUQXTN",
|
||||
"VUQXTN2",
|
||||
"VURHADD",
|
||||
"VUSHL",
|
||||
"VUSHLL",
|
||||
"VUSHLL2",
|
||||
"VUSHR",
|
||||
@@ -541,6 +646,8 @@ var arm64GeneratedInstrs = []string{
|
||||
"VUZP1",
|
||||
"VUZP2",
|
||||
"VXAR",
|
||||
"VXTN",
|
||||
"VXTN2",
|
||||
"VZIP1",
|
||||
"VZIP2",
|
||||
"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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
@@ -152,6 +152,8 @@ var loong64GeneratedInstrs = []string{
|
||||
"FNMADDF",
|
||||
"FNMSUBD",
|
||||
"FNMSUBF",
|
||||
"FRINTD",
|
||||
"FRINTF",
|
||||
"FSCALEBD",
|
||||
"FSCALEBF",
|
||||
"FSEL",
|
||||
@@ -177,7 +179,10 @@ var loong64GeneratedInstrs = []string{
|
||||
"FTINTWF",
|
||||
"JIRL",
|
||||
"LL",
|
||||
"LLACQV",
|
||||
"LLACQW",
|
||||
"LLV",
|
||||
"LLW",
|
||||
"LU12IW",
|
||||
"LU32ID",
|
||||
"LU52ID",
|
||||
@@ -248,7 +253,11 @@ var loong64GeneratedInstrs = []string{
|
||||
"ROTR",
|
||||
"ROTRV",
|
||||
"SC",
|
||||
"SCQ",
|
||||
"SCRELV",
|
||||
"SCRELW",
|
||||
"SCV",
|
||||
"SCW",
|
||||
"SGT",
|
||||
"SGTU",
|
||||
"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.
|
||||
//
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
@@ -81,6 +81,9 @@ var riscvGeneratedInstrs = []string{
|
||||
"CLD",
|
||||
"CLDSP",
|
||||
"CLI",
|
||||
"CLMUL",
|
||||
"CLMULH",
|
||||
"CLMULR",
|
||||
"CLUI",
|
||||
"CLW",
|
||||
"CLWSP",
|
||||
@@ -95,13 +98,20 @@ var riscvGeneratedInstrs = []string{
|
||||
"CSDSP",
|
||||
"CSLLI",
|
||||
"CSRAI",
|
||||
"CSRC",
|
||||
"CSRCI",
|
||||
"CSRLI",
|
||||
"CSRR",
|
||||
"CSRRC",
|
||||
"CSRRCI",
|
||||
"CSRRS",
|
||||
"CSRRSI",
|
||||
"CSRRW",
|
||||
"CSRRWI",
|
||||
"CSRS",
|
||||
"CSRSI",
|
||||
"CSRW",
|
||||
"CSRWI",
|
||||
"CSUB",
|
||||
"CSUBW",
|
||||
"CSW",
|
||||
@@ -259,6 +269,7 @@ var riscvGeneratedInstrs = []string{
|
||||
"ORCB",
|
||||
"ORI",
|
||||
"ORN",
|
||||
"PAUSE",
|
||||
"RDCYCLE",
|
||||
"RDINSTRET",
|
||||
"RDTIME",
|
||||
@@ -322,6 +333,8 @@ var riscvGeneratedInstrs = []string{
|
||||
"VADDVI",
|
||||
"VADDVV",
|
||||
"VADDVX",
|
||||
"VANDNVV",
|
||||
"VANDNVX",
|
||||
"VANDVI",
|
||||
"VANDVV",
|
||||
"VANDVX",
|
||||
@@ -329,8 +342,17 @@ var riscvGeneratedInstrs = []string{
|
||||
"VASUBUVX",
|
||||
"VASUBVV",
|
||||
"VASUBVX",
|
||||
"VBREV8V",
|
||||
"VBREVV",
|
||||
"VCLMULHVV",
|
||||
"VCLMULHVX",
|
||||
"VCLMULVV",
|
||||
"VCLMULVX",
|
||||
"VCLZV",
|
||||
"VCOMPRESSVM",
|
||||
"VCPOPM",
|
||||
"VCPOPV",
|
||||
"VCTZV",
|
||||
"VDIVUVV",
|
||||
"VDIVUVX",
|
||||
"VDIVVV",
|
||||
@@ -743,10 +765,16 @@ var riscvGeneratedInstrs = []string{
|
||||
"VREMUVX",
|
||||
"VREMVV",
|
||||
"VREMVX",
|
||||
"VREV8V",
|
||||
"VRGATHEREI16VV",
|
||||
"VRGATHERVI",
|
||||
"VRGATHERVV",
|
||||
"VRGATHERVX",
|
||||
"VROLVV",
|
||||
"VROLVX",
|
||||
"VRORVI",
|
||||
"VRORVV",
|
||||
"VRORVX",
|
||||
"VRSUBVI",
|
||||
"VRSUBVX",
|
||||
"VS1RV",
|
||||
@@ -950,6 +978,9 @@ var riscvGeneratedInstrs = []string{
|
||||
"VWMULVX",
|
||||
"VWREDSUMUVS",
|
||||
"VWREDSUMVS",
|
||||
"VWSLLVI",
|
||||
"VWSLLVV",
|
||||
"VWSLLVX",
|
||||
"VWSUBUVV",
|
||||
"VWSUBUVX",
|
||||
"VWSUBUWV",
|
||||
|
||||
+89
-39
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
||||
@@ -178,26 +178,10 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||
}
|
||||
var work, linkLine, asmObj string
|
||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
||||
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)
|
||||
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||
defer os.RemoveAll(st.work)
|
||||
|
||||
// Expand $WORK in the object path.
|
||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||
|
||||
// Read the toolchain-produced object and assemble the same source with gasm.
|
||||
// Assemble the same source with gasm and substitute the object.
|
||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -210,30 +194,16 @@ func main() {
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||
}
|
||||
|
||||
// Replace the toolchain-produced object with gasm's.
|
||||
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)
|
||||
}
|
||||
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||
gasmObj, "GOARCH=arm64")
|
||||
|
||||
// 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 {
|
||||
t.Fatalf("binary not found: %v", err)
|
||||
}
|
||||
@@ -245,3 +215,83 @@ func main() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4834
-154
File diff suppressed because it is too large.
Load diff
+916
-20
File diff suppressed because it is too large.
Load diff
+1804
-18
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 (
|
||||
"strings"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
)
|
||||
|
||||
// arm64FrameInfo holds the frame layout derived from a TEXT directive.
|
||||
@@ -69,6 +69,12 @@ type arm64FrameInfo struct {
|
||||
// autosize below StackSmall as NOSPLIT.
|
||||
needSplit bool
|
||||
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.
|
||||
@@ -87,7 +93,10 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
|
||||
if fi.frame != 0 || !fi.leaf {
|
||||
fi.autosize = fi.frame + 8 // space for the saved LR
|
||||
// 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 {
|
||||
case 8:
|
||||
fi.autosize += 8
|
||||
@@ -99,6 +108,13 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
|
||||
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 {
|
||||
case fi.noSplit:
|
||||
case fi.autosize < stackSmall && fi.leaf:
|
||||
@@ -301,6 +317,37 @@ func arm64Return(fi arm64FrameInfo) []byte {
|
||||
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
|
||||
// prologue has finished decrementing SP (the delta becomes autosize).
|
||||
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
||||
@@ -362,6 +409,59 @@ func arm64ResolvePseudo(sym *ast.Symbol, fi arm64FrameInfo) (base int, off int32
|
||||
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):
|
||||
// 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 {
|
||||
@@ -454,6 +554,10 @@ func arm64GuardBytes(fi arm64FrameInfo, blockStart int) []byte {
|
||||
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),
|
||||
// BL runtime.morestack_noctxt, B back to the function start. The BL carries
|
||||
// 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"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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
|
||||
// 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"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
fn := firstText(t, `
|
||||
#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
|
||||
|
||||
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
|
||||
// guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation.
|
||||
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.
|
||||
stNames := newElfStrtab()
|
||||
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"} {
|
||||
stSections.add(n)
|
||||
}
|
||||
if hasDataRela {
|
||||
stSections.add(".rela.data")
|
||||
}
|
||||
for _, n := range dwarfSectionNames {
|
||||
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.
|
||||
var out []byte
|
||||
out = append(out, make([]byte, 64)...) // ELF header, filled last
|
||||
@@ -214,7 +255,7 @@ func (img *Image) ELFObject() ([]byte, error) {
|
||||
strtabOff := len(out)
|
||||
out = append(out, stNames.bytes()...)
|
||||
|
||||
var relaOff int
|
||||
var relaOff, relaDataOff int
|
||||
if hasRela {
|
||||
align(8)
|
||||
relaOff = len(out)
|
||||
@@ -226,6 +267,17 @@ func (img *Image) ELFObject() ([]byte, error) {
|
||||
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)
|
||||
out = append(out, stSections.bytes()...)
|
||||
@@ -284,6 +336,9 @@ func (img *Image) ELFObject() ([]byte, error) {
|
||||
if hasRela {
|
||||
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)
|
||||
|
||||
// DWARF section headers; their indices follow the write order.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
||||
|
||||
+110
-2
@@ -12,8 +12,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction)
|
||||
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
|
||||
// AArch64 (EM_AARCH64, 64-bit, little-endian). The structure mirrors the
|
||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an
|
||||
// optional .rela.text.
|
||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an
|
||||
// optional .rela.text and an optional .rela.data.
|
||||
func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
||||
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 + 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:
|
||||
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.
|
||||
stNames := newElfStrtab()
|
||||
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"} {
|
||||
stSections.add(n)
|
||||
}
|
||||
if hasDataRela {
|
||||
stSections.add(".rela.data")
|
||||
}
|
||||
for _, n := range dwarfSectionNames {
|
||||
stSections.add(n)
|
||||
}
|
||||
|
||||
hasRela := len(relas) > 0
|
||||
nSections := 6
|
||||
if hasRela {
|
||||
nSections = 7
|
||||
}
|
||||
secSymtab, secStrtab := 3, 4
|
||||
secShstr := nSections - 1
|
||||
|
||||
// Layout.
|
||||
var out []byte
|
||||
out = append(out, make([]byte, 64)...)
|
||||
@@ -188,7 +239,7 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
||||
strtabOff := len(out)
|
||||
out = append(out, stNames.bytes()...)
|
||||
|
||||
var relaOff int
|
||||
var relaOff, relaDataOff int
|
||||
if hasRela {
|
||||
align(8)
|
||||
relaOff = len(out)
|
||||
@@ -200,6 +251,17 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
||||
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)
|
||||
out = append(out, stSections.bytes()...)
|
||||
@@ -257,6 +319,9 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
||||
if hasRela {
|
||||
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)
|
||||
// DWARF section headers; their indices follow the write order.
|
||||
if dw != nil {
|
||||
|
||||
+116
-1
@@ -9,7 +9,7 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
||||
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
|
||||
// LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the
|
||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an
|
||||
// optional .rela.text.
|
||||
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an
|
||||
// optional .rela.text and an optional .rela.data.
|
||||
func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||
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.
|
||||
stNames := newElfStrtab()
|
||||
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"} {
|
||||
stSections.add(n)
|
||||
}
|
||||
if hasDataRela {
|
||||
stSections.add(".rela.data")
|
||||
}
|
||||
for _, n := range dwarfSectionNames {
|
||||
stSections.add(n)
|
||||
}
|
||||
|
||||
hasRela := len(relas) > 0
|
||||
nSections := 6
|
||||
if hasRela {
|
||||
nSections = 7
|
||||
}
|
||||
secSymtab, secStrtab := 3, 4
|
||||
secShstr := nSections - 1
|
||||
|
||||
// Layout.
|
||||
var out []byte
|
||||
out = append(out, make([]byte, 64)...)
|
||||
@@ -172,7 +215,7 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||
strtabOff := len(out)
|
||||
out = append(out, stNames.bytes()...)
|
||||
|
||||
var relaOff int
|
||||
var relaOff, relaDataOff int
|
||||
if hasRela {
|
||||
align(8)
|
||||
relaOff = len(out)
|
||||
@@ -184,6 +227,17 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||
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)
|
||||
out = append(out, stSections.bytes()...)
|
||||
@@ -239,6 +293,9 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||
if hasRela {
|
||||
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)
|
||||
// DWARF section headers; their indices follow the write order.
|
||||
if dw != nil {
|
||||
|
||||
+116
-1
@@ -9,7 +9,7 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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
|
||||
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
||||
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
|
||||
// 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) {
|
||||
le := binary.LittleEndian
|
||||
|
||||
@@ -123,12 +130,65 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
||||
)
|
||||
case RelRISCVJal:
|
||||
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:
|
||||
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.
|
||||
stNames := newElfStrtab()
|
||||
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"} {
|
||||
stSections.add(n)
|
||||
}
|
||||
if hasDataRela {
|
||||
stSections.add(".rela.data")
|
||||
}
|
||||
for _, n := range dwarfSectionNames {
|
||||
stSections.add(n)
|
||||
}
|
||||
|
||||
hasRela := len(relas) > 0
|
||||
nSections := 6
|
||||
if hasRela {
|
||||
nSections = 7
|
||||
}
|
||||
secSymtab, secStrtab := 3, 4
|
||||
secShstr := nSections - 1
|
||||
|
||||
// Layout.
|
||||
var out []byte
|
||||
out = append(out, make([]byte, 64)...)
|
||||
@@ -184,7 +239,7 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
||||
strtabOff := len(out)
|
||||
out = append(out, stNames.bytes()...)
|
||||
|
||||
var relaOff int
|
||||
var relaOff, relaDataOff int
|
||||
if hasRela {
|
||||
align(8)
|
||||
relaOff = len(out)
|
||||
@@ -196,6 +251,17 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
||||
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)
|
||||
out = append(out, stSections.bytes()...)
|
||||
@@ -251,6 +317,9 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
||||
if hasRela {
|
||||
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)
|
||||
// DWARF section headers; their indices follow the write order.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
-17
@@ -16,9 +16,36 @@ import "strings"
|
||||
func Encodable(mnemonic string) bool {
|
||||
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).
|
||||
switch upper {
|
||||
case "RET", "NOP", "CALL", "JMP":
|
||||
case "RET", "NOP", "CALL", "JMP",
|
||||
"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
|
||||
}
|
||||
if _, ok := noOperandTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := condCode(upper); ok {
|
||||
@@ -31,35 +58,48 @@ func Encodable(mnemonic string) bool {
|
||||
return false
|
||||
}
|
||||
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) ||
|
||||
base == "KMOVW" || base == "KMOVQ" {
|
||||
base == "KMOVW" || base == "KMOVQ" || base == "KMOVB" || base == "KMOVD" {
|
||||
return true
|
||||
}
|
||||
|
||||
// CMOV carries size then condition (CMOVLGT); SET carries the condition
|
||||
// alone (SETNE). The size letter is checked exactly as encodeCmov does,
|
||||
// so a spelling like CMOVBGT is not reported encodable when Encode
|
||||
// would reject it.
|
||||
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 {
|
||||
switch rest[0] {
|
||||
case 'W', 'L', 'Q':
|
||||
if _, ok := jccMap[rest[1:]]; ok {
|
||||
// CMOV carries size then condition (CMOVLGT), or the renderer's
|
||||
// condition alone (CMOVLE) with the width from the operand; SET carries
|
||||
// the condition alone (SETNE). cmovCondition checks the suffix exactly
|
||||
// as encodeCmov does, so a spelling like CMOVBGT is not reported
|
||||
// encodable when Encode would reject it.
|
||||
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok {
|
||||
if _, _, ok := cmovCondition(rest); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
||||
if _, ok := jccMap[rest]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy SSE shuffles and packed binaries dispatch on the full name.
|
||||
// Legacy SSE shuffles and packed binaries dispatch on the full name; so
|
||||
// do the imm8-controlled instructions, the lane extracts and inserts and
|
||||
// the packed integer shifts (their trailing width letters belong to the
|
||||
// mnemonic).
|
||||
if _, ok := sseShufTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := sseBinTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := sseImm3Table[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := sseExtractTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := sseInsertTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := sseShiftImm[upper]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The size-suffix split: retry the tables and the scalar switch on the
|
||||
// base.
|
||||
@@ -74,12 +114,15 @@ func Encodable(mnemonic string) bool {
|
||||
}
|
||||
}
|
||||
switch base2 {
|
||||
case "MOV",
|
||||
"ADD", "SUB", "AND", "OR", "XOR", "CMP",
|
||||
case "MOV", "MOVD",
|
||||
"ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB",
|
||||
"TEST",
|
||||
"LEA",
|
||||
"INC", "DEC", "NEG", "NOT",
|
||||
"SHL", "SHR", "SAR",
|
||||
"INC", "DEC", "NEG", "NOT", "MUL", "DIV", "IDIV",
|
||||
"SHL", "SHR", "SAR", "SAL", "ROL", "ROR", "RCL", "RCR",
|
||||
"BT", "BTS", "BTR", "BTC",
|
||||
"XCHG", "CMPXCHG", "XADD", "CRC32", "ADCX", "ADOX",
|
||||
"MOVS", "STOS",
|
||||
"IMUL", "IMUL3",
|
||||
"PUSH", "POP",
|
||||
"BSF", "BSR", "LZCNT", "TZCNT", "POPCNT",
|
||||
@@ -88,7 +131,9 @@ func Encodable(mnemonic string) bool {
|
||||
"MOVBLZX", "MOVBQZX", "MOVWLZX", "MOVWQZX", "MOVWLSX", "MOVLQSX",
|
||||
"MOVBWZX", "MOVBWSX", "MOVBLSX", "MOVBQSX", "MOVWQSX", "MOVLQZX",
|
||||
"CVTSL2SD", "CVTSQ2SD",
|
||||
"MOVOU", "MOVO", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD", "MOVSD", "MOVSS":
|
||||
"CVTSD2S", "CVTTSD2S", "CVTSS2S", "CVTTSS2S",
|
||||
"FMOVD",
|
||||
"MOVOU", "MOVO", "MOVOA", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD", "MOVSD", "MOVSS":
|
||||
return true
|
||||
}
|
||||
// Full-name dispatches the size split would eat (a trailing width
|
||||
|
||||
+491
-9
@@ -5,6 +5,8 @@ package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -21,6 +23,39 @@ func Encode(mnemonic string, ops ...Operand) ([]byte, error) {
|
||||
type enc struct {
|
||||
out []byte
|
||||
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
|
||||
@@ -29,17 +64,39 @@ type encPatch struct {
|
||||
off int
|
||||
name string
|
||||
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 {
|
||||
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).
|
||||
switch {
|
||||
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()
|
||||
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":
|
||||
// Through a register or memory: FF /2 (CALL) or FF /4 (JMP).
|
||||
// Anything else is a rel32 against a label resolved by the assembler.
|
||||
@@ -58,6 +115,121 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
if cc, ok := condCode(upper); ok {
|
||||
return e.encodeJcc(cc, ops)
|
||||
}
|
||||
// No-operand system and string-control instructions (CPUID, RDTSC,
|
||||
// SYSCALL, the fences, UNDEF, …).
|
||||
if op, ok := noOperandTable[upper]; ok {
|
||||
if len(ops) != 0 {
|
||||
return fmt.Errorf("%s takes no operands, got %d", upper, len(ops))
|
||||
}
|
||||
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
|
||||
// are rejected by go tool asm in 64-bit mode, so they stay unsupported.
|
||||
switch upper {
|
||||
case "POPFQ":
|
||||
if len(ops) != 0 {
|
||||
return fmt.Errorf("POPFQ takes no operands, got %d", len(ops))
|
||||
}
|
||||
return e.emit(&instr{opcode: []byte{0x9D}, modrm: -1, sib: -1})
|
||||
case "PUSHFQ":
|
||||
if len(ops) != 0 {
|
||||
return fmt.Errorf("PUSHFQ takes no operands, got %d", len(ops))
|
||||
}
|
||||
return e.emit(&instr{opcode: []byte{0x9C}, modrm: -1, sib: -1})
|
||||
case "INT":
|
||||
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":
|
||||
return e.encodeMxcsr(2, ops)
|
||||
case "STMXCSR":
|
||||
return e.encodeMxcsr(3, ops)
|
||||
// CMPSD is the scalar double compare, whose predicate immediate comes
|
||||
// LAST in Plan 9 order (src, dst, $imm); the family shares the shape.
|
||||
case "CMPSD":
|
||||
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.
|
||||
case "SHA256RNDS2":
|
||||
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
|
||||
// B/W/L/Q/D is part of the mnemonic, not a size suffix, so dispatch
|
||||
@@ -67,7 +239,9 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) || base == "KMOVW" || base == "KMOVQ" {
|
||||
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) ||
|
||||
isEvexPrefGather(base) ||
|
||||
base == "KMOVW" || base == "KMOVQ" || base == "KMOVB" || base == "KMOVD" {
|
||||
return e.encodeVec(base, ops, sfx)
|
||||
}
|
||||
if sfx.any() {
|
||||
@@ -83,6 +257,19 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
}
|
||||
|
||||
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 {
|
||||
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
||||
}
|
||||
@@ -94,13 +281,35 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
}
|
||||
// Legacy SSE packed binaries dispatch on the full name: the packed
|
||||
// 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 f, isFloat := floatImmOperand(ops); isFloat {
|
||||
return e.encodeSSEFloatBin(upper, m, f, ops)
|
||||
}
|
||||
return e.encodeSSEBin(m, ops)
|
||||
}
|
||||
if m, ok := sseBinTable[base]; ok {
|
||||
if f, isFloat := floatImmOperand(ops); isFloat {
|
||||
return e.encodeSSEFloatBin(upper, m, f, ops)
|
||||
}
|
||||
return e.encodeSSEBin(m, ops)
|
||||
}
|
||||
// The imm8-controlled legacy instructions, the lane extracts and inserts
|
||||
// and the packed integer shifts all dispatch on the full name: a trailing
|
||||
// width letter here belongs to the mnemonic, not to the size split.
|
||||
if m, ok := sseImm3Table[upper]; ok {
|
||||
return e.encodeSSEImm3(m, ops)
|
||||
}
|
||||
if m, ok := sseExtractTable[upper]; ok {
|
||||
return e.encodeSSEExtract(m, ops)
|
||||
}
|
||||
if m, ok := sseInsertTable[upper]; ok {
|
||||
return e.encodeSSEInsert(m, ops)
|
||||
}
|
||||
if _, ok := sseShiftImm[upper]; ok {
|
||||
return e.encodeSSEShift(upper, ops)
|
||||
}
|
||||
// PMOVMSKB ends in a width letter the size split would eat, so it
|
||||
// dispatches on the full name like the packed binaries above.
|
||||
if upper == "PMOVMSKB" {
|
||||
@@ -109,16 +318,40 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
switch base {
|
||||
case "MOV":
|
||||
return e.encodeMov(ops, size)
|
||||
case "ADD", "SUB", "AND", "OR", "XOR", "CMP":
|
||||
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
|
||||
// REX.W and all. The alias takes no byte register either, the same
|
||||
// conflict rule the Q-suffixed spelling answers to.
|
||||
case "MOVD":
|
||||
if _, err := operandWidth(mnem, 8, ops); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.encodeMov(ops, 8)
|
||||
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
|
||||
return e.encodeALU(aluOp[base], ops, size)
|
||||
case "TEST":
|
||||
return e.encodeTest(ops, size)
|
||||
case "LEA":
|
||||
return e.encodeLea(ops, size)
|
||||
case "INC", "DEC", "NEG", "NOT":
|
||||
case "INC", "DEC", "NEG", "NOT", "MUL", "DIV", "IDIV":
|
||||
return e.encodeUnary(unaryOp[base], ops, size)
|
||||
case "SHL", "SHR", "SAR":
|
||||
return e.encodeShift(shiftOp[base], ops, size)
|
||||
case "SHL", "SHR", "SAR", "SAL", "ROL", "ROR", "RCL", "RCR":
|
||||
return e.encodeShift(base, ops, size)
|
||||
case "BT", "BTS", "BTR", "BTC":
|
||||
return e.encodeBitTest(base, ops, size)
|
||||
case "XCHG":
|
||||
return e.encodeExchange(ops, size)
|
||||
case "CMPXCHG":
|
||||
return e.encodeRegRegOp(0xB0, 0xB1, base, ops, size)
|
||||
case "XADD":
|
||||
return e.encodeRegRegOp(0xC0, 0xC1, base, ops, size)
|
||||
case "CRC32":
|
||||
return e.encodeCrc32(ops, size)
|
||||
case "ADCX":
|
||||
return e.encodeCarryExt(0x66, ops, size)
|
||||
case "ADOX":
|
||||
return e.encodeCarryExt(0xF3, ops, size)
|
||||
case "MOVS", "STOS":
|
||||
return e.encodeStringOp(base, ops, size)
|
||||
case "IMUL", "IMUL3":
|
||||
return e.encodeImul(ops, size)
|
||||
case "PUSH":
|
||||
@@ -136,7 +369,16 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
||||
return e.encodeMovExtend(base, ops)
|
||||
case "CVTSL2SD", "CVTSQ2SD":
|
||||
return e.encodeCvtsi2sd(base == "CVTSQ2SD", ops)
|
||||
case "MOVOU", "MOVO", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD", "MOVSD", "MOVSS":
|
||||
case "CVTSD2S", "CVTTSD2S", "CVTSS2S", "CVTTSS2S":
|
||||
return e.encodeCvtInt(base, ops, size)
|
||||
case "FMOVD":
|
||||
return e.encodeFmov(ops)
|
||||
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 fmt.Errorf("unsupported instruction %q", mnem)
|
||||
@@ -166,6 +408,214 @@ var prefetchVariant = map[string]int{
|
||||
"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.
|
||||
func splitSize(upper string) (base string, size int) {
|
||||
if upper == "" {
|
||||
@@ -195,7 +645,7 @@ func (e *enc) encodeVec(upper string, ops []Operand, sfx evexSuffix) error {
|
||||
if ss, ok := scatterTable[upper]; ok {
|
||||
return e.encodeScatter(upper, ss, ops, sfx)
|
||||
}
|
||||
if upper == "KMOVW" || upper == "KMOVQ" {
|
||||
if upper == "KMOVW" || upper == "KMOVQ" || upper == "KMOVB" || upper == "KMOVD" {
|
||||
if sfx.any() {
|
||||
return fmt.Errorf("%s takes no EVEX suffixes", upper)
|
||||
}
|
||||
@@ -232,6 +682,7 @@ type instr struct {
|
||||
disp []byte
|
||||
imm []byte
|
||||
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
|
||||
@@ -274,6 +725,9 @@ func (e *enc) emit(i *instr) error {
|
||||
if i.sb != nil {
|
||||
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.imm...)
|
||||
return nil
|
||||
@@ -328,12 +782,30 @@ func setRMReg(i *instr, regField int, rexR, regForced bool, rm Operand, opSize i
|
||||
i.disp = le32(0)
|
||||
i.sb = &sbRef{name: r.name, addend: r.addend}
|
||||
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:
|
||||
return fmt.Errorf("invalid r/m operand %T", rm)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -346,6 +818,16 @@ func setMem(i *instr, regField int, m Mem) error {
|
||||
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
|
||||
// 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.
|
||||
|
||||
+763
-4
@@ -9,6 +9,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"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
|
||||
@@ -200,10 +203,60 @@ func TestUnary(t *testing.T) {
|
||||
func TestShift(t *testing.T) {
|
||||
checkSyntax(t, "shl rdx, 0x2", "SHLQ", Imm(2), DX)
|
||||
checkSyntax(t, "shl rdx, cl", "SHLQ", CL, DX)
|
||||
checkSyntax(t, "shl rdx, cl", "SHLQ", CX, DX)
|
||||
checkSyntax(t, "shl rdx, 0x1", "SHLQ", Imm(1), DX)
|
||||
checkSyntax(t, "sar rcx, 0x1f", "SARQ", Imm(31), CX)
|
||||
}
|
||||
|
||||
// TestDoubleShift pins the three-operand SHL/SHR form, which encodes as
|
||||
// SHLD/SHRD: go tool asm accepts it for SHL/SHR at W/L/Q widths and rejects
|
||||
// it for SAR, SAL, the rotates and the B width. The byte pins mirror the
|
||||
// oracle's objdump output (48 0f a4 fe 0d for the first case, and so on).
|
||||
func TestDoubleShift(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
want string // hex encoding
|
||||
}{
|
||||
{"SHLQ imm", "SHLQ", []Operand{Imm(0x0d), DI, SI}, "480fa4fe0d"},
|
||||
{"SHLQ CX high regs", "SHLQ", []Operand{CX, Reg{idx: 8, size: 8}, Reg{idx: 9, size: 8}}, "4d0fa5c1"},
|
||||
{"SHRQ imm", "SHRQ", []Operand{Imm(1), AX, CX}, "480facc101"},
|
||||
{"SHLW imm", "SHLW", []Operand{Imm(1), AX, CX}, "660fa4c101"},
|
||||
{"SHRD CL", "SHRQ", []Operand{CL, AX, CX}, "480fadc1"},
|
||||
{"SHLD imm high regs", "SHLQ", []Operand{Imm(2), Reg{idx: 10, size: 8}, Reg{idx: 11, size: 8}}, "4d0fa4d302"},
|
||||
{"SHRD imm max", "SHRQ", []Operand{Imm(63), Reg{idx: 9, size: 8}, Reg{idx: 15, size: 8}}, "4d0faccf3f"},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Rejected forms: the oracle rejects every one of these.
|
||||
rejected := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
}{
|
||||
{"SARQ three operands", "SARQ", []Operand{Imm(1), AX, CX}},
|
||||
{"SALQ three operands", "SALQ", []Operand{Imm(1), AX, CX}},
|
||||
{"ROLQ three operands", "ROLQ", []Operand{Imm(1), AX, CX}},
|
||||
{"SHLB three operands", "SHLB", []Operand{Imm(1), AL, CL}},
|
||||
{"SHRQ memory source", "SHRQ", []Operand{Imm(1), Ptr(AX, 0, 8), CX}},
|
||||
{"SHRQ ECX count", "SHRQ", []Operand{Reg{idx: 1, size: 4}, AX, CX}},
|
||||
}
|
||||
for _, c := range rejected {
|
||||
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||||
t.Errorf("%s: Encode succeeded, want rejection", c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImul(t *testing.T) {
|
||||
checkSyntax(t, "imul rdx, rcx", "IMULQ", CX, DX)
|
||||
checkSyntax(t, "imul edx, edx, 0x3", "IMULL", Imm(3), DX, DX)
|
||||
@@ -212,7 +265,11 @@ func TestImul(t *testing.T) {
|
||||
|
||||
func TestControl(t *testing.T) {
|
||||
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.CALL, "CALL", Imm(0))
|
||||
checkOp(t, x86asm.JGE, "JGE", Imm(0))
|
||||
@@ -277,6 +334,12 @@ func TestSSEMoveGroundTruth(t *testing.T) {
|
||||
{"MOVSD (SI),X1", "MOVSD", []Operand{Ptr(SI, 0, 8), vreg(t, "X1")}, "f20f100e", "MOVSD_XMM"},
|
||||
{"MOVSD X1,X2", "MOVSD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f20f10d1", "MOVSD_XMM"},
|
||||
{"MOVSS X3,(DI)", "MOVSS", []Operand{vreg(t, "X3"), Ptr(DI, 0, 4)}, "f30f111f", "MOVSS"},
|
||||
// Static-symbol (SB) references: the GOROOT crypto kernels load and
|
||||
// store octa constants by name (MOVOU bswapMask<>+0(SB), X0).
|
||||
{"MOVOU sym,X0", "MOVOU", []Operand{sbMem{size: 16, name: "bswapMask"}, vreg(t, "X0")}, "f30f6f0500000000", "MOVDQU"},
|
||||
{"MOVOU X0,sym+8", "MOVOU", []Operand{vreg(t, "X0"), sbMem{size: 16, name: "bswapMask", addend: 8}}, "f30f7f0500000000", "MOVDQU"},
|
||||
{"MOVO sym,X1", "MOVO", []Operand{sbMem{size: 16, name: "gcmPoly"}, vreg(t, "X1")}, "660f6f0d00000000", "MOVDQA"},
|
||||
{"MOVO X2,sym", "MOVO", []Operand{vreg(t, "X2"), sbMem{size: 16, name: "gcmPoly"}}, "660f7f1500000000", "MOVDQA"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
code, err := Encode(c.mnem, c.ops...)
|
||||
@@ -348,6 +411,16 @@ func TestScalarGroundTruth(t *testing.T) {
|
||||
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
||||
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
||||
{"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 (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
||||
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
||||
@@ -532,20 +605,263 @@ func TestImmediateTruncation(t *testing.T) {
|
||||
|
||||
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
||||
// 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) {
|
||||
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOVB", "CMOV", "CMOVWXX"} {
|
||||
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOV", "CMOVWXX"} {
|
||||
if Encodable(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) {
|
||||
t.Errorf("Encodable(%q) = false, want true", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCarryShiftMulGroundTruth pins the carry-flag ALU family (ADC/SBB with
|
||||
// their accumulator immediate forms), the rotate family, MUL/DIV/IDIV and the
|
||||
// bit-test family byte for byte against go tool asm (see
|
||||
// testdata/verify/scalar_amd64.s).
|
||||
func TestCarryShiftMulGroundTruth(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
want string
|
||||
}{
|
||||
{"ADCQ AX,BX", "ADCQ", []Operand{AX, BX}, "4811c3"},
|
||||
{"ADCL AX,BX", "ADCL", []Operand{AX, BX}, "11c3"},
|
||||
{"ADCB AL,BL", "ADCB", []Operand{AL, BL}, "10c3"},
|
||||
{"ADCW AX,BX", "ADCW", []Operand{AX, BX}, "6611c3"},
|
||||
{"SBBQ AX,BX", "SBBQ", []Operand{AX, BX}, "4819c3"},
|
||||
{"ADCQ $5,BX", "ADCQ", []Operand{Imm(5), BX}, "4883d305"},
|
||||
{"ADCQ $300,BX", "ADCQ", []Operand{Imm(300), BX}, "4881d32c010000"},
|
||||
{"ADCQ $300,AX", "ADCQ", []Operand{Imm(300), AX}, "48152c010000"},
|
||||
{"ADCB $5,AL", "ADCB", []Operand{Imm(5), AL}, "1405"},
|
||||
{"SBBQ $300,AX", "SBBQ", []Operand{Imm(300), AX}, "481d2c010000"},
|
||||
{"ADCQ AX,(BX)", "ADCQ", []Operand{AX, Ptr(BX, 0, 8)}, "481103"},
|
||||
{"ROLQ $3,AX", "ROLQ", []Operand{Imm(3), AX}, "48c1c003"},
|
||||
{"ROLL CX,BX", "ROLL", []Operand{CL, BX}, "d3c3"},
|
||||
{"RORQ CL,AX", "RORQ", []Operand{CL, AX}, "48d3c8"},
|
||||
{"RCRQ $1,BX", "RCRQ", []Operand{Imm(1), BX}, "48d1db"},
|
||||
{"RCLQ $3,AX", "RCLQ", []Operand{Imm(3), AX}, "48c1d003"},
|
||||
{"RORB CL,BL", "RORB", []Operand{CL, BL}, "d2cb"},
|
||||
{"SALQ $2,AX", "SALQ", []Operand{Imm(2), AX}, "48c1e002"},
|
||||
{"ROLW $1,AX", "ROLW", []Operand{Imm(1), AX}, "66d1c0"},
|
||||
{"MULQ CX", "MULQ", []Operand{CX}, "48f7e1"},
|
||||
{"MULL CX", "MULL", []Operand{CX}, "f7e1"},
|
||||
{"MULB CL", "MULB", []Operand{CL}, "f6e1"},
|
||||
{"DIVL CX", "DIVL", []Operand{CX}, "f7f1"},
|
||||
{"IDIVQ CX", "IDIVQ", []Operand{CX}, "48f7f9"},
|
||||
{"MULW CX", "MULW", []Operand{CX}, "66f7e1"},
|
||||
{"BTQ AX,DX", "BTQ", []Operand{AX, DX}, "480fa3c2"},
|
||||
{"BTL AX,DX", "BTL", []Operand{AX, DX}, "0fa3c2"},
|
||||
{"BTW AX,DX", "BTW", []Operand{AX, DX}, "660fa3c2"},
|
||||
{"BTQ $3,BX", "BTQ", []Operand{Imm(3), BX}, "480fbae303"},
|
||||
{"BTQ $3,(AX)", "BTQ", []Operand{Imm(3), Ptr(AX, 0, 8)}, "480fba2003"},
|
||||
{"BTSQ $5,BX", "BTSQ", []Operand{Imm(5), BX}, "480fbaeb05"},
|
||||
{"BTCQ AX,BX", "BTCQ", []Operand{AX, BX}, "480fbbc3"},
|
||||
{"BTRQ $7,BX", "BTRQ", []Operand{Imm(7), BX}, "480fbaf307"},
|
||||
}
|
||||
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 := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
// The bit-test immediate is an unsigned bit index with the negative
|
||||
// spelling accepted, the shuffle convention: BTQ $300 must be rejected.
|
||||
if _, err := Encode("BTQ", Imm(300), AX); err == nil {
|
||||
t.Errorf("BTQ $300: expected an error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAtomicSystemGroundTruth pins the exchange/compare-exchange/accumulate
|
||||
// family, the string primitives, the flag and system instructions, the MXCSR
|
||||
// pair, the scalar float-to-int conversions and the x87 FMOVD byte for byte
|
||||
// against go tool asm (see testdata/verify/atomics_amd64.s and
|
||||
// testdata/verify/system_amd64.s).
|
||||
func TestAtomicSystemGroundTruth(t *testing.T) {
|
||||
r8 := Reg{idx: 8, size: 8}
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
want string
|
||||
}{
|
||||
{"XCHGQ AX,BX", "XCHGQ", []Operand{AX, BX}, "4893"},
|
||||
{"XCHGQ BX,AX", "XCHGQ", []Operand{BX, AX}, "4893"},
|
||||
{"XCHGL AX,BX", "XCHGL", []Operand{AX, BX}, "93"},
|
||||
{"XCHGB AL,BL", "XCHGB", []Operand{AL, BL}, "86c3"},
|
||||
{"XCHGW AX,BX", "XCHGW", []Operand{AX, BX}, "6693"},
|
||||
{"XCHGQ R8,R9", "XCHGQ", []Operand{r8, Reg{idx: 9, size: 8}}, "4d87c1"},
|
||||
{"XCHGQ BX,(AX)", "XCHGQ", []Operand{BX, Ptr(AX, 0, 8)}, "488718"},
|
||||
{"XCHGQ (AX),BX", "XCHGQ", []Operand{Ptr(AX, 0, 8), BX}, "488718"},
|
||||
{"XCHGQ AX,(BX)", "XCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "488703"},
|
||||
{"CMPXCHGL AX,BX", "CMPXCHGL", []Operand{AX, BX}, "0fb1c3"},
|
||||
{"CMPXCHGQ AX,(BX)", "CMPXCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fb103"},
|
||||
{"CMPXCHGB AL,(BX)", "CMPXCHGB", []Operand{AL, Ptr(BX, 0, 1)}, "0fb003"},
|
||||
{"CMPXCHGW AX,BX", "CMPXCHGW", []Operand{AX, BX}, "660fb1c3"},
|
||||
{"XADDL AX,BX", "XADDL", []Operand{AX, BX}, "0fc1c3"},
|
||||
{"XADDQ AX,(BX)", "XADDQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fc103"},
|
||||
{"XADDB AL,(BX)", "XADDB", []Operand{AL, Ptr(BX, 0, 1)}, "0fc003"},
|
||||
{"XADDW AX,BX", "XADDW", []Operand{AX, BX}, "660fc1c3"},
|
||||
{"ADCXL AX,CX", "ADCXL", []Operand{AX, CX}, "660f38f6c8"},
|
||||
{"ADCXQ AX,CX", "ADCXQ", []Operand{AX, CX}, "66480f38f6c8"},
|
||||
{"ADOXL AX,CX", "ADOXL", []Operand{AX, CX}, "f30f38f6c8"},
|
||||
{"ADOXQ AX,CX", "ADOXQ", []Operand{AX, CX}, "f3480f38f6c8"},
|
||||
{"CRC32B AX,CX", "CRC32B", []Operand{AX, CX}, "f20f38f0c8"},
|
||||
{"CRC32W AX,CX", "CRC32W", []Operand{AX, CX}, "66f20f38f1c8"},
|
||||
{"CRC32L AX,CX", "CRC32L", []Operand{AX, CX}, "f20f38f1c8"},
|
||||
{"CRC32Q AX,CX", "CRC32Q", []Operand{AX, CX}, "f2480f38f1c8"},
|
||||
{"CRC32L (AX),CX", "CRC32L", []Operand{Ptr(AX, 0, 4), CX}, "f20f38f108"},
|
||||
{"MOVSQ", "MOVSQ", []Operand{}, "48a5"},
|
||||
{"MOVSL", "MOVSL", []Operand{}, "a5"},
|
||||
{"MOVSB", "MOVSB", []Operand{}, "a4"},
|
||||
{"MOVSW", "MOVSW", []Operand{}, "66a5"},
|
||||
{"STOSB", "STOSB", []Operand{}, "aa"},
|
||||
{"STOSQ", "STOSQ", []Operand{}, "48ab"},
|
||||
{"STOSL", "STOSL", []Operand{}, "ab"},
|
||||
{"STOSW", "STOSW", []Operand{}, "66ab"},
|
||||
{"CLD", "CLD", []Operand{}, "fc"},
|
||||
{"STD", "STD", []Operand{}, "fd"},
|
||||
{"POPFQ", "POPFQ", []Operand{}, "9d"},
|
||||
{"PUSHFQ", "PUSHFQ", []Operand{}, "9c"},
|
||||
{"CPUID", "CPUID", []Operand{}, "0fa2"},
|
||||
{"RDTSC", "RDTSC", []Operand{}, "0f31"},
|
||||
{"RDTSCP", "RDTSCP", []Operand{}, "0f01f9"},
|
||||
{"SYSCALL", "SYSCALL", []Operand{}, "0f05"},
|
||||
{"XGETBV", "XGETBV", []Operand{}, "0f01d0"},
|
||||
{"PAUSE", "PAUSE", []Operand{}, "f390"},
|
||||
{"LFENCE", "LFENCE", []Operand{}, "0faee8"},
|
||||
{"MFENCE", "MFENCE", []Operand{}, "0faef0"},
|
||||
{"SFENCE", "SFENCE", []Operand{}, "0faef8"},
|
||||
{"UNDEF", "UNDEF", []Operand{}, "0f0b"},
|
||||
{"INT $3", "INT", []Operand{Imm(3)}, "cd03"},
|
||||
{"LDMXCSR (AX)", "LDMXCSR", []Operand{Ptr(AX, 0, 4)}, "0fae10"},
|
||||
{"STMXCSR (AX)", "STMXCSR", []Operand{Ptr(AX, 0, 4)}, "0fae18"},
|
||||
{"CVTSD2SL X0,AX", "CVTSD2SL", []Operand{vreg(t, "X0"), AX}, "f20f2dc0"},
|
||||
{"CVTTSD2SQ X0,AX", "CVTTSD2SQ", []Operand{vreg(t, "X0"), AX}, "f2480f2cc0"},
|
||||
{"CVTTSD2SL X0,AX", "CVTTSD2SL", []Operand{vreg(t, "X0"), AX}, "f20f2cc0"},
|
||||
{"CVTSS2SQ X0,AX", "CVTSS2SQ", []Operand{vreg(t, "X0"), AX}, "f3480f2dc0"},
|
||||
{"FMOVD (AX),F0", "FMOVD", []Operand{Ptr(AX, 0, 8), vreg(t, "F0")}, "dd00"},
|
||||
{"FMOVD F0,(AX)", "FMOVD", []Operand{vreg(t, "F0"), Ptr(AX, 0, 8)}, "dd10"},
|
||||
{"FMOVD F0,F1", "FMOVD", []Operand{vreg(t, "F0"), vreg(t, "F1")}, "ddd1"},
|
||||
{"MOVD AX,X0", "MOVD", []Operand{AX, vreg(t, "X0")}, "66480f6ec0"},
|
||||
{"MOVD X0,AX", "MOVD", []Operand{vreg(t, "X0"), AX}, "66480f7ec0"},
|
||||
{"MOVD X0,X1", "MOVD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "f30f7ec8"},
|
||||
{"MOVD (AX),X0", "MOVD", []Operand{Ptr(AX, 0, 8), vreg(t, "X0")}, "f30f7e00"},
|
||||
{"MOVD X0,(AX)", "MOVD", []Operand{vreg(t, "X0"), Ptr(AX, 0, 8)}, "660fd600"},
|
||||
}
|
||||
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 := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
// LDMXCSR/STMXCSR take a memory operand only.
|
||||
if _, err := Encode("LDMXCSR", AX); err == nil {
|
||||
t.Errorf("LDMXCSR AX: expected an error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSEGapsGroundTruth pins the legacy SSE gap families: the scalar
|
||||
// compare and square root, the Plan 9 packed spellings, the imm8-controlled
|
||||
// shuffles, the lane extracts and inserts, the packed integer shifts and the
|
||||
// AES/SHA round instructions, byte for byte against go tool asm (see
|
||||
// testdata/verify/crypto_amd64.s and testdata/verify/sse_amd64.s).
|
||||
func TestSSEGapsGroundTruth(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
want string
|
||||
}{
|
||||
{"ANDNPD X0,X1", "ANDNPD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f55c8"},
|
||||
{"ANDNPS X0,X1", "ANDNPS", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f55c8"},
|
||||
{"COMISD X0,X1", "COMISD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f2fc8"},
|
||||
{"SQRTSD X0,X1", "SQRTSD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "f20f51c8"},
|
||||
{"PSHUFL $3,X0,X1", "PSHUFL", []Operand{Imm(3), vreg(t, "X0"), vreg(t, "X1")}, "660f70c803"},
|
||||
{"PALIGNR $2,X0,X1", "PALIGNR", []Operand{Imm(2), vreg(t, "X0"), vreg(t, "X1")}, "660f3a0fc802"},
|
||||
{"PBLENDW $3,X0,X1", "PBLENDW", []Operand{Imm(3), vreg(t, "X0"), vreg(t, "X1")}, "660f3a0ec803"},
|
||||
{"PCMPESTRI $1,X0,X1", "PCMPESTRI", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "X1")}, "660f3a61c801"},
|
||||
{"PCLMULQDQ $0,X0,X1", "PCLMULQDQ", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "660f3a44c800"},
|
||||
{"PCLMULQDQ $0,(AX),X1", "PCLMULQDQ", []Operand{Imm(0), Ptr(AX, 0, 16), vreg(t, "X1")}, "660f3a440800"},
|
||||
{"PEXTRB $1,X0,AX", "PEXTRB", []Operand{Imm(1), vreg(t, "X0"), AX}, "660f3a14c001"},
|
||||
{"PEXTRD $1,X0,AX", "PEXTRD", []Operand{Imm(1), vreg(t, "X0"), AX}, "660f3a16c001"},
|
||||
{"PEXTRQ $1,X0,AX", "PEXTRQ", []Operand{Imm(1), vreg(t, "X0"), AX}, "66480f3a16c001"},
|
||||
{"PEXTRW $1,X0,AX", "PEXTRW", []Operand{Imm(1), vreg(t, "X0"), AX}, "660fc5c001"},
|
||||
{"PEXTRW $1,X0,(AX)", "PEXTRW", []Operand{Imm(1), vreg(t, "X0"), Ptr(AX, 0, 2)}, "660f3a150001"},
|
||||
{"PINSRB $1,AX,X0", "PINSRB", []Operand{Imm(1), AX, vreg(t, "X0")}, "660f3a20c001"},
|
||||
{"PINSRD $1,AX,X0", "PINSRD", []Operand{Imm(1), AX, vreg(t, "X0")}, "660f3a22c001"},
|
||||
{"PINSRQ $1,AX,X0", "PINSRQ", []Operand{Imm(1), AX, vreg(t, "X0")}, "66480f3a22c001"},
|
||||
{"PINSRW $1,AX,X0", "PINSRW", []Operand{Imm(1), AX, vreg(t, "X0")}, "660fc4c001"},
|
||||
{"PINSRW $1,(AX),X0", "PINSRW", []Operand{Imm(1), Ptr(AX, 0, 2), vreg(t, "X0")}, "660fc40001"},
|
||||
{"PSLLL $2,X0", "PSLLL", []Operand{Imm(2), vreg(t, "X0")}, "660f72f002"},
|
||||
{"PSRAL $2,X0", "PSRAL", []Operand{Imm(2), vreg(t, "X0")}, "660f72e002"},
|
||||
{"PSRLL $2,X0", "PSRLL", []Operand{Imm(2), vreg(t, "X0")}, "660f72d002"},
|
||||
{"PSRLQ $2,X0", "PSRLQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73d002"},
|
||||
{"PSLLQ $2,X0", "PSLLQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73f002"},
|
||||
{"PSLLW $2,X0", "PSLLW", []Operand{Imm(2), vreg(t, "X0")}, "660f71f002"},
|
||||
{"PSRLW $2,X0", "PSRLW", []Operand{Imm(2), vreg(t, "X0")}, "660f71d002"},
|
||||
{"PSRAW $2,X0", "PSRAW", []Operand{Imm(2), vreg(t, "X0")}, "660f71e002"},
|
||||
{"PSLLDQ $2,X0", "PSLLDQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73f802"},
|
||||
{"PSRLDQ $2,X0", "PSRLDQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73d802"},
|
||||
{"PSLLL X0,X1", "PSLLL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ff2c8"},
|
||||
{"PSRLQ X0,X1", "PSRLQ", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660fd3c8"},
|
||||
{"PSLLL (AX),X1", "PSLLL", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660ff208"},
|
||||
{"PSUBL X0,X1", "PSUBL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ffac8"},
|
||||
{"PADDL X0,X1", "PADDL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ffec8"},
|
||||
{"PCMPEQL X0,X1", "PCMPEQL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f76c8"},
|
||||
{"PUNPCKLBW X0,X1", "PUNPCKLBW", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f60c8"},
|
||||
{"MOVOA X0,X1", "MOVOA", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f6fc8"},
|
||||
{"MOVOA (AX),X1", "MOVOA", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660f6f08"},
|
||||
{"MOVOA X0,(AX)", "MOVOA", []Operand{vreg(t, "X0"), Ptr(AX, 0, 16)}, "660f7f00"},
|
||||
{"AESIMC X0,X1", "AESIMC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dbc8"},
|
||||
{"AESIMC (AX),X1", "AESIMC", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660f38db08"},
|
||||
{"AESENC X0,X1", "AESENC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dcc8"},
|
||||
{"AESENCLAST X0,X1", "AESENCLAST", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38ddc8"},
|
||||
{"AESDEC X0,X1", "AESDEC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dec8"},
|
||||
{"AESDECLAST X0,X1", "AESDECLAST", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dfc8"},
|
||||
{"AESKEYGENASSIST $0,X0,X1", "AESKEYGENASSIST", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "660f3adfc800"},
|
||||
{"SHA1MSG1 X0,X1", "SHA1MSG1", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38c9c8"},
|
||||
{"SHA1MSG2 X0,X1", "SHA1MSG2", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38cac8"},
|
||||
{"SHA1NEXTE X0,X1", "SHA1NEXTE", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38c8c8"},
|
||||
{"SHA1RNDS4 $0,X0,X1", "SHA1RNDS4", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "0f3accc800"},
|
||||
{"SHA256MSG1 X0,X1", "SHA256MSG1", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38ccc8"},
|
||||
{"SHA256MSG2 X0,X1", "SHA256MSG2", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38cdc8"},
|
||||
{"SHA256RNDS2 X0,X1,X2", "SHA256RNDS2", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "0f38cbd1"},
|
||||
}
|
||||
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 := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
// SHA256RNDS2's first operand must be the literal X0.
|
||||
if _, err := Encode("SHA256RNDS2", vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")); err == nil {
|
||||
t.Errorf("SHA256RNDS2 X1,...: expected an error, got none")
|
||||
}
|
||||
// PSLLDQ has no variable-count form.
|
||||
if _, err := Encode("PSLLDQ", vreg(t, "X0"), vreg(t, "X1")); err == nil {
|
||||
t.Errorf("PSLLDQ X0,X1: expected an error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSEBinGroundTruth checks the legacy packed/scalar binary family
|
||||
// byte for byte (no prefix / 66 / F2 / F3 variants).
|
||||
func TestSSEBinGroundTruth(t *testing.T) {
|
||||
@@ -627,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+676
-41
@@ -5,6 +5,7 @@ package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -91,7 +92,7 @@ var evexTable = map[string]evexSpec{
|
||||
"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;
|
||||
// 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,
|
||||
// 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.
|
||||
"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).
|
||||
"VINSERTF32X4": {3, 0x18, 0, 1, -1, vexNDS3Imm, [3]int{0, 16, 32}},
|
||||
@@ -182,8 +183,17 @@ var evexTable = map[string]evexSpec{
|
||||
// EVEX.66.0F38, permutes (NDS form).
|
||||
"VPERMB": {2, 0x8D, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMW": {2, 0x8D, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMI2B": {2, 0x75, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMI2D": {2, 0x76, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMI2Q": {2, 0x76, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
// EVEX.66.0F38, population count (reg=dst, rm=src; W selects byte/word
|
||||
// against dword/qword).
|
||||
"VPOPCNTB": {2, 0x54, 0, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||
"VPOPCNTD": {2, 0x55, 0, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||
"VPOPCNTQ": {2, 0x55, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||
// EVEX.66.0F.W1, the qword spelling of the packed OR (VPORQ has no VEX
|
||||
// form in the Go assembler: it always encodes through EVEX).
|
||||
"VPORQ": {1, 0xEB, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMT2D": {2, 0x7E, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMT2Q": {2, 0x7E, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPERMT2PD": {2, 0x7F, 1, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
@@ -193,7 +203,7 @@ var evexTable = map[string]evexSpec{
|
||||
"VPMULHUW": {1, 0xE4, 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}},
|
||||
"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}},
|
||||
"VPACKUSWB": {1, 0x67, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
"VPACKSSDW": {1, 0x6B, 0, 1, -1, vexNDS3, [3]int{16, 32, 64}},
|
||||
@@ -315,7 +325,7 @@ var evexTable = map[string]evexSpec{
|
||||
"VCVTPD2UQQ": {1, 0x79, 1, 1, -1, vexRM, [3]int{16, 32, 64}},
|
||||
"VCVTPS2QQ": {1, 0x7B, 0, 1, -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).
|
||||
"VCVTPH2PS": {2, 0x13, 0, 1, -1, vexRM, [3]int{8, 16, 32}},
|
||||
// EVEX.66.0F3A, half-precision convert back ($imm, src, dst: reg=src,
|
||||
@@ -485,6 +495,331 @@ var evexTable = map[string]evexSpec{
|
||||
// destination (VPMOVDW dword→word, VPMOVQD qword→dword).
|
||||
"VPMOVDW": {2, 0x33, 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
|
||||
@@ -520,32 +855,42 @@ type evexMoveSpec struct {
|
||||
n [3]int
|
||||
vecOK bool // the non-memory operand may be a vector register
|
||||
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.
|
||||
var evexMoveTable = map[string]evexMoveSpec{
|
||||
// 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.
|
||||
"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
|
||||
// F2 prefix, dword/qword moves F3; the element size only changes the tuple
|
||||
// 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
|
||||
// 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.
|
||||
"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.
|
||||
"VMOVAPS": {1, 0, 0x28, 0x29, 0, [3]int{16, 32, 64}, true, false},
|
||||
"VMOVAPD": {1, 1, 0x28, 0x29, 1, [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, false, false},
|
||||
// EVEX.128/256/512.66.0F, aligned integer moves.
|
||||
"VMOVDQA32": {1, 1, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false},
|
||||
"VMOVDQA64": {1, 1, 0x6F, 0x7F, 1, [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, false, false},
|
||||
// EVEX.128.F3.0F.W0, scalar single move, memory operands (the
|
||||
// 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.
|
||||
@@ -556,8 +901,13 @@ func isEvex(mnemUpper string) bool {
|
||||
if _, ok := evexBcastTable[mnemUpper]; ok {
|
||||
return true
|
||||
}
|
||||
_, ok := evexMoveTable[mnemUpper]
|
||||
return ok
|
||||
if _, ok := evexMoveTable[mnemUpper]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := evexHptrTable[mnemUpper]; ok {
|
||||
return true
|
||||
}
|
||||
return isEvexQuad(mnemUpper)
|
||||
}
|
||||
|
||||
// evexRequired reports whether the operands force the EVEX encoding of a
|
||||
@@ -568,8 +918,21 @@ func evexRequired(upper string, ops []Operand) bool {
|
||||
_, inVex := vexTable[upper]
|
||||
_, inVexMove := vexMoveTable[upper]
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
if r, ok := op.(Reg); ok && (r.size == 64 || r.mask || (r.isVec() && r.idx >= 16)) {
|
||||
return true
|
||||
@@ -668,6 +1031,8 @@ var evexRound = map[string]bool{
|
||||
"VCVTTSD2USIL": true, "VCVTTSD2USIQ": true, "VCVTTSS2USIL": true, "VCVTTSS2USIQ": true,
|
||||
"VCVTSI2SDQ": true, "VCVTSI2SSL": true, "VCVTSI2SSQ": 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
|
||||
@@ -732,6 +1097,22 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
||||
return e.encodeEvexRM(spec, ops, 0, sfx)
|
||||
}
|
||||
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 (sfx.rounding >= 0 || sfx.sae) && !evexRound[mnemUpper] {
|
||||
return fmt.Errorf("%s: rounding/SAE is not supported for this instruction", mnemUpper)
|
||||
@@ -743,6 +1124,27 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
||||
}
|
||||
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() {
|
||||
return fmt.Errorf("%s: the instruction does not take rounding/SAE/broadcast suffixes", mnemUpper)
|
||||
}
|
||||
@@ -802,6 +1204,12 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("unsupported instruction %q for ZMM/K operands", mnemUpper)
|
||||
}
|
||||
@@ -820,6 +1228,8 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
||||
return e.encodeEvexNDS3Imm(spec, ops, mask, sfx)
|
||||
case vexExtract:
|
||||
return e.encodeEvexExtract(spec, ops, mask, sfx)
|
||||
case vexExtractGPR:
|
||||
return e.encodeEvexExtractGPR(spec, ops, mask, sfx)
|
||||
case vexRMSrcLen:
|
||||
return e.encodeEvexRMSrcLen(spec, ops, mask, sfx)
|
||||
}
|
||||
@@ -899,6 +1309,11 @@ func (e *enc) encodeEvexImmRM(spec evexSpec, ops []Operand, mask int, sfx evexSu
|
||||
if dstReg.mask {
|
||||
if r, ok := src.(Reg); ok && r.isVec() {
|
||||
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() {
|
||||
ll = r.vecLenBit()
|
||||
@@ -925,9 +1340,11 @@ func (e *enc) encodeEvexShiftImm(spec evexSpec, ops []Operand, mask int, sfx eve
|
||||
if !ok {
|
||||
return fmt.Errorf("shift count must be an immediate")
|
||||
}
|
||||
srcReg, ok := src.(Reg)
|
||||
if !ok || !srcReg.isVec() {
|
||||
return fmt.Errorf("shift source must be a vector register")
|
||||
// The count source is a vector register or memory; the length the L'L
|
||||
// field and the disp8×N multiplier follow is the destination's either
|
||||
// way.
|
||||
if !vecOrMem(src) {
|
||||
return fmt.Errorf("shift source must be a vector register or memory")
|
||||
}
|
||||
dstReg, ok := dst.(Reg)
|
||||
if !ok || !dstReg.isVec() {
|
||||
@@ -937,7 +1354,7 @@ func (e *enc) encodeEvexShiftImm(spec evexSpec, ops []Operand, mask int, sfx eve
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
e.out = append(e.out, immByte)
|
||||
@@ -1009,10 +1426,90 @@ func (e *enc) encodeEvexExtract(spec evexSpec, ops []Operand, mask int, sfx evex
|
||||
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
|
||||
// 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 {
|
||||
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 {
|
||||
return fmt.Errorf("EVEX move expects 2 operands, got %d", len(ops))
|
||||
}
|
||||
@@ -1033,12 +1530,12 @@ func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask i
|
||||
}
|
||||
reg, rm = srcReg, dst
|
||||
case srcIsVec:
|
||||
if !memOperand(dst) {
|
||||
if !validEvexMoveOther(ms, dst) {
|
||||
return fmt.Errorf("%s: invalid destination operand", mnem)
|
||||
}
|
||||
reg, rm = srcReg, dst
|
||||
case dstIsVec:
|
||||
if !memOperand(src) {
|
||||
if !validEvexMoveOther(ms, src) {
|
||||
return fmt.Errorf("%s: invalid source operand", mnem)
|
||||
}
|
||||
op = ms.load
|
||||
@@ -1132,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")
|
||||
}
|
||||
spec := evexSpec{mapSel: bs.mapSel, w: bs.w, pp: 1, opdigit: -1}
|
||||
switch src.(type) {
|
||||
switch r := src.(type) {
|
||||
case Mem, sbMem:
|
||||
spec.opcode = bs.opMem
|
||||
spec.n = [3]int{bs.n, bs.n, bs.n}
|
||||
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
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("broadcast source must be a register or memory")
|
||||
}
|
||||
@@ -1340,6 +1845,111 @@ func isScatter(upper string) bool {
|
||||
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
|
||||
// register) and returns it with the vector length the index selects, the
|
||||
// EVEX L'L field follows the index register, not the data register.
|
||||
@@ -1361,7 +1971,9 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("%s expects 3 operands (vsib, K, dst), got %d", upper, len(ops))
|
||||
}
|
||||
@@ -1373,6 +1985,9 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
||||
if !ok || !dst.isVec() {
|
||||
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}}
|
||||
return e.emitEvexFields(evex, ll, dst.idx, -1, vsib, mask, sfx)
|
||||
}
|
||||
@@ -1384,7 +1999,7 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
||||
if !ok || !maskReg.isVec() {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -1392,12 +2007,16 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
||||
if !ok || !dst.isVec() {
|
||||
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}
|
||||
rBit := 0
|
||||
if dst.idx >= 8 {
|
||||
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,
|
||||
@@ -1422,6 +2041,11 @@ func (e *enc) encodeScatter(upper string, ss gatherSpec, ops []Operand, sfx evex
|
||||
if err != nil {
|
||||
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}}
|
||||
return e.emitEvexFields(evex, ll, src.idx, -1, vsib, mask, sfx)
|
||||
}
|
||||
@@ -1432,21 +2056,25 @@ func (e *enc) encodeScatter(upper string, ss gatherSpec, ops []Operand, sfx evex
|
||||
var evexKOperand = map[string]bool{
|
||||
"VPMOVM2B": true, "VPMOVM2W": true, "VPMOVM2D": true, "VPMOVM2Q": 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
|
||||
// direction, kk (k/mem → K is 90, k → k uses the same), kmem (K → mem),
|
||||
// gprk (GPR/mem → K), kgpr (K → GPR), and the GPR forms carry a mandatory
|
||||
// prefix and W for the wider widths.
|
||||
// direction, kk (k → k), kmem (k → mem), gprk (GPR/mem → k) and kgpr
|
||||
// (k → GPR). Each direction group carries its own mandatory prefix and W:
|
||||
// the k-destination/source forms share one pair, the GPR forms another.
|
||||
type kmovSpec struct {
|
||||
kk, kmem, gprk, kgpr byte
|
||||
gprPP int
|
||||
w int
|
||||
kPP, kW int // prefix and VEX.W for the k forms
|
||||
gprPP, gprW int // prefix and VEX.W for the GPR forms
|
||||
}
|
||||
|
||||
var kmovTable = map[string]kmovSpec{
|
||||
"KMOVW": {0x90, 0x91, 0x92, 0x93, 0, 0},
|
||||
"KMOVQ": {0x90, 0x91, 0x92, 0x93, 3, 1},
|
||||
"KMOVW": {0x90, 0x91, 0x92, 0x93, 0, 0, 0, 0},
|
||||
"KMOVB": {0x90, 0x91, 0x92, 0x93, 1, 0, 1, 0},
|
||||
"KMOVD": {0x90, 0x91, 0x92, 0x93, 1, 1, 3, 0},
|
||||
"KMOVQ": {0x90, 0x91, 0x92, 0x93, 0, 1, 3, 1},
|
||||
}
|
||||
|
||||
// encodeKmov encodes a KMOV width, selecting the opcode by direction.
|
||||
@@ -1460,14 +2088,14 @@ func (e *enc) encodeKmov(upper string, ops []Operand) error {
|
||||
dstReg, dstIsReg := dst.(Reg)
|
||||
srcK := srcIsReg && srcReg.mask
|
||||
dstK := dstIsReg && dstReg.mask
|
||||
spec := vexSpec{mapSel: 1, w: ks.w, pp: 0, opdigit: -1}
|
||||
switch {
|
||||
case srcK && dstK:
|
||||
spec.opcode = ks.kk // k ← k: reg = dst, rm = src
|
||||
// k ← k: reg = dst, rm = src.
|
||||
spec := vexSpec{mapSel: 1, opcode: ks.kk, w: ks.kW, pp: ks.kPP, opdigit: -1}
|
||||
return e.emitVexFields(spec, 0, dstReg.idx&7, 0, 15, src)
|
||||
case srcK && dstIsReg:
|
||||
spec.opcode = ks.kgpr // GPR ← k: reg = dst, rm = src
|
||||
spec.pp = ks.gprPP
|
||||
// GPR ← k: reg = dst, rm = src.
|
||||
spec := vexSpec{mapSel: 1, opcode: ks.kgpr, w: ks.gprW, pp: ks.gprPP, opdigit: -1}
|
||||
rBit := 0
|
||||
if dstReg.idx >= 8 {
|
||||
rBit = 1
|
||||
@@ -1477,11 +2105,17 @@ func (e *enc) encodeKmov(upper string, ops []Operand) error {
|
||||
if _, ok := dst.(Mem); !ok {
|
||||
return fmt.Errorf("%s: invalid destination operand", upper)
|
||||
}
|
||||
spec.opcode = ks.kmem // mem ← k: reg = src, rm = dst
|
||||
// mem ← k: reg = src, rm = dst.
|
||||
spec := vexSpec{mapSel: 1, opcode: ks.kmem, w: ks.kW, pp: ks.kPP, opdigit: -1}
|
||||
return e.emitVexFields(spec, 0, srcReg.idx&7, 0, 15, dst)
|
||||
case dstK:
|
||||
spec.opcode = ks.gprk // k ← GPR/mem: reg = dst, rm = src
|
||||
spec.pp = ks.gprPP
|
||||
// k ← GPR: reg = dst, rm = src. A memory source shares the k ← k
|
||||
// opcode and prefix group (the ykmovb layout the Go assembler uses).
|
||||
opcode, w, pp := ks.gprk, ks.gprW, ks.gprPP
|
||||
if memOperand(src) {
|
||||
opcode, w, pp = ks.kk, ks.kW, ks.kPP
|
||||
}
|
||||
spec := vexSpec{mapSel: 1, opcode: opcode, w: w, pp: pp, opdigit: -1}
|
||||
return e.emitVexFields(spec, 0, dstReg.idx&7, 0, 15, src)
|
||||
}
|
||||
return fmt.Errorf("%s requires a K register operand", upper)
|
||||
@@ -1524,6 +2158,7 @@ var kOpsTable = map[string]kOpSpec{
|
||||
"KXORD": {1, 0x47, 1, 1, 1, vexNDS3},
|
||||
"KXORQ": {1, 0x47, 1, 0, 1, vexNDS3},
|
||||
"KUNPCKBW": {1, 0x4B, 0, 1, 1, vexNDS3},
|
||||
"KUNPCKWD": {1, 0x4B, 0, 0, 1, vexNDS3},
|
||||
"KUNPCKDQ": {1, 0x4B, 1, 0, 1, vexNDS3},
|
||||
"KADDB": {1, 0x4A, 0, 1, 1, vexNDS3},
|
||||
"KADDW": {1, 0x4A, 0, 0, 1, vexNDS3},
|
||||
|
||||
@@ -38,6 +38,15 @@ func TestEvexGroundTruth(t *testing.T) {
|
||||
{"VADDPD Z11,Z10,Z10", "VADDPD", []Operand{vreg(t, "Z11"), vreg(t, "Z10"), vreg(t, "Z10")}, "6251ad4858d3"},
|
||||
{"VMULPD Z13,Z12,Z12", "VMULPD", []Operand{vreg(t, "Z13"), vreg(t, "Z12"), vreg(t, "Z12")}, "62519d4859e5"},
|
||||
{"VFMADD231PD Z14,Z12,Z10", "VFMADD231PD", []Operand{vreg(t, "Z14"), vreg(t, "Z12"), vreg(t, "Z10")}, "62529d48b8d6"},
|
||||
// The qword OR spelling always encodes through EVEX.
|
||||
{"VPORQ Y0,Y1,Y2", "VPORQ", []Operand{vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}, "62f1f528ebd0"},
|
||||
{"VPORQ X0,X1,X2", "VPORQ", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "62f1f508ebd0"},
|
||||
// Byte permute and population count.
|
||||
{"VPERMI2B X0,X1,X2", "VPERMI2B", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "62f2750875d0"},
|
||||
{"VPOPCNTB X0,X1", "VPOPCNTB", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "62f27d0854c8"},
|
||||
{"VPOPCNTD X0,X1", "VPOPCNTD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "62f27d0855c8"},
|
||||
{"VPOPCNTD Y0,Y1", "VPOPCNTD", []Operand{vreg(t, "Y0"), vreg(t, "Y1")}, "62f27d2855c8"},
|
||||
{"VPOPCNTQ X0,X1", "VPOPCNTQ", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "62f2fd0855c8"},
|
||||
// Align (NDS + imm8).
|
||||
{"VALIGND $12,Z12,Z0,Z1", "VALIGND", []Operand{Imm(12), vreg(t, "Z12"), vreg(t, "Z0"), vreg(t, "Z1")}, "62d37d4803cc0c"},
|
||||
{"VALIGND $15,Z9,Z0,Z1", "VALIGND", []Operand{Imm(15), vreg(t, "Z9"), vreg(t, "Z0"), vreg(t, "Z1")}, "62d37d4803c90f"},
|
||||
@@ -52,6 +61,16 @@ func TestEvexGroundTruth(t *testing.T) {
|
||||
{"KMOVW K1,CX", "KMOVW", []Operand{vreg(t, "K1"), CX}, "c5f893c9"},
|
||||
{"KMOVW K1,R12", "KMOVW", []Operand{vreg(t, "K1"), vreg(t, "R12")}, "c57893e1"},
|
||||
{"KTESTW K1,K1", "KTESTW", []Operand{vreg(t, "K1"), vreg(t, "K1")}, "c5f899c9"},
|
||||
{"KMOVB K1,K2", "KMOVB", []Operand{vreg(t, "K1"), vreg(t, "K2")}, "c5f990d1"},
|
||||
{"KMOVB AX,K1", "KMOVB", []Operand{AX, vreg(t, "K1")}, "c5f992c8"},
|
||||
{"KMOVB K1,AX", "KMOVB", []Operand{vreg(t, "K1"), AX}, "c5f993c1"},
|
||||
{"KMOVB K1,(AX)", "KMOVB", []Operand{vreg(t, "K1"), Ptr(AX, 0, 1)}, "c5f99108"},
|
||||
{"KMOVD K1,K2", "KMOVD", []Operand{vreg(t, "K1"), vreg(t, "K2")}, "c4e1f990d1"},
|
||||
{"KMOVD AX,K1", "KMOVD", []Operand{AX, vreg(t, "K1")}, "c5fb92c8"},
|
||||
{"KMOVD K1,AX", "KMOVD", []Operand{vreg(t, "K1"), AX}, "c5fb93c1"},
|
||||
{"KMOVD K1,(AX)", "KMOVD", []Operand{vreg(t, "K1"), Ptr(AX, 0, 4)}, "c4e1f99108"},
|
||||
{"KMOVB (AX),K1", "KMOVB", []Operand{Ptr(AX, 0, 1), vreg(t, "K1")}, "c5f99008"},
|
||||
{"KMOVQ (AX),K1", "KMOVQ", []Operand{Ptr(AX, 0, 8), vreg(t, "K1")}, "c4e1f89008"},
|
||||
// Moves, incl. disp8×N (64 for a 512-bit operand).
|
||||
{"VMOVDQU32 (SI)(R15*4),Z3", "VMOVDQU32", []Operand{Idx(SI, vreg(t, "R15"), 4, 0, 64), vreg(t, "Z3")}, "62b17e486f1cbe"},
|
||||
{"VMOVDQU32 4(SI)(AX*1),Z4", "VMOVDQU32", []Operand{Idx(SI, AX, 1, 4, 64), vreg(t, "Z4")}, "62f17e486fa40604000000"},
|
||||
@@ -702,3 +721,216 @@ func hexCompact(b []byte) string {
|
||||
}
|
||||
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 (
|
||||
kindSTEXT = 1
|
||||
kindSRODATA = 3
|
||||
kindSNOPTRDATA = 5
|
||||
kindSDATA = 7
|
||||
kindSDWARFFCN = 14
|
||||
kindSDWARFLINES = 20
|
||||
@@ -305,9 +306,16 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
||||
if !d.Static {
|
||||
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)
|
||||
if d.Rodata {
|
||||
typ = kindSRODATA
|
||||
} else if d.Noptr {
|
||||
typ = kindSNOPTRDATA
|
||||
}
|
||||
flag := uint8(0)
|
||||
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[:]...)
|
||||
}
|
||||
}
|
||||
// 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).
|
||||
for _, ds := range dwarfRelocs {
|
||||
for _, r := range ds.relocs {
|
||||
|
||||
@@ -45,6 +45,9 @@ func TestReadRuntimeSymbols(t *testing.T) {
|
||||
// TestResolveExternalSymbols verifies end-to-end resolution of external
|
||||
// symbol references.
|
||||
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 {
|
||||
t.Skip("go toolchain not available")
|
||||
}
|
||||
|
||||
+143
-1
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"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
|
||||
@@ -514,6 +514,145 @@ func fieldAfter(line, flag string) string {
|
||||
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
|
||||
// GOOBJ whose code references a real external package symbol (runtime's
|
||||
// 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
|
||||
// stack check, which a hand-written caller has none of.
|
||||
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")
|
||||
if err != nil {
|
||||
t.Skip("no Go toolchain available")
|
||||
|
||||
@@ -50,6 +50,8 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
||||
return relocArm64Branch, 4
|
||||
case RelArm64LDST64:
|
||||
return relocArm64LDST64, 8
|
||||
case RelArm64TLSLE:
|
||||
return relocArm64TLSLE, 4
|
||||
default:
|
||||
return relocArm64Addr, 8
|
||||
}
|
||||
@@ -60,6 +62,7 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
||||
const (
|
||||
relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
+5
-1
@@ -30,6 +30,8 @@ func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
||||
return relocRISCVPcrelStype, 8
|
||||
case RelRISCVJal:
|
||||
return relocRISCVJal, 4
|
||||
case RelRISCVTLSLE:
|
||||
return relocRISCVTLSLE, 8
|
||||
default:
|
||||
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
|
||||
// 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 (
|
||||
relocRISCVJal = 59 // R_RISCV_JAL
|
||||
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
||||
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
||||
relocRISCVTLSLE = 65 // R_RISCV_TLS_LE
|
||||
)
|
||||
|
||||
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
||||
|
||||
+6
-6
@@ -9,8 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
for _, tt := range []struct {
|
||||
src string
|
||||
assemble func(*ast.File) (*Image, error)
|
||||
assemble func(*ast.File, ...AssembleOption) (*Image, error)
|
||||
}{
|
||||
{"g_amd64.s", AssembleFile},
|
||||
{"g_arm64.s", AssembleFileARM64},
|
||||
{"g_riscv64.s", AssembleFileRISCV},
|
||||
{"g_loong64.s", AssembleFileLOONG64},
|
||||
{"g_arm64.s", func(f *ast.File, _ ...AssembleOption) (*Image, error) { return AssembleFileARM64(f) }},
|
||||
{"g_riscv64.s", func(f *ast.File, _ ...AssembleOption) (*Image, error) { return AssembleFileRISCV(f) }},
|
||||
{"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")
|
||||
if len(errs) > 0 {
|
||||
|
||||
+1052
-43
File diff suppressed because it is too large.
Load diff
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
"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;
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
||||
path := "../../go-libraries/go-flac/avx2_amd64.s"
|
||||
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)
|
||||
if err != nil {
|
||||
@@ -86,7 +86,7 @@ func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
||||
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
||||
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
||||
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)
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// TestGOObjectLOONG64Structure checks the emitted loong64 object's blocks:
|
||||
|
||||
+307
-19
@@ -5,10 +5,11 @@ package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"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,
|
||||
@@ -97,12 +98,15 @@ const (
|
||||
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
||||
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
||||
RelRISCVJal // R_RISCV_JAL (J-type call)
|
||||
RelRISCVTLSLE // R_RISCV_TLS_LE (LUI + I-type local-exec pair)
|
||||
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
||||
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
||||
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
||||
RelArm64Branch // R_CALLARM64 (BL instruction)
|
||||
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
||||
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 {
|
||||
@@ -112,13 +116,17 @@ type Reloc struct {
|
||||
// Addend select the target: the symbol plus the byte offset. An
|
||||
// External relocation names a symbol no GLOBL in the file defines;
|
||||
// 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
|
||||
After int
|
||||
Name string
|
||||
Addend int64
|
||||
External bool
|
||||
Kind RelocKind
|
||||
Siz uint8
|
||||
}
|
||||
|
||||
// DataSymbol describes one GLOBL symbol laid out in the data section.
|
||||
@@ -128,8 +136,14 @@ type DataSymbol struct {
|
||||
Offset int // byte offset within Data
|
||||
Size int
|
||||
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
|
||||
// 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.
|
||||
@@ -139,6 +153,18 @@ func (img *Image) Bytes() []byte {
|
||||
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
|
||||
// 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
|
||||
@@ -146,7 +172,7 @@ func (img *Image) Bytes() []byte {
|
||||
// GLOBL defines is recorded as an external relocation (Externals) with its
|
||||
// displacement left zero, the object-file emitters resolve it at link
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -155,7 +181,18 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
||||
for _, d := range dataSyms {
|
||||
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}
|
||||
for _, o := range opts {
|
||||
o(link)
|
||||
}
|
||||
poolSeen := map[string]bool{}
|
||||
|
||||
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||
textOff := map[string]int{}
|
||||
@@ -169,7 +206,26 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
||||
if !ok {
|
||||
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 {
|
||||
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),
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
img.Externals = append(img.Externals, name)
|
||||
}
|
||||
@@ -264,6 +337,35 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
||||
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
|
||||
// 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
|
||||
@@ -273,6 +375,14 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
||||
if err != nil {
|
||||
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}
|
||||
for _, d := range f.Decls {
|
||||
@@ -280,10 +390,23 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
code, labels, relocs, lines, spadj, err := assembleRISCV(t)
|
||||
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t, tlsSyms)
|
||||
if err != nil {
|
||||
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{
|
||||
Name: t.Name.Name,
|
||||
Pkg: t.Name.Pkg,
|
||||
@@ -327,6 +450,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
||||
Size: d.size,
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
}
|
||||
@@ -399,6 +523,7 @@ func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
||||
Size: d.size,
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
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 {
|
||||
img.Externals = append(img.Externals, name)
|
||||
}
|
||||
@@ -443,16 +585,91 @@ type dataSym struct {
|
||||
size int
|
||||
static bool
|
||||
rodata bool
|
||||
noptr 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
|
||||
// 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
|
||||
// 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) {
|
||||
if err := checkDuplicateDecls(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := map[string]int{}
|
||||
var syms []dataSym
|
||||
total := 0
|
||||
for _, d := range f.Decls {
|
||||
gd, ok := d.(*ast.Globl)
|
||||
if !ok {
|
||||
@@ -462,12 +679,19 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
continue
|
||||
}
|
||||
name := gd.Name.Name
|
||||
if _, dup := index[name]; dup {
|
||||
return nil, fmt.Errorf("duplicate GLOBL %q", name)
|
||||
}
|
||||
size := 0
|
||||
if gd.Size != nil && gd.Size.Imm.HasVal {
|
||||
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)
|
||||
ds := dataSym{
|
||||
@@ -481,12 +705,14 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
switch f {
|
||||
case "RODATA":
|
||||
ds.rodata = true
|
||||
case "NOPTR":
|
||||
ds.noptr = true
|
||||
case "DUPOK":
|
||||
ds.dupok = true
|
||||
default:
|
||||
// Legacy numeric flag constants (runtime/textflag.h):
|
||||
// DUPOK is 2, RODATA is 8; combinations arrive as one
|
||||
// number (e.g. 10 = RODATA|DUPOK).
|
||||
// DUPOK is 2, RODATA is 8, NOPTR is 16; combinations arrive
|
||||
// as one number (e.g. 10 = RODATA|DUPOK).
|
||||
if n, err := strconv.Atoi(f); err == nil {
|
||||
if n&2 != 0 {
|
||||
ds.dupok = true
|
||||
@@ -494,6 +720,9 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
if n&8 != 0 {
|
||||
ds.rodata = true
|
||||
}
|
||||
if n&16 != 0 {
|
||||
ds.noptr = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,20 +740,79 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
||||
}
|
||||
if dd.Value == nil || !dd.Value.Imm.HasVal {
|
||||
return nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name)
|
||||
if dd.Value == nil {
|
||||
return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
|
||||
}
|
||||
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
|
||||
buf := syms[i].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))
|
||||
}
|
||||
// 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
|
||||
if dd.Value.Imm.Neg {
|
||||
v = -v
|
||||
|
||||
+409
-12
@@ -4,12 +4,115 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"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
|
||||
// and the data section; and that the RIP-relative displacements of static
|
||||
// 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) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src string
|
||||
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",
|
||||
`
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1299
-57
File diff suppressed because it is too large.
Load diff
+667
-7
@@ -30,7 +30,10 @@ package asm
|
||||
// of the immediate and register fields), mirroring the toolchain's OP_*
|
||||
// helpers, so each l64* function only ORs its fields in.
|
||||
|
||||
import "maps"
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// loong64RegNum returns the 5-bit register number for a LoongArch register
|
||||
// name: R0-R31 (integer), F0-F31 (floating point), FCC0-FCC7 (condition
|
||||
@@ -103,7 +106,12 @@ func loong64RegNum(name string) int {
|
||||
case "R31", "S8":
|
||||
return 31
|
||||
}
|
||||
// F0-F31, FCC0-FCC7, FCSR0-FCSR31.
|
||||
// F0-F31, FCC0-FCC7, FCSR0-FCSR31. The LSX/LASX vector banks (V0-V31,
|
||||
// X0-X31) are deliberately NOT accepted here: they are a separate
|
||||
// register class, and the toolchain rejects V/X names wherever an
|
||||
// integer or FP register is expected (GOARCH=loong64 go tool asm reports
|
||||
// "unrecognized instruction" for `BEQZ X0`). Vector operands are
|
||||
// resolved only through loong64VecRegNum.
|
||||
if len(name) >= 4 && name[:4] == "FCSR" {
|
||||
return loong64RegSpecial(name[4:], 31)
|
||||
}
|
||||
@@ -148,6 +156,19 @@ func loong64RegSpecial(digits string, max int) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// loong64VecRegNum resolves an LSX/LASX vector register name (V0-V31 or
|
||||
// X0-X31) to its 5-bit number, or -1. The vector banks are a register class
|
||||
// of their own: the toolchain accepts them only in the vector operands of the
|
||||
// LSX/LASX instructions (GOARCH=loong64 go tool asm assembles `VADDV V0, V1,
|
||||
// V2` and `XVADDV X0, X1, X2`, and rejects `VADDV R4, R5, R6`), so the V/X
|
||||
// spellings never reach the integer/FP resolver.
|
||||
func loong64VecRegNum(name string) int {
|
||||
if len(name) < 2 || (name[0] != 'V' && name[0] != 'X') {
|
||||
return -1
|
||||
}
|
||||
return loong64RegSpecial(name[1:], 31)
|
||||
}
|
||||
|
||||
// ---- format helpers ----
|
||||
|
||||
// l64rrr encodes a 3R instruction: op | rk<<10 | rj<<5 | rd.
|
||||
@@ -236,6 +257,37 @@ func l64WordsLE(ws ...uint32) []byte {
|
||||
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 ----
|
||||
|
||||
type l64Format uint8
|
||||
@@ -247,7 +299,7 @@ const (
|
||||
l64Firr14 // 2RI14 (ldptr/stptr)
|
||||
l64Firr16 // 2RI16 (addu16i.d)
|
||||
l64Fir20 // 2RI20 (lu12i.w, lu32i.d, pcalau12i, pcaddu12i)
|
||||
l64Frrrr // 4R (fmadd/fmsub/fnmadd/fnmsub)
|
||||
l64Frrrr // 4R (fmadd/fmsub/fnmadd/fnmsub, fsel)
|
||||
l64Firir // bstrins/bstrpick
|
||||
l64Firrr // alsl
|
||||
l64Fi15 // syscall/break/dbar
|
||||
@@ -255,6 +307,11 @@ const (
|
||||
l64Frdtime // rdtime (rd at bits [9:5], rj at bits [4:0])
|
||||
l64Fshift // 2RI12 with a 5/6-bit shift immediate
|
||||
l64Fpreld // preld (2RI12 + 5-bit hint)
|
||||
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
|
||||
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
|
||||
@@ -277,17 +334,90 @@ type l64DualEnc struct {
|
||||
var l64DualTable = map[string]l64DualEnc{}
|
||||
|
||||
// l64InstrTable maps LoongArch mnemonics (as the Go assembler spells them)
|
||||
// to their encoding. SIMD (LSX/LASX: V*/XV*) instructions are not covered
|
||||
// yet; the base integer, memory and floating-point ISA is complete.
|
||||
// to their encoding.
|
||||
var l64InstrTable = map[string]l64Enc{}
|
||||
|
||||
// l64Vec3Enc pairs a vector opcode with its register bank: false = LSX
|
||||
// (V0-V31), true = LASX (X0-X31). The toolchain accepts one bank per
|
||||
// spelling: GOARCH=loong64 go tool asm assembles `VADDV V1, V2, V3` and
|
||||
// `XVADDV X1, X2, X3`, and rejects the crossed spellings.
|
||||
type l64Vec3Enc struct {
|
||||
op uint32
|
||||
lasx bool
|
||||
}
|
||||
|
||||
// l64VecImmEnc carries the immediate-form encoding of a vector mnemonic:
|
||||
// the opcode, the bank, the accepted immediate range, the bias the toolchain
|
||||
// adds (vsrai.b encodes imm+8) and the mask of the encoded field (vseqi.b
|
||||
// keeps a 5-bit two's-complement value, vseqi.d a 7-bit one).
|
||||
type l64VecImmEnc struct {
|
||||
op uint32
|
||||
lasx bool
|
||||
min, max int
|
||||
bias int
|
||||
mask int
|
||||
}
|
||||
|
||||
// l64VecBank marks the LSX/LASX mnemonics and records which register bank
|
||||
// each accepts; presence in the map routes the mnemonic through the vector
|
||||
// dispatcher rather than the integer/FP formats.
|
||||
var l64VecBank = map[string]bool{}
|
||||
|
||||
// l64VecImmInfo mirrors l64VecImmTable for the dispatcher.
|
||||
var l64VecImmInfo = map[string]l64VecImmEnc{}
|
||||
|
||||
// l64Vec2R marks the two-operand vector mnemonics (INSTR vj, vd, such as
|
||||
// vpcnt.v).
|
||||
var l64Vec2R = map[string]bool{}
|
||||
|
||||
// l64Vec4R marks the four-operand vector mnemonics (INSTR va, vk, vj, vd,
|
||||
// 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 {
|
||||
ld, st, ldx, stx uint32 // plain and indexed load/store
|
||||
replB, replH, replW, replD uint32 // vldrepl: load and replicate element
|
||||
pickS, pickU uint32 // vpickve2gr.{,u} element extract
|
||||
ins uint32 // vinsgr2vr element insert
|
||||
dup uint32 // vreplgr2vr duplicate (width in [11:10])
|
||||
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{
|
||||
false: { // VMOVQ, the LSX (V) bank
|
||||
ld: 0x5800 << 15, st: 0x5880 << 15, ldx: 0x7080 << 15, stx: 0x7088 << 15,
|
||||
replB: 0x6100 << 15, replH: 0x6080 << 15, replW: 0x6040 << 15, replD: 0x6020 << 15,
|
||||
pickS: 0xE5DF << 15, pickU: 0xE5E7 << 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
|
||||
ld: 0x5900 << 15, st: 0x5980 << 15, ldx: 0x7090 << 15, stx: 0x7098 << 15,
|
||||
replB: 0x6500 << 15, replH: 0x6480 << 15, replW: 0x6440 << 15, replD: 0x6420 << 15,
|
||||
pickS: 0xEDDF << 15, pickU: 0xEDE7 << 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,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 3R, integer.
|
||||
rrr := map[string]uint32{
|
||||
"ADD": 0x20 << 15, "ADDW": 0x20 << 15, "ADDV": 0x21 << 15, "ADDVU": 0x21 << 15,
|
||||
"SUB": 0x22 << 15, "SUBW": 0x22 << 15, "SUBV": 0x23 << 15, "SUBVU": 0x23 << 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,
|
||||
"ORN": 0x2c << 15, "ANDN": 0x2d << 15,
|
||||
"SLL": 0x2e << 15, "SRL": 0x2f << 15, "SRA": 0x30 << 15,
|
||||
@@ -360,6 +490,18 @@ func init() {
|
||||
"FTINTRZVF": 0x46a9 << 10, "FTINTRZVD": 0x46aa << 10,
|
||||
"FTINTRNEWF": 0x46b1 << 10, "FTINTRNEWD": 0x46b2 << 10,
|
||||
"FTINTRNEVF": 0x46b9 << 10, "FTINTRNEVD": 0x46ba << 10,
|
||||
// LSX: convert a 64-bit integer lane to a double float. The operand
|
||||
// bank is the FP registers (the toolchain spells it `FFINTDV F0, F1`),
|
||||
// so the entry stays on the 2R integer/FP format.
|
||||
"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 {
|
||||
l64InstrTable[m] = l64Enc{format: l64Frr, op: op}
|
||||
@@ -369,6 +511,20 @@ func init() {
|
||||
l64InstrTable["RDTIMEHW"] = l64Enc{format: l64Frdtime, op: 0x19 << 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),
|
||||
// selected by the operand kind; the shift mnemonics pair the 3R form
|
||||
// with a 5/6-bit shift immediate.
|
||||
@@ -416,12 +572,14 @@ func init() {
|
||||
// LUI is the Plan 9 spelling of lu12i.w.
|
||||
l64InstrTable["LUI"] = l64Enc{format: l64Fir20, op: 0x0a << 25}
|
||||
|
||||
// 4R, fused multiply-add.
|
||||
// 4R, fused multiply-add, and FSEL (fsel.d: the first operand is a FCC
|
||||
// condition flag, the layout matches the 4R shape).
|
||||
rrrr := map[string]uint32{
|
||||
"FMADDF": 0x81 << 20, "FMADDD": 0x82 << 20,
|
||||
"FMSUBF": 0x85 << 20, "FMSUBD": 0x86 << 20,
|
||||
"FNMADDF": 0x89 << 20, "FNMADDD": 0x8a << 20,
|
||||
"FNMSUBF": 0x8d << 20, "FNMSUBD": 0x8e << 20,
|
||||
"FSEL": 0x340 << 18,
|
||||
}
|
||||
for m, op := range rrrr {
|
||||
l64InstrTable[m] = l64Enc{format: l64Frrrr, op: op}
|
||||
@@ -455,6 +613,10 @@ func init() {
|
||||
l64InstrTable["PRELD"] = l64Enc{format: l64Fpreld, op: 0x0ab << 22}
|
||||
|
||||
// Atomics, 3R with the AM field order (rk=value, rj=address, rd=result).
|
||||
// The toolchain's form is three operands, `AMADDW rk, (rj), rd`
|
||||
// (cmd/asm/internal/asm/testdata/loong64enc1.s and
|
||||
// internal/runtime/atomic/atomic_loong64.s); the two-register spelling
|
||||
// is rejected by the oracle.
|
||||
am := map[string]uint32{
|
||||
"AMSWAPB": 0x070B8 << 15, "AMSWAPH": 0x070B9 << 15,
|
||||
"AMSWAPW": 0x070C0 << 15, "AMSWAPV": 0x070C1 << 15,
|
||||
@@ -472,10 +634,508 @@ func init() {
|
||||
"AMSWAPDBW": 0x070D2 << 15, "AMSWAPDBV": 0x070D3 << 15,
|
||||
"AMCASDBB": 0x070B4 << 15, "AMCASDBH": 0x070B5 << 15,
|
||||
"AMCASDBW": 0x070B6 << 15, "AMCASDBV": 0x070B7 << 15,
|
||||
// The _dbar (acquire/release) add, and, or variants: opcodes read off
|
||||
// `go tool objdump` of `AMADDDBW R14, (R13), R12` and friends.
|
||||
"AMADDDBW": 0x070D4 << 15, "AMADDDBV": 0x070D5 << 15,
|
||||
"AMANDDBW": 0x070D6 << 15, "AMANDDBV": 0x070D7 << 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 {
|
||||
l64InstrTable[m] = l64Enc{format: l64Fam, op: op}
|
||||
}
|
||||
|
||||
// ---- LSX/LASX (V*/XV*) ----
|
||||
// Every opcode below was read off `go tool objdump` of a GOARCH=loong64
|
||||
// `go tool asm` kernel (the toolchain's own loong64enc1.s cross-checks
|
||||
// most of them), not assumed from the LoongArch manual.
|
||||
|
||||
// Three vector registers: INSTR vk, vj, vd (or INSTR vk, vd with
|
||||
// vj = vd). l64Vec3Enc.lasx selects the register bank the toolchain
|
||||
// accepts: LSX spellings take V0-V31, LASX spellings X0-X31.
|
||||
vec3 := map[string]l64Vec3Enc{
|
||||
"VADDW": {0xE016 << 15, false}, "VADDV": {0xE017 << 15, false},
|
||||
"VANDV": {0xE24C << 15, false}, "VXORV": {0xE24E << 15, false},
|
||||
"VSEQB": {0xE000 << 15, false}, "VSEQV": {0xE003 << 15, false},
|
||||
"VSRAB": {0xE1D8 << 15, false}, "VROTRW": {0xE1DE << 15, false},
|
||||
"XVADDV": {0xE817 << 15, true},
|
||||
"XVANDV": {0xEA4C << 15, true}, "XVXORV": {0xEA4E << 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 {
|
||||
l64InstrTable[m] = l64Enc{format: l64Fvvv, op: e.op}
|
||||
l64VecBank[m] = e.lasx
|
||||
}
|
||||
|
||||
// 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
|
||||
// stores the raw 8-bit constant, vsrari.b stores imm+8 (lane-width
|
||||
// bias), the si5 compares store 5-bit two's-complement values and vseqi.d
|
||||
// a 7-bit field the toolchain range-checks down to si5.
|
||||
// The mnemonics that also have a register form (the shifts, the bit
|
||||
// clear/set/rev families, VSEQ and the logic immediates) keep their
|
||||
// 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{
|
||||
"VANDB": {0xE7A0 << 15, false, 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},
|
||||
"XVSEQB": {0xED00 << 15, true, -16, 15, 0, 0x1F},
|
||||
// vseqi.h/w accept the same si5 window as vseqi.b; vseqi.d carries a
|
||||
// 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},
|
||||
"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},
|
||||
"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 {
|
||||
l64VecImmInfo[m] = e
|
||||
l64VecBank[m] = e.lasx
|
||||
}
|
||||
|
||||
// Vector-to-condition flag: INSTR vj, FCCn (vsetnez.v, vsetanyeqz.*,
|
||||
// vsetallnez.*): the sub-op rides in the rk field.
|
||||
vecCf := map[string]uint32{
|
||||
"VSETNEV": 0xE539<<15 | 7<<10, "XVSETNEV": 0xED39<<15 | 7<<10,
|
||||
"VSETANYEQB": 0xE539<<15 | 8<<10, "XVSETANYEQB": 0xED39<<15 | 8<<10,
|
||||
"VSETANYEQV": 0xE539<<15 | 11<<10, "XVSETANYEQV": 0xED39<<15 | 11<<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 {
|
||||
l64InstrTable[m] = l64Enc{format: l64Fvcf, op: op}
|
||||
l64VecBank[m] = strings.HasPrefix(m, "XV")
|
||||
}
|
||||
|
||||
// Lane popcount and the two-operand vector FP/unary spellings: INSTR vj,
|
||||
// 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{
|
||||
"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 {
|
||||
l64InstrTable[m] = l64Enc{format: l64Frr, op: e.op}
|
||||
l64VecBank[m] = e.lasx
|
||||
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
|
||||
|
||||
+819
-2
@@ -8,8 +8,8 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
||||
@@ -263,6 +263,20 @@ func TestLOONG64_regNames(t *testing.T) {
|
||||
t.Errorf("loong64RegNum(%q) = %d, want %d", name, got, want)
|
||||
}
|
||||
}
|
||||
// The X/V spellings name the LSX/LASX vector banks, a register class of
|
||||
// their own: the oracle (GOARCH=loong64 go tool asm) rejects `BEQZ X0`
|
||||
// with "unrecognized instruction" while assembling `VADDV V0, V1, V2`
|
||||
// and `XVADDV X0, X1, X2`, so loong64RegNum stays strict and the vector
|
||||
// operands resolve through loong64VecRegNum only.
|
||||
vecCases := map[string]int{
|
||||
"V0": 0, "V31": 31, "X0": 0, "X31": 31,
|
||||
"R4": -1, "F0": -1, "FCC0": -1, "V32": -1, "X32": -1, "V": -1, "X": -1,
|
||||
}
|
||||
for name, want := range vecCases {
|
||||
if got := loong64VecRegNum(name); got != want {
|
||||
t.Errorf("loong64VecRegNum(%q) = %d, want %d", name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLOONG64_bytesEqualGroundTruth(t *testing.T) {
|
||||
@@ -328,3 +342,806 @@ TEXT ·f(SB), NOSPLIT, $0-0
|
||||
0x4C000020, // jirl r0, r1, 0 (RET)
|
||||
)
|
||||
}
|
||||
|
||||
// TestLOONG64_vector pins the LSX/LASX slice against words read off
|
||||
// GOARCH=loong64 go tool asm (cross-checked against the toolchain's own
|
||||
// loong64enc1.s): the three-register forms, the immediate forms with their
|
||||
// biases, the vector-to-condition forms, lane popcount, the FP conversion,
|
||||
// FSEL and the VMOVQ move family.
|
||||
func TestLOONG64_vector(t *testing.T) {
|
||||
t.Run("three-register and immediate forms", func(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·v(SB), NOSPLIT, $0
|
||||
VADDV V1, V2, V3
|
||||
VADDW V1, V2, V3
|
||||
VADDV V2, V1
|
||||
VANDV V1, V2
|
||||
VXORV V1, V2, V3
|
||||
VSEQB V1, V2, V3
|
||||
VSEQV V1, V2, V3
|
||||
VSRAB V1, V2, V3
|
||||
VROTRW V1, V2, V3
|
||||
VANDB $0, V2, V3
|
||||
VANDB $255, V2
|
||||
VSEQB $3, V2, V3
|
||||
VSEQV $15, V2, V3
|
||||
VSEQV $-15, V2, V3
|
||||
VSRAB $7, V1, V2
|
||||
VROTRW $16, V1, V2
|
||||
VPCNTV V1, V2
|
||||
XVADDV X1, X2, X3
|
||||
XVXORV X1, X2, X3
|
||||
XVSEQB X1, X2, X3
|
||||
XVPCNTV X1, X2
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x700B8443, // vadd.v v3, v2, v1
|
||||
0x700B0443, // vadd.w
|
||||
0x700B8821, // vadd.v v1, v1, v2 (two-operand form)
|
||||
0x71260442, // vand.v v2, v2, v1
|
||||
0x71270443, // vxor.v
|
||||
0x70000443, // vseq.b
|
||||
0x70018443, // vseq.d
|
||||
0x70EC0443, // vsra.b
|
||||
0x70EF0443, // vrotr.w
|
||||
0x73D00043, // vandi.b v3, v2, 0
|
||||
0x73D3FC42, // vandi.b v2, v2, 255 (two-operand form)
|
||||
0x72800C43, // vseqi.b v3, v2, 3
|
||||
0x7281BC43, // vseqi.d v3, v2, 15
|
||||
0x7281C443, // vseqi.d v3, v2, -15 (7-bit two's complement)
|
||||
0x73343C22, // vsrai.b v2, v1, 7 (encoded as 7+8)
|
||||
0x72A0C022, // vrotri.w v2, v1, 16
|
||||
0x729C2C22, // vpcnt.d v2, v1
|
||||
0x740B8443, // xvadd.d x3, x2, x1
|
||||
0x75270443, // xvxor.d
|
||||
0x74000443, // xvseq.b
|
||||
0x769C2C22, // xvpcnt.d x2, x1
|
||||
0x4C000020,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("vector-to-condition", func(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·v(SB), NOSPLIT, $0
|
||||
VSETNEV V1, FCC0
|
||||
VSETANYEQB V1, FCC0
|
||||
VSETANYEQV V2, FCC0
|
||||
VSETALLNEV V0, FCC0
|
||||
XVSETNEV X1, FCC0
|
||||
XVSETALLNEV X1, FCC0
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x729C9C20, // vsetnez.d fcc0, v1
|
||||
0x729CA020, // vsetanyeqz.b
|
||||
0x729CAC40, // vsetanyeqz.d
|
||||
0x729CBC00, // vsetallnez.d
|
||||
0x769C9C20, // xvsetnez.d
|
||||
0x769CBC20, // xvsetallnez.d
|
||||
0x4C000020,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("FP convert and FSEL", func(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·v(SB), NOSPLIT, $0
|
||||
FFINTDV F0, F1
|
||||
FSEL FCC0, F3, F4, F3
|
||||
FSEL FCC1, F1, F2
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x011D2801, // ffint.d.v f1, f0
|
||||
0x0D000C83, // fsel f3, f4, f3, fcc0
|
||||
0x0D008442, // fsel f2, f2, f1, fcc1
|
||||
0x4C000020,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("VMOVQ move family", func(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·v(SB), NOSPLIT, $0
|
||||
VMOVQ V1, V9
|
||||
VMOVQ (R4), V2
|
||||
VMOVQ 16(R4), V2
|
||||
VMOVQ V0, (R4)
|
||||
VMOVQ V0, 32(R4)
|
||||
VMOVQ (R4)(R7), V3
|
||||
VMOVQ V3, (R4)(R7)
|
||||
VMOVQ R6, V0.B16
|
||||
VMOVQ R6, V12.W4
|
||||
VMOVQ (R4), V4.W4
|
||||
XVMOVQ X3, X7
|
||||
XVMOVQ (R4), X2
|
||||
XVMOVQ X0, (R4)
|
||||
XVMOVQ (R4)(R7), X4
|
||||
XVMOVQ X0, (R4)(R7)
|
||||
XVMOVQ R6, X0.B32
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x732D0029, // vori.b v9, v1, 0 (register move)
|
||||
0x2C000082, // vld v2, r4, 0
|
||||
0x2C004082, // vld v2, r4, 16
|
||||
0x2C400080, // vst v0, r4, 0
|
||||
0x2C408080, // vst v0, r4, 32
|
||||
0x38401C83, // vldx v3, r4, r7
|
||||
0x38441C83, // vstx v3, r4, r7
|
||||
0x729F00C0, // vreplgr2vr.b v0, r6
|
||||
0x729F08CC, // vreplgr2vr.w v12, r6
|
||||
0x30200084, // vldrepl.w v4, r4, 0
|
||||
0x772D0067, // xvori.b x7, x3, 0
|
||||
0x2C800082, // xvld x2, r4, 0
|
||||
0x2CC00080, // xvst x0, r4, 0
|
||||
0x38481C84, // xvldx x4, r4, r7
|
||||
0x384C1C80, // xvstx x0, r4, r7
|
||||
0x769F00C0, // xvreplgr2vr.b x0, r6
|
||||
0x4C000020,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("element extract and insert", func(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·v(SB), NOSPLIT, $0
|
||||
VMOVQ V0.V[0], R10
|
||||
VMOVQ V6.V[1], R8
|
||||
VMOVQ R9, V1.V[0]
|
||||
XVMOVQ X0.V[0], R10
|
||||
XVMOVQ X5.W[7], R7
|
||||
XVMOVQ R4, X7.V[3]
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x72EFF00A, // vpickve2gr.d r10, v0, 0
|
||||
0x72EFF4C8, // vpickve2gr.d r8, v6, 1
|
||||
0x72EBF121, // vinsgr2vr.d v1, r9, 0
|
||||
0x76EFE00A, // xvpickve2gr.d r10, x0, 0
|
||||
0x76EFDCA7, // xvpickve2gr.w r7, x5, 7
|
||||
0x76EBEC87, // xvinsgr2vr.d x7, r4, 3
|
||||
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
|
||||
// the vector slice; each shape is rejected by the oracle as well
|
||||
// (GOARCH=loong64 go tool asm).
|
||||
func TestLOONG64_vectorErrors(t *testing.T) {
|
||||
cases := []string{
|
||||
// Integer registers in vector positions.
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VADDV R4, R5, R6
|
||||
RET
|
||||
`,
|
||||
// Crossed banks: LSX spellings take V, LASX spellings X.
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VADDV X1, X2, X3
|
||||
RET
|
||||
`,
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
XVADDV V1, V2, V3
|
||||
RET
|
||||
`,
|
||||
// The LASX bank has no .b/.h element forms.
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
XVMOVQ R4, X2.B[0]
|
||||
RET
|
||||
`,
|
||||
// Immediate ranges.
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VANDB $256, V2
|
||||
RET
|
||||
`,
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VSEQB $16, V2, V3
|
||||
RET
|
||||
`,
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VROTRW $32, V1, V2
|
||||
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.
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
VSETNEV V1, V2
|
||||
RET
|
||||
`,
|
||||
}
|
||||
for i, src := range cases {
|
||||
fn := firstTextLOONG64(t, src)
|
||||
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||
t.Errorf("case %d: expected an error, got none", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLOONG64_dbarAtomics pins the _dbar (acquire/release) AMO variants.
|
||||
// The oracle words come from GOARCH=loong64 go tool objdump of kernels
|
||||
// assembled with go tool asm, and match the toolchain's loong64enc1.s.
|
||||
func TestLOONG64_dbarAtomics(t *testing.T) {
|
||||
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||
TEXT ·atoms(SB), NOSPLIT, $0
|
||||
AMADDDBW R14, (R13), R12
|
||||
AMADDDBV R14, (R13), R12
|
||||
AMANDDBW R5, (R4), R6
|
||||
AMANDDBV R5, (R4), R6
|
||||
AMORDBW R5, (R4), R0
|
||||
AMORDBV R5, (R4), R6
|
||||
AMSWAPDBW R5, (R4), R6
|
||||
AMCASDBV R6, (R4), R5
|
||||
RET
|
||||
`)
|
||||
code := assembleLOONG64Helper(t, fn)
|
||||
wantWords(t, code,
|
||||
0x386A39AC, // amadd_db.w r12, r13, r14
|
||||
0x386AB9AC, // amadd_db.d
|
||||
0x386B1486, // amand_db.w r6, r4, r5
|
||||
0x386B9486, // amand_db.d
|
||||
0x386C1480, // amor_db.w r0, r4, r5
|
||||
0x386C9486, // amor_db.d
|
||||
0x38691486, // amswap_db.w
|
||||
0x385B9885, // amcas_db.w
|
||||
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 (
|
||||
"strings"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
)
|
||||
|
||||
// Loong64 frame mapping, matching the Go toolchain's loong64 backend.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// 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
|
||||
// RDTIME, plus the MOVV FP→GP move.
|
||||
func TestLOONG64_atomics(t *testing.T) {
|
||||
@@ -232,7 +324,10 @@ DATA ·table+0(SB)/8, $42
|
||||
}
|
||||
|
||||
// TestLOONG64_errors checks the encoder's error paths: undefined labels,
|
||||
// invalid register operands and operand-count mismatches.
|
||||
// invalid register operands and operand-count mismatches. The X0 and
|
||||
// AMADDW cases follow the oracle: GOARCH=loong64 go tool asm rejects
|
||||
// `BEQZ X0` (the X bank is not an integer register) and the two-register
|
||||
// `AMADDW R4, R5` (the AM* family is strictly `val, (addr), result`).
|
||||
func TestLOONG64_errors(t *testing.T) {
|
||||
cases := []string{
|
||||
`TEXT ·e(SB), NOSPLIT, $0
|
||||
|
||||
@@ -6,7 +6,7 @@ package asm
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative
|
||||
|
||||
+142
@@ -3,6 +3,8 @@
|
||||
|
||||
package asm
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Operand is an instruction operand: a Reg, a Mem reference or an Imm value.
|
||||
type Operand interface {
|
||||
isOperand()
|
||||
@@ -14,6 +16,51 @@ type Imm int64
|
||||
|
||||
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).
|
||||
type Mem struct {
|
||||
Base Reg
|
||||
@@ -23,6 +70,7 @@ type Mem struct {
|
||||
Size int // operand width in bytes
|
||||
HasBase bool
|
||||
HasIndex bool
|
||||
Seg byte // segment override prefix (0x64 FS, 0x65 GS); 0 = none
|
||||
}
|
||||
|
||||
func (Mem) isOperand() {}
|
||||
@@ -48,3 +96,97 @@ type sbMem struct {
|
||||
}
|
||||
|
||||
func (sbMem) isOperand() {}
|
||||
|
||||
// isX86Mem reports whether the operand is an amd64 memory reference: a base
|
||||
// or indexed Mem, or an SB-relative sbMem. Encoders that gate on "memory in
|
||||
// this position" must accept both; the r/m emitters distinguish the two
|
||||
// themselves.
|
||||
func isX86Mem(o Operand) bool {
|
||||
switch o.(type) {
|
||||
case Mem, sbMem:
|
||||
return true
|
||||
default:
|
||||
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
|
||||
}
|
||||
+35
-1
@@ -17,12 +17,28 @@ import "strings"
|
||||
// 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
|
||||
// those indices but require one. The mask flag marks the AVX-512 opmask
|
||||
// registers K0-K7.
|
||||
// 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 {
|
||||
idx int
|
||||
size int // informational width implied by the name; the mnemonic decides
|
||||
high bool // AH/CH/DH/BH
|
||||
mask bool // K0-K7 opmask 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).
|
||||
@@ -144,6 +160,24 @@ func buildRegByName() map[string]Reg {
|
||||
for i := 0; i <= 7; i++ {
|
||||
m["K"+itoa(i)] = Reg{idx: i, size: 8, mask: true}
|
||||
}
|
||||
// x87 stack: F0..F7.
|
||||
for i := 0; i <= 7; i++ {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4406
-218
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)
|
||||
}
|
||||
}
|
||||
+860
-159
File diff suppressed because it is too large.
Load diff
Loaded 100 of 373 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user