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 |
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 ./...
|
|
||||||
+26
-116
@@ -1,20 +1,28 @@
|
|||||||
# Release, Go binaries. Runs on version tags (v1.2.3) pushed to main.
|
# Release, Go binaries. Runs on version tags (v1.2.3) pushed to main.
|
||||||
#
|
#
|
||||||
# The module sits at the repository root: the toolchain records a version only for a root
|
# No gate runs at the tag: the tagged tree was tested on every push to development,
|
||||||
# module, measured on go1.27.1, so a build of a module in a subdirectory reports (devel)
|
# the full suite is the suite workflow's business, and race never runs in CI at all.
|
||||||
# even at its own <module>/vX.Y.Z tag and this workflow's smoke test can never pass for
|
# This pipeline publishes and nothing else. The version contract has no injection
|
||||||
# it. A Go repository is one module at the root.
|
# step: the toolchain records the tag into the binary's build information, so the
|
||||||
|
# build simply has to happen at the tag, which the trigger guarantees.
|
||||||
#
|
#
|
||||||
# The version contract these steps implement: nothing is injected. The toolchain records
|
# The module sits at the repository root: the toolchain records a version only for a
|
||||||
# the tag into the binary's build information, so the build simply has to happen at the
|
# root module, measured on go1.27.1, so a build of a module in a subdirectory reports
|
||||||
# tag, which the trigger guarantees.
|
# (devel) even at its own <module>/vX.Y.Z tag and this workflow's smoke test can never
|
||||||
|
# pass for it. A Go repository is one module at the root.
|
||||||
#
|
#
|
||||||
# The gates run in their own job, once, before the matrix, minus the race detector: race
|
# The matrix carries the platforms the project ships: Linux on amd64, arm64,
|
||||||
# never runs on a push path or a tag, and the local gate raced this tree before the tag
|
# loong64 and riscv64, and FreeBSD on amd64 and arm64. Go cross-compiles
|
||||||
# was cut. Putting the gates inside the matrix would run the whole suite once per target
|
# FreeBSD natively and the tree builds under CGO_ENABLED=0, which the
|
||||||
# on the box that also hosts the forge. Each job validates the tag for itself rather than
|
# suite's cross-build gates prove for the whole module; the ptrace suite
|
||||||
# passing a value between jobs, so no workflow feature has to be trusted for the version
|
# itself still needs a FreeBSD machine, so nothing FreeBSD runs here.
|
||||||
# to reach the file name.
|
# Nothing is installed: the fedora job image carries git, perl and node
|
||||||
|
# (verified on the runner, 2026-10-04).
|
||||||
|
#
|
||||||
|
# Each job validates the tag for itself rather than passing a value between jobs, so
|
||||||
|
# no workflow feature has to be trusted for the version to reach the file name. Every
|
||||||
|
# step is one command, and the scripted steps are Perl with builtins only: Perl drives
|
||||||
|
# curl through a list, so no argument is ever word-split, globbed or quoted wrong.
|
||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -22,111 +30,16 @@ on:
|
|||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# The box is shared with the forge, so parallelism is bounded on purpose. The gates job
|
# The box is shared with the forge, so parallelism is bounded on purpose.
|
||||||
# needs it most; the build jobs inherit it for their parallel compilation.
|
|
||||||
GOFLAGS: -p=1
|
GOFLAGS: -p=1
|
||||||
GOMAXPROCS: "2"
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gates:
|
|
||||||
runs-on: fedora
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
# Perl for the steps below. The install is a no-op where the package
|
|
||||||
# is already present.
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Validate the tag
|
|
||||||
env:
|
|
||||||
VERSION: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
my $v = $ENV{VERSION} // q{};
|
|
||||||
$v =~ m{^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$}
|
|
||||||
or die qq{ERROR: expected a semver tag like v1.2.3, got: $v\n};
|
|
||||||
print qq{tag $v\n};
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Security policy names this release
|
|
||||||
# The supported-versions table is the one part of SECURITY.md that
|
|
||||||
# carries a version, so it goes stale the moment a tag is cut. Fail
|
|
||||||
# here rather than publish a policy naming the previous release.
|
|
||||||
env:
|
|
||||||
VERSION: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
my $v = $ENV{VERSION} // q{};
|
|
||||||
(my $nv = $v) =~ s/^v//;
|
|
||||||
open(my $f, q{<}, q{SECURITY.md}) or die qq{SECURITY.md: $!\n};
|
|
||||||
local $/;
|
|
||||||
my $t = <$f>;
|
|
||||||
close $f;
|
|
||||||
$t =~ m{^\|\s*\Q$nv\E\s*\|\s*yes\s*\|}m
|
|
||||||
or die qq{ERROR: SECURITY.md does not name $nv as supported; update the table before releasing.\n};
|
|
||||||
print qq{SECURITY.md names $nv\n};
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: go build ./...
|
|
||||||
|
|
||||||
- name: Format
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
|
||||||
my @bad = <$g>;
|
|
||||||
close($g);
|
|
||||||
print @bad;
|
|
||||||
exit(@bad ? 1 : 0);
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Vet
|
|
||||||
run: go vet ./...
|
|
||||||
|
|
||||||
- name: Modernise
|
|
||||||
run: go fix -diff ./...
|
|
||||||
|
|
||||||
- name: Tests
|
|
||||||
# The same command as in test.yml, so the floor is the same number everywhere.
|
|
||||||
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
|
||||||
|
|
||||||
- name: Tests outside the coverage set
|
|
||||||
# The same command as in test.yml: the CLI's exit codes and manual-page guard,
|
|
||||||
# and the debugger's architecture-neutral units, run outside the floor.
|
|
||||||
run: go test -count=1 -timeout 10m ./cmd/... ./debug/...
|
|
||||||
|
|
||||||
- name: Coverage floor
|
|
||||||
run: |
|
|
||||||
perl -e '
|
|
||||||
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
|
||||||
my $total;
|
|
||||||
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
|
||||||
close($c);
|
|
||||||
die qq{no total line in coverage.out\n} unless defined $total;
|
|
||||||
printf qq{Total coverage: %s%%\n}, $total;
|
|
||||||
exit($total < 80 ? 1 : 0);
|
|
||||||
'
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 25
|
|
||||||
needs: gates
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Portable targets: amd64, arm64, loong64 and riscv64 on Linux, at the toolchain
|
|
||||||
# default level. No 32-bit, no wasm, no macOS, no Windows. FreeBSD stays out until
|
|
||||||
# verify/jit.go ports off syscall.Mprotect: the Go syscall package defines no
|
|
||||||
# Mprotect for freebsd, and verify/jit.go:50 calls it to drop the write bit from
|
|
||||||
# the JIT mapping, so every freebsd target fails to build with "undefined:
|
|
||||||
# syscall.Mprotect" (verified for amd64, arm64 and riscv64 on go1.27.1).
|
|
||||||
include:
|
include:
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
@@ -136,6 +49,10 @@ jobs:
|
|||||||
goarch: loong64
|
goarch: loong64
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: riscv64
|
goarch: riscv64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: amd64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -144,9 +61,6 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Validate the tag
|
- name: Validate the tag
|
||||||
id: version
|
id: version
|
||||||
env:
|
env:
|
||||||
@@ -209,7 +123,6 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 15
|
|
||||||
needs: build
|
needs: build
|
||||||
permissions:
|
permissions:
|
||||||
# contents: read is required for the checkout: a job that declares any
|
# contents: read is required for the checkout: a job that declares any
|
||||||
@@ -226,9 +139,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
- name: Extract the CHANGELOG section
|
- name: Extract the CHANGELOG section
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ gitea.ref_name }}
|
VERSION: ${{ gitea.ref_name }}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Suite, Go. Dispatched by hand, on development. Never a push gate.
|
||||||
|
#
|
||||||
|
# The complete gate set minus race: the build, the FreeBSD cross-builds the
|
||||||
|
# port's compile proof needs (amd64 and arm64, the same two the release
|
||||||
|
# matrix ships), both static gates, the full suite with every short-layer
|
||||||
|
# skip unskipped, and the coverage floor. It is the pipeline form of the
|
||||||
|
# local `just test`, for the moments when the tree must be proven end to end
|
||||||
|
# and nobody is at the keyboard.
|
||||||
|
#
|
||||||
|
# Race never runs in CI. It roughly doubles the time and the memory on a box shared
|
||||||
|
# with the forge, and the local `just gates` races the tree on the machine at the
|
||||||
|
# keyboard, which is where that gate belongs.
|
||||||
|
name: Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# One core: parallelism buys no speed here and costs memory the box does not have.
|
||||||
|
GOFLAGS: -p=1
|
||||||
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
suite:
|
||||||
|
runs-on: fedora
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
# The module is the source of truth for the version, so it cannot drift.
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build ./...
|
||||||
|
|
||||||
|
- name: FreeBSD build (amd64)
|
||||||
|
# The port is pure Go: the cross-build needs no C toolchain and the
|
||||||
|
# release matrix ships the same two binaries.
|
||||||
|
run: CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build ./...
|
||||||
|
|
||||||
|
- name: FreeBSD build (arm64)
|
||||||
|
run: CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build ./...
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
||||||
|
my @bad = <$g>;
|
||||||
|
close($g);
|
||||||
|
print @bad;
|
||||||
|
exit(@bad ? 1 : 0);
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Modernise
|
||||||
|
# Exits non-zero when it has something to rewrite, so it needs no output capture.
|
||||||
|
run: go fix -diff ./...
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
# The full suite over the logic packages (`packages` in the justfile), every
|
||||||
|
# short-layer skip lifted, with the coverage profile. No timeout: a run that
|
||||||
|
# does not finish is a defect to find.
|
||||||
|
run: go test -count=1 -timeout 0 -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
||||||
|
|
||||||
|
- name: Tests outside the coverage set
|
||||||
|
# The same second invocation as the local gate: the CLI's exit codes and
|
||||||
|
# manual-page guard, and the debugger's units, live ptrace sessions included.
|
||||||
|
# They run without a profile, because a thin main and a ptrace-bound package
|
||||||
|
# would drag the floor down rather than measure the product.
|
||||||
|
run: go test -count=1 -timeout 0 ./cmd/... ./debug/...
|
||||||
|
|
||||||
|
- name: Coverage floor
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
||||||
|
my $total;
|
||||||
|
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
||||||
|
close($c);
|
||||||
|
die qq{no total line in coverage.out\n} unless defined $total;
|
||||||
|
printf qq{Total coverage: %s%%\n}, $total;
|
||||||
|
exit($total < 80 ? 1 : 0);
|
||||||
|
'
|
||||||
+24
-46
@@ -1,17 +1,20 @@
|
|||||||
# Test, Go. Push and pull request to development. Never on main.
|
# Test, Go. Push and pull request to development. Never on main.
|
||||||
#
|
#
|
||||||
# The gates are the ones the justfile's `gates` recipe runs, minus race: the shared
|
# The push path owns a two-minute budget end to end, so it carries exactly the gates
|
||||||
# runner box cannot afford the race detector on every push, so it lives in race.yml.
|
# that fit it: the format check, go vet, the suite's short layer and the coverage
|
||||||
# The box is one core and 2 GB beside Gitea, so parallelism is bounded on purpose and
|
# floor. There is no build step (go test compiles what it runs) and the modernisation
|
||||||
# everything runs in one job. Extra jobs would duplicate the checkout, the Go setup and
|
# gate (`go fix -diff`) belongs to the suite workflow, which is dispatched by hand.
|
||||||
# the dependency download three times without buying any parallelism.
|
# Nothing is installed: the fedora job image carries git, perl and node (verified on
|
||||||
|
# the runner, 2026-10-04), and no pipeline ever installs gcc or runs the race detector.
|
||||||
#
|
#
|
||||||
# Every step is one command, so the step that fails is the gate that failed, and no shell
|
# The live ptrace sessions and the other deliberate-run categories skip under
|
||||||
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and
|
# testing.Short: they need the machine to themselves and belong to the suite workflow
|
||||||
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over
|
# and to the local `just test`, never to every push.
|
||||||
# on ash, and it is one language instead of two. The Perl uses builtins only, because
|
#
|
||||||
# Fedora packages the Perl modules separately and nothing beyond `perl` itself may be
|
# Every step is one command, so the step that fails is the gate that failed, and no
|
||||||
# assumed present.
|
# shell option has to be trusted for the run to stop. The scripted steps are Perl with
|
||||||
|
# builtins only: Fedora packages the Perl modules separately, so nothing beyond `perl`
|
||||||
|
# itself may be assumed present, and there is no bashism to trip over.
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -36,7 +39,6 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -46,16 +48,6 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install Perl
|
|
||||||
# The runner images are minimal and Perl is not guaranteed. The install is a
|
|
||||||
# no-op where it is already present; drop this step once verified on the box.
|
|
||||||
run: dnf install -y perl
|
|
||||||
|
|
||||||
# The steps follow the `gates` order of the justfile contract: build, format,
|
|
||||||
# vet, test. The vet gate is go vet and go fix -diff, two steps here.
|
|
||||||
- name: Build
|
|
||||||
run: go build ./...
|
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: |
|
run: |
|
||||||
perl -e '
|
perl -e '
|
||||||
@@ -69,33 +61,19 @@ jobs:
|
|||||||
- name: Vet
|
- name: Vet
|
||||||
run: go vet ./...
|
run: go vet ./...
|
||||||
|
|
||||||
- name: Modernise
|
|
||||||
# Exits non-zero when it has something to rewrite, so it needs no output capture.
|
|
||||||
run: go fix -diff ./...
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
# The suite must be fast: a push pipeline that cannot finish in a few minutes moves
|
# The pattern is `packages` in the project's justfile, so the floor is the
|
||||||
# its heavy part behind a dispatch. The inner timeout matches the job's, so a
|
# same number the local gate reports: the logic packages, since a thin cmd/
|
||||||
# hanging test reports its own goroutine dump rather than a silent job kill.
|
# and a ptrace-bound debug package would drag the total under it. No timeout
|
||||||
# The pattern is `packages` in the project's justfile: the logic packages, since a
|
# anywhere: `-timeout 0` disables go test's own ten-minute default, because a
|
||||||
# thin cmd/ would drag the total under the floor. release.yml runs the same
|
# run that does not finish is a defect to find and a timeout only hides it.
|
||||||
# command, so the floor is the same number everywhere. ./verify/... carries the
|
run: go test -short -count=1 -timeout 0 -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
||||||
# live oracle-parity comparison against `go tool asm` (the TestGroundTruth
|
|
||||||
# suites); the runner's Go setup provides both the tool and GOROOT.
|
|
||||||
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
|
|
||||||
|
|
||||||
- name: Tests outside the coverage set
|
- name: Tests outside the coverage set
|
||||||
# The CLI and the debugger sit outside `packages` because a thin main and a
|
# The CLI's exit codes and manual-page guard and the debugger's
|
||||||
# ptrace-bound package pull the total under the floor, but their tests guard
|
# architecture-neutral units, still tested but outside the profile the floor
|
||||||
# shipped surfaces: the command exit codes, the manual pages against the
|
# is computed from. `-short` skips the debugger's live ptrace sessions.
|
||||||
# binary's own help, and the debugger's architecture-neutral units. They run
|
run: go test -short -count=1 -timeout 0 ./cmd/... ./debug/...
|
||||||
# here so the floor stays a product measure and nothing is left untested.
|
|
||||||
run: go test -count=1 -timeout 10m ./cmd/... ./debug/...
|
|
||||||
|
|
||||||
- name: Oracle parity
|
|
||||||
# Re-run the live go-tool-asm comparison as its own step so that a parity
|
|
||||||
# regression names the gate that failed instead of hiding inside the suite.
|
|
||||||
run: go test -count=1 -timeout 10m -run 'TestGroundTruth' ./verify/...
|
|
||||||
|
|
||||||
- name: Coverage floor
|
- name: Coverage floor
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
coverage.out
|
coverage.out
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
# The fuzz campaigns' cache: committed seeds live in the test file, crashes are
|
||||||
|
# minimised into it, so everything go test -fuzz writes under testdata/fuzz is
|
||||||
|
# transient. pending-* holds minimised reproductions awaiting their owner's fix.
|
||||||
|
/asm/testdata/fuzz/
|
||||||
|
|
||||||
# Crash dumps from the emulator runs
|
# Crash dumps from the emulator runs
|
||||||
core
|
core
|
||||||
core.*
|
core.*
|
||||||
|
|||||||
+255
-2
@@ -1,15 +1,268 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to gasm-devkit are documented here.
|
All notable changes to gasm-sdk are documented here.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [development]
|
## [development]
|
||||||
|
|
||||||
|
## [0.36.0] - 2026-10-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
-
|
- **The 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
|
## [0.35.0] - 2026-09-22
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Contributions to **gasm-devkit** are governed by the Contributor terms
|
Contributions to **gasm-sdk** are governed by the Contributor terms
|
||||||
below; submitting one means you accept them.
|
below; submitting one means you accept them.
|
||||||
|
|
||||||
## Contributor terms
|
## Contributor terms
|
||||||
@@ -29,8 +29,8 @@ compiler (gcc), because `just gates` includes `just race` and the race
|
|||||||
detector needs cgo.
|
detector needs cgo.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
git clone https://sourcedock.dev/petrbalvin/gasm-sdk.git
|
||||||
cd gasm-devkit
|
cd gasm-sdk
|
||||||
just build
|
just build
|
||||||
just gates
|
just gates
|
||||||
```
|
```
|
||||||
@@ -117,16 +117,17 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
|
|||||||
|
|
||||||
| Workflow | Trigger | What it does |
|
| Workflow | Trigger | What it does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Test | push or pull request to `development` | build, format check, vet, modernisation, the test suite with the coverage floor, the CLI and debugger tests outside the profile, then the oracle-parity rerun against `go tool asm` |
|
| Test | push or pull request to `development` | the format check, `go vet`, and the short layer of the test suite with the coverage profile and the 80 % floor, inside the two-minute budget |
|
||||||
| Release | a `v*` tag | the same gates as Test minus the oracle-parity step, then the matrix build, the version smoke test and the release itself; the race detector runs locally in `just gates` before the tag is cut |
|
| Suite | dispatched by hand | the complete gate set minus race: the build, the two FreeBSD cross-build gates (amd64, arm64), both static gates, the full suite and the coverage floor |
|
||||||
|
| Release | a `v*` tag | the matrix build (Linux on four architectures, FreeBSD on two), the version smoke test and the release with its assets; no gate runs at the tag |
|
||||||
|
|
||||||
The local equivalent is `just gates`, which is the same set plus the race detector. The
|
The local equivalent is `just gates`, which is the same set plus the race detector. Race
|
||||||
race detector also has its own workflow, dispatched by hand; it never runs on a push or a
|
never runs in CI, on a push or a tag: it would double the time and the memory a shared
|
||||||
tag, where it would double the time and the memory a shared runner cannot spare.
|
runner cannot spare, so the local `just gates` runs it before the tag is cut.
|
||||||
|
|
||||||
## Reporting bugs
|
## Reporting bugs
|
||||||
|
|
||||||
Open an issue at `https://sourcedock.dev/petrbalvin/gasm-devkit/issues` with the
|
Open an issue at `https://sourcedock.dev/petrbalvin/gasm-sdk/issues` with the
|
||||||
version, the operating system and architecture, the exact command, the full output,
|
version, the operating system and architecture, the exact command, the full output,
|
||||||
and the expected against the actual behaviour.
|
and the expected against the actual behaviour.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Plan 9 assembly tooling, inside and outside Go
|
# GAsm: Software Development Kit for Plan 9 Assembly
|
||||||
|
|
||||||
> **Warning: this is an experiment.** gasm-devkit is under active
|
> **Warning: this is an experiment.** gasm-sdk is under active
|
||||||
> development and is not stable. The version is 0.x.x: commands, flags,
|
> development and is not stable. The version is 0.x.x: commands, flags,
|
||||||
> output formats and behaviour can change without warning at any time.
|
> output formats and behaviour can change without warning at any time.
|
||||||
> A 1.0.0 release is light years away. Nothing in this document is a
|
> A 1.0.0 release is light years away. Nothing in this document is a
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
there is no formatter, no linter and no debugger for `.s` files, and no
|
there is no formatter, no linter and no debugger for `.s` files, and no
|
||||||
assembler that works without a Go installation. Developers write
|
assembler that works without a Go installation. Developers write
|
||||||
assembly blind, validate it by benchmark, and debug it by print
|
assembly blind, validate it by benchmark, and debug it by print
|
||||||
statement. gasm-devkit is the missing toolkit: a single, self-contained
|
statement. gasm-sdk is the missing toolkit: a single, self-contained
|
||||||
binary, `gasm`, that serves both purposes.
|
binary, `gasm`, that serves both purposes.
|
||||||
|
|
||||||
- **Help develop Plan 9 assembly.** Formatting, linting, disassembly,
|
- **Help develop Plan 9 assembly.** Formatting, linting, disassembly,
|
||||||
@@ -58,7 +58,7 @@ Plan 9 (Go): MOVQ AX, total-16(SP)
|
|||||||
|
|
||||||
The same lines, but only one of them tells you what the number is for.
|
The same lines, but only one of them tells you what the number is for.
|
||||||
The syntax is uppercase, regular and boring, which is the highest
|
The syntax is uppercase, regular and boring, which is the highest
|
||||||
compliment a language for machine code can earn. gasm-devkit exists
|
compliment a language for machine code can earn. gasm-sdk exists
|
||||||
to give that syntax the tooling it deserves.
|
to give that syntax the tooling it deserves.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -94,13 +94,16 @@ to give that syntax the tooling it deserves.
|
|||||||
- **Debugger.** `gasm debug` is a source-level ptrace debugger with
|
- **Debugger.** `gasm debug` is a source-level ptrace debugger with
|
||||||
breakpoints (optionally conditional), hardware watchpoints, register and
|
breakpoints (optionally conditional), hardware watchpoints, register and
|
||||||
memory inspection, and headless script runs that report instruction and
|
memory inspection, and headless script runs that report instruction and
|
||||||
label coverage.
|
label coverage; it runs on Linux (all four architectures) and FreeBSD
|
||||||
|
(amd64, arm64).
|
||||||
- **Language server.** `gasm lsp` serves completion, hover, document symbols,
|
- **Language server.** `gasm lsp` serves completion, hover, document symbols,
|
||||||
push and pull diagnostics, semantic-token highlighting, go-to-definition,
|
push and pull diagnostics, semantic-token highlighting, go-to-definition,
|
||||||
find references, rename, formatting, inlay hints, code actions, signature
|
find references, rename, formatting, inlay hints, code actions, signature
|
||||||
help, document highlights, workspace symbol search, #include document
|
help, document highlights, workspace symbol search, #include document
|
||||||
links and folding ranges over stdio; definition, references and rename
|
links and folding ranges over stdio; definition, references and rename
|
||||||
work across every open document.
|
work across every open document and the indexed workspace files beyond
|
||||||
|
them, and the quick fixes add a missing textflag.h include and set the
|
||||||
|
argument area from the // func signature.
|
||||||
- **Comparators and audits.** `gasm diff` compares the machine code of two
|
- **Comparators and audits.** `gasm diff` compares the machine code of two
|
||||||
assembly files byte-for-byte, `gasm profile` shows basic-block structure,
|
assembly files byte-for-byte, `gasm profile` shows basic-block structure,
|
||||||
`gasm audit-instructions` diffs the encoder against the installed toolchain,
|
`gasm audit-instructions` diffs the encoder against the installed toolchain,
|
||||||
@@ -127,10 +130,13 @@ can emit today is narrower, and a recognised but unencodable instruction is
|
|||||||
reported as an explicit error, never as a wrong byte.
|
reported as an explicit error, never as a wrong byte.
|
||||||
|
|
||||||
The same measurement runs over GOROOT's whole assembly corpus:
|
The same measurement runs over GOROOT's whole assembly corpus:
|
||||||
`gasm audit-instructions --corpus` reports 291 of 353 attemptable files
|
`gasm audit-instructions --corpus` reports every real-code GOROOT assembly
|
||||||
(82.4 %) assembling for every target architecture today (files named for
|
file (the tree without testdata) assembling for every target its build
|
||||||
other Go ports are counted but never attempted), with the top failure
|
admits: 250 of 250, 100 %. Over the whole tree including testdata the
|
||||||
reasons per architecture; the number moves with every release.
|
measure is 271 of 322 attemptable (84.2 %); files named for other Go ports
|
||||||
|
are counted but never attempted, and `//go:build` constraints decide which
|
||||||
|
targets attempt a file at all, exactly as the build does. The number moves
|
||||||
|
with every release.
|
||||||
|
|
||||||
### Validation status
|
### Validation status
|
||||||
|
|
||||||
@@ -145,7 +151,7 @@ actually been executed.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Encoding: byte-for-byte against `go tool asm` | native hardware | native hardware (the toolchain cross-assembles any GOARCH on any host) |
|
| Encoding: byte-for-byte against `go tool asm` | native hardware | native hardware (the toolchain cross-assembles any GOARCH on any host) |
|
||||||
| Execution: JIT calls, ABI checks, differential fuzzing | native hardware | qemu-user emulation |
|
| Execution: JIT calls, ABI checks, differential fuzzing | native hardware | qemu-user emulation |
|
||||||
| Debugger: ptrace tracing, breakpoints, watchpoints, coverage | native hardware | emulation cannot run ptrace; the layer compiles and its architecture-neutral units run under `go test ./...`, nothing more |
|
| Debugger: ptrace tracing, breakpoints, watchpoints, coverage | native hardware | emulation cannot run ptrace; the layer compiles and its architecture-neutral units run under `go test ./...`, nothing more. FreeBSD (amd64, arm64) is in the same position: the port compiles behind the cross-build gate and its integration test is ready, but no FreeBSD machine has executed it |
|
||||||
|
|
||||||
Consequences, stated plainly. An emulator is a model of a CPU, not the
|
Consequences, stated plainly. An emulator is a model of a CPU, not the
|
||||||
CPU: instruction semantics are implemented in software and can differ
|
CPU: instruction semantics are implemented in software and can differ
|
||||||
@@ -156,9 +162,10 @@ other. Encoding parity is the exception: the byte comparison against the
|
|||||||
toolchain runs on the host for every architecture, so no emulator stands
|
toolchain runs on the host for every architecture, so no emulator stands
|
||||||
between the claim and the evidence. The debugger is the weakest case: on
|
between the claim and the evidence. The debugger is the weakest case: on
|
||||||
the three emulated architectures its per-architecture ptrace code has
|
the three emulated architectures its per-architecture ptrace code has
|
||||||
been compiled and read, never executed. Its architecture-neutral units
|
been compiled and read, never executed. Its units run under
|
||||||
run under `go test ./...`, which the race workflow and a manual run
|
`go test ./...`, which `just test` sweeps in a second invocation beside
|
||||||
perform; the default `just test` gate does not sweep `./debug/...`.
|
the coverage profile; the live ptrace sessions are skipped in short
|
||||||
|
mode, so they run locally or in the dispatched suite, never on a push.
|
||||||
|
|
||||||
## The documentation goal
|
## The documentation goal
|
||||||
|
|
||||||
@@ -215,21 +222,23 @@ The plan, in the order it is being worked:
|
|||||||
toolchain itself does not support; through ELF, Plan 9 assembly becomes
|
toolchain itself does not support; through ELF, Plan 9 assembly becomes
|
||||||
usable outside Go entirely.
|
usable outside Go entirely.
|
||||||
- **Platforms: Linux and FreeBSD.** Linux is supported today on all four
|
- **Platforms: Linux and FreeBSD.** Linux is supported today on all four
|
||||||
architectures and is where the binary builds. FreeBSD follows: the
|
architectures and is where the binary builds. FreeBSD follows on amd64
|
||||||
JIT's executable-memory mapping and the ptrace debugger layer are the
|
and arm64: the JIT's executable-memory mapping and the ptrace debugger
|
||||||
two pieces of porting work. Other unix systems may follow those two.
|
layer are ported, the release carries the two FreeBSD binaries, and the
|
||||||
|
debugger's live validation awaits a FreeBSD machine (the validation
|
||||||
|
status states it). Other unix systems may follow those two.
|
||||||
- **Four architectures, no more.** amd64, arm64, riscv64 and loong64.
|
- **Four architectures, no more.** amd64, arm64, riscv64 and loong64.
|
||||||
No others are planned.
|
No others are planned.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and
|
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64,
|
||||||
linux/loong64 are on the
|
linux/loong64, freebsd/amd64 and freebsd/arm64 are on the
|
||||||
[releases page](https://sourcedock.dev/petrbalvin/gasm-devkit/releases).
|
[releases page](https://sourcedock.dev/petrbalvin/gasm-sdk/releases).
|
||||||
From source (Go 1.27.1):
|
From source (Go 1.27.1):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install sourcedock.dev/petrbalvin/gasm-devkit/cmd/gasm@latest
|
go install sourcedock.dev/petrbalvin/gasm-sdk/cmd/gasm@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or from a repository checkout:
|
Or from a repository checkout:
|
||||||
|
|||||||
+7
-7
@@ -5,7 +5,7 @@
|
|||||||
// toolchain's own assembler source. Go's Plan 9 assembler defines the exact,
|
// toolchain's own assembler source. Go's Plan 9 assembler defines the exact,
|
||||||
// complete set of mnemonics it accepts for each architecture in
|
// complete set of mnemonics it accepts for each architecture in
|
||||||
// $GOROOT/src/cmd/internal/obj/<arch>/anames.go; this tool extracts those
|
// $GOROOT/src/cmd/internal/obj/<arch>/anames.go; this tool extracts those
|
||||||
// names so gasm-devkit supports every instruction the real assembler does,
|
// names so gasm-sdk supports every instruction the real assembler does,
|
||||||
// with no hand-maintained (and therefore inevitably incomplete) lists.
|
// with no hand-maintained (and therefore inevitably incomplete) lists.
|
||||||
//
|
//
|
||||||
// The same data feeds the generated instruction appendices of the assembly
|
// The same data feeds the generated instruction appendices of the assembly
|
||||||
@@ -31,11 +31,11 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// archDirs maps a gasm-devkit architecture name to its obj sub-directory.
|
// archDirs maps a gasm-sdk architecture name to its obj sub-directory.
|
||||||
var archDirs = []struct {
|
var archDirs = []struct {
|
||||||
arch string
|
arch string
|
||||||
sub string
|
sub string
|
||||||
@@ -119,7 +119,7 @@ func filterCommon(names []string) []string {
|
|||||||
// writeCommon emits arch/common_gen.go.
|
// writeCommon emits arch/common_gen.go.
|
||||||
func writeCommon(names []string) error {
|
func writeCommon(names []string) error {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("// Code generated by gasm-devkit _gen; DO NOT EDIT.\n")
|
b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n")
|
||||||
b.WriteString("// Source: cmd/internal/obj/util.go from the Go toolchain.\n")
|
b.WriteString("// Source: cmd/internal/obj/util.go from the Go toolchain.\n")
|
||||||
b.WriteString("//\n")
|
b.WriteString("//\n")
|
||||||
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
||||||
@@ -190,7 +190,7 @@ func stringLit(elt ast.Expr) string {
|
|||||||
// writeGen emits arch/<arch>_gen.go.
|
// writeGen emits arch/<arch>_gen.go.
|
||||||
func writeGen(arch, sub string, names []string) error {
|
func writeGen(arch, sub string, names []string) error {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("// Code generated by gasm-devkit _gen; DO NOT EDIT.\n")
|
b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n")
|
||||||
b.WriteString("// Source: cmd/internal/obj/" + sub + "/anames.go from the Go toolchain.\n")
|
b.WriteString("// Source: cmd/internal/obj/" + sub + "/anames.go from the Go toolchain.\n")
|
||||||
b.WriteString("//\n")
|
b.WriteString("//\n")
|
||||||
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
b.WriteString("// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)\n")
|
||||||
@@ -216,7 +216,7 @@ func writeDocPage(a arch.Arch, title, file, anames, version string, encodable bo
|
|||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("# " + title + ": instruction inventory\n\n")
|
b.WriteString("# " + title + ": instruction inventory\n\n")
|
||||||
b.WriteString("Generated by gasm-devkit's `_gen` from the Go toolchain's instruction table\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("(`" + 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("`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("language on it: a name absent here is not an instruction of the target,\n")
|
||||||
|
|||||||
+1835
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,201 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package arch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The memory-operand mechanism: the base-plus-displacement validation and
|
||||||
|
// the ModR/M, SIB and displacement byte choices. The expected words are
|
||||||
|
// built on the VMOVSH memory-load template, whose rows the binutils-gdb
|
||||||
|
// assembler testsuite quotes byte for byte; the rows marked GNU match the
|
||||||
|
// listing bytes. Note on the quoted disp8 rows: binutils mainline encodes
|
||||||
|
// EVEX displacements with the APX disp8*N scaling, so its source spellings
|
||||||
|
// (254 for the m16 rows, 8128 for the m512 ones) are N times the plain SDM
|
||||||
|
// displacement the disp8 bytes carry; the words here pin the bytes with the
|
||||||
|
// plain displacement those bytes encode.
|
||||||
|
func TestAmd64ExtMemoryEncoding(t *testing.T) {
|
||||||
|
load := []byte{0x62, 0x05, 0x06, 0x00, 0x10, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
gnuSource string // the binutils source line the bytes serve, empty for a derived row
|
||||||
|
}{
|
||||||
|
{"zero displacement drops the disp bytes", 9, 0,
|
||||||
|
"62457e081031", "vmovsh (%r9),%xmm30"},
|
||||||
|
{"positive disp8", 1, 127,
|
||||||
|
"62657e0810717f", "vmovsh 254(%rcx),%xmm30 (Disp8(7f) under the disp8*N scaling)"},
|
||||||
|
{"negative disp8", 2, -128,
|
||||||
|
"62657e08107280", "vmovsh -256(%rdx),%xmm30 (Disp8(80) under the disp8*N scaling)"},
|
||||||
|
{"disp32 past the disp8 range", 2, 8128,
|
||||||
|
"62657e0810b2c01f0000", ""},
|
||||||
|
{"negative disp32", 13, -200,
|
||||||
|
"62457e0810b538ffffff", ""},
|
||||||
|
{"RSP base takes the SIB byte", 12, 0,
|
||||||
|
"62457e08103424", ""},
|
||||||
|
{"RSP base with a disp8", 12, 4,
|
||||||
|
"62457e0810742404", ""},
|
||||||
|
{"RBP base keeps a zero displacement explicit", 5, 0,
|
||||||
|
"62657e08107500", ""},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeMemory(load, 30, -1, tt.base, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtMemoryRejects checks the validation around the memory operand:
|
||||||
|
// the kinds and ranges the layer refuses before a byte is laid down.
|
||||||
|
func TestAmd64ExtMemoryRejects(t *testing.T) {
|
||||||
|
in := ExtInstr{Name: "TEST"}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
op ExtOperand
|
||||||
|
quote string
|
||||||
|
}{
|
||||||
|
{"a register where the memory operand belongs", ExtXmm(3),
|
||||||
|
"wants a memory operand"},
|
||||||
|
{"base beyond r15", ExtMemory(16, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
{"base under r0", ExtMemory(-1, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
{"displacement past the signed 32-bit ceiling", ExtMemory(8, 1<<31),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"displacement past the signed 32-bit floor", ExtMemory(8, -1<<31-1),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"shift on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Imm: 4, Shift: 2, HasShift: true},
|
||||||
|
"take none"},
|
||||||
|
{"arrangement suffix on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Arr: ExtArrH},
|
||||||
|
"arrangement"},
|
||||||
|
{"predicate qualifier on the memory operand", ExtOperand{Kind: ExtMem, Reg: 8, Qual: ExtQualZeroing},
|
||||||
|
"predicate qualifier"},
|
||||||
|
{"broadcast on an entry that takes none", ExtBroadcast(8, 0),
|
||||||
|
"takes none"},
|
||||||
|
} {
|
||||||
|
_, _, err := in.amd64Memory(tt.op, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s: validation succeeded, want an error", tt.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.quote) {
|
||||||
|
t.Errorf("%s: error %q lacks %q", tt.name, err, tt.quote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The broadcast spelling passes the flag gate on an entry that carries
|
||||||
|
// Bcast and then meets the same base and displacement checks.
|
||||||
|
bcast := ExtInstr{Name: "TEST", Bcast: true}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
op ExtOperand
|
||||||
|
quote string
|
||||||
|
}{
|
||||||
|
{"broadcast base beyond r15", ExtOperand{Kind: ExtMem, Reg: 16, Imm: 0, Broadcast: true},
|
||||||
|
"outside 0-15"},
|
||||||
|
{"broadcast displacement past the signed 32-bit ceiling", ExtBroadcast(8, 1<<31),
|
||||||
|
"outside the signed 32-bit range"},
|
||||||
|
{"broadcast base under r0", ExtBroadcast(-1, 0),
|
||||||
|
"outside 0-15"},
|
||||||
|
} {
|
||||||
|
if _, _, err := bcast.amd64Memory(tt.op, 1); err == nil {
|
||||||
|
t.Errorf("%s: validation succeeded, want an error", tt.name)
|
||||||
|
} else if !strings.Contains(err.Error(), tt.quote) {
|
||||||
|
t.Errorf("%s: error %q lacks %q", tt.name, err, tt.quote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtBroadcastEncoding pins the broadcast layer over the memory
|
||||||
|
// encoding: EVEX.b, bit 4 of byte three, set on the VADDPH 512-bit template
|
||||||
|
// while the ModR/M mod bits, the SIB byte and the displacement choices keep
|
||||||
|
// the plain semantics amd64EncodeMemory chooses.
|
||||||
|
func TestAmd64ExtBroadcastEncoding(t *testing.T) {
|
||||||
|
add := []byte{0x62, 0x05, 0x04, 0x40, 0x58, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"zero displacement keeps the mod-00 form under the broadcast bit", 9, 0,
|
||||||
|
"624514505831"},
|
||||||
|
{"disp8 semantics unchanged", 1, 127,
|
||||||
|
"6265145058717f"},
|
||||||
|
{"disp32 semantics unchanged", 2, 8128,
|
||||||
|
"6265145058b2c01f0000"},
|
||||||
|
{"RBP keeps the forced displacement", 5, 0,
|
||||||
|
"62651450587500"},
|
||||||
|
{"RSP keeps the SIB byte", 12, 0,
|
||||||
|
"62451450583424"},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeBroadcast(add, 30, 29, tt.base, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtScaledMemoryEncoding pins the SIB layer over the memory
|
||||||
|
// encoding: the scale field, the index and the base in one byte, the r/m
|
||||||
|
// field 100, EVEX.X clearing on an index above 7, and the ModR/M and
|
||||||
|
// displacement choices keeping the plain semantics, RBP's forced
|
||||||
|
// displacement included.
|
||||||
|
func TestAmd64ExtScaledMemoryEncoding(t *testing.T) {
|
||||||
|
add := []byte{0x62, 0x05, 0x04, 0x40, 0x58, 0xC0}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
base int
|
||||||
|
index int
|
||||||
|
scale int
|
||||||
|
disp int64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"scale 1 encodes the scale field zero", 1, 2, 1, 0,
|
||||||
|
"62651440583411"},
|
||||||
|
{"scale 2", 1, 2, 2, 0,
|
||||||
|
"62651440583451"},
|
||||||
|
{"scale 4", 1, 2, 4, 0,
|
||||||
|
"62651440583491"},
|
||||||
|
{"scale 8", 1, 2, 8, 0,
|
||||||
|
"626514405834d1"},
|
||||||
|
{"an index above 7 clears EVEX.X", 1, 12, 2, 0,
|
||||||
|
"62251440583461"},
|
||||||
|
{"RBP base keeps the forced displacement", 5, 14, 8, 0,
|
||||||
|
"622514405874f500"},
|
||||||
|
{"RSP base takes the SIB byte with the index", 12, 3, 4, 0,
|
||||||
|
"6245144058349c"},
|
||||||
|
} {
|
||||||
|
got := amd64EncodeScaledMemory(add, 30, 29, tt.base, tt.index, tt.scale, tt.disp)
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtMemoryVocabulary pins the names the shared layer gives the
|
||||||
|
// memory operand and its two forms.
|
||||||
|
func TestAmd64ExtMemoryVocabulary(t *testing.T) {
|
||||||
|
if got := ExtMem.String(); got != "memory operand" {
|
||||||
|
t.Errorf("ExtMem = %q, want %q", got, "memory operand")
|
||||||
|
}
|
||||||
|
if got := ExtFormAmdMemVec.String(); got != "memory into a vector" {
|
||||||
|
t.Errorf("ExtFormAmdMemVec = %q, want %q", got, "memory into a vector")
|
||||||
|
}
|
||||||
|
if got := ExtFormAmdVecMem.String(); got != "a vector into memory" {
|
||||||
|
t.Errorf("ExtFormAmdVecMem = %q, want %q", got, "a vector into memory")
|
||||||
|
}
|
||||||
|
for _, f := range []ExtForm{ExtFormAmdMemVec, ExtFormAmdVecMem} {
|
||||||
|
if got := f.Arity(); got != 2 {
|
||||||
|
t.Errorf("%s takes %d operands, want 2", f, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := ExtMemory(9, 4096); got.Kind != ExtMem || got.Reg != 9 || got.Imm != 4096 {
|
||||||
|
t.Errorf("ExtMemory(9, 4096) = %+v, want base 9 with displacement 4096", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/x86/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/x86/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+31
-10
@@ -24,20 +24,41 @@ const (
|
|||||||
Unknown Arch = ""
|
Unknown Arch = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromFilename guesses the target architecture from a source file name. Go
|
// FromFilename guesses the target architecture from a source file name, by
|
||||||
// assembly files conventionally carry a GOARCH suffix such as "_amd64.s",
|
// go/build's goodOSArchFile rule: directories are stripped, the name is cut
|
||||||
// "_arm64.s", "_riscv64.s" or "_loong64.s". It returns Unknown when no suffix
|
// at its first dot, everything before the first underscore is ignored and a
|
||||||
// matches.
|
// trailing _test segment is dropped; the name is per-architecture only when
|
||||||
|
// its final underscore segment is an architecture name (foo_amd64.s,
|
||||||
|
// sys_darwin_arm64.s, foo_amd64_test.s). Any other name narrows nothing:
|
||||||
|
// not one whose last segment merely contains an architecture name
|
||||||
|
// (x_loong64y.s), not one with no underscore at all (amd64.s, which
|
||||||
|
// go/build also keeps generic), and not one differing in case
|
||||||
|
// (foo_AMD64.s), because the segment match is case-sensitive as go/build's
|
||||||
|
// is. A name carrying an operating system alone (foo_linux.s) narrows by
|
||||||
|
// GOOS rather than architecture, and one carrying an architecture gasm
|
||||||
|
// does not assemble for (vlop_arm.s) reports Unknown so the caller's own
|
||||||
|
// port filter decides. It returns Unknown when the name narrows nothing.
|
||||||
func FromFilename(name string) Arch {
|
func FromFilename(name string) Arch {
|
||||||
lower := strings.ToLower(name)
|
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||||
switch {
|
name = name[i+1:]
|
||||||
case strings.Contains(lower, "_amd64"):
|
}
|
||||||
|
name, _, _ = strings.Cut(name, ".")
|
||||||
|
i := strings.Index(name, "_")
|
||||||
|
if i < 0 {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
segs := strings.Split(name[i:], "_")
|
||||||
|
if last := len(segs) - 1; segs[last] == "test" {
|
||||||
|
segs = segs[:last]
|
||||||
|
}
|
||||||
|
switch segs[len(segs)-1] {
|
||||||
|
case "amd64":
|
||||||
return AMD64
|
return AMD64
|
||||||
case strings.Contains(lower, "_arm64"):
|
case "arm64":
|
||||||
return ARM64
|
return ARM64
|
||||||
case strings.Contains(lower, "_riscv64"), strings.Contains(lower, "_riscv"):
|
case "riscv64":
|
||||||
return RISCV
|
return RISCV
|
||||||
case strings.Contains(lower, "_loong64"), strings.Contains(lower, "_loong"):
|
case "loong64":
|
||||||
return LOONG64
|
return LOONG64
|
||||||
default:
|
default:
|
||||||
return Unknown
|
return Unknown
|
||||||
|
|||||||
+29
-2
@@ -7,13 +7,40 @@ import "testing"
|
|||||||
|
|
||||||
func TestFromFilename(t *testing.T) {
|
func TestFromFilename(t *testing.T) {
|
||||||
cases := map[string]Arch{
|
cases := map[string]Arch{
|
||||||
|
// Positive: the final underscore segment is the architecture.
|
||||||
"avx2_amd64.s": AMD64,
|
"avx2_amd64.s": AMD64,
|
||||||
"foo_arm64.s": ARM64,
|
"foo_arm64.s": ARM64,
|
||||||
"portable.s": Unknown,
|
|
||||||
"decode_ARM64.S": ARM64,
|
|
||||||
"kernels_amd64.s": AMD64,
|
"kernels_amd64.s": AMD64,
|
||||||
"kernel_riscv64.s": RISCV,
|
"kernel_riscv64.s": RISCV,
|
||||||
"kernel_loong64.s": LOONG64,
|
"kernel_loong64.s": LOONG64,
|
||||||
|
"decode_arm64.S": ARM64, // the extension is cut with the first dot
|
||||||
|
// OS before arch: the _<os>_<arch> pair form.
|
||||||
|
"sys_darwin_arm64.s": ARM64,
|
||||||
|
"rt0_linux_amd64.s": AMD64,
|
||||||
|
// A trailing _test segment is dropped before the suffix rule.
|
||||||
|
"foo_amd64_test.s": AMD64,
|
||||||
|
// Trailing junk: a final segment that merely contains an
|
||||||
|
// architecture name narrows nothing, exactly as go/build's
|
||||||
|
// segment rule says.
|
||||||
|
"x_loong64y.s": Unknown,
|
||||||
|
"asm_amd64x.s": Unknown, // a real GOROOT name
|
||||||
|
"asm_darwin_arm64_gc.s": Unknown, // the trailing _gc segment is junk
|
||||||
|
"x_loong.s": Unknown,
|
||||||
|
"foo_amd64x_test.s": Unknown,
|
||||||
|
// Case sensitivity: the segment match is exact, as go/build's is.
|
||||||
|
"foo_AMD64.s": Unknown,
|
||||||
|
"decode_ARM64.S": Unknown,
|
||||||
|
// No underscore at all: generic whatever the stem says.
|
||||||
|
"amd64.s": Unknown,
|
||||||
|
"portable.s": Unknown,
|
||||||
|
// A GOOS-only name narrows by operating system, not architecture.
|
||||||
|
"foo_linux.s": Unknown,
|
||||||
|
// An architecture gasm does not assemble for: named, but Unknown.
|
||||||
|
"vlop_arm.s": Unknown,
|
||||||
|
"foo_riscv.s": Unknown,
|
||||||
|
// Directories are stripped first, whatever dots they carry.
|
||||||
|
"some/dir/kernel_loong64.s": LOONG64,
|
||||||
|
"/a.b/x_amd64.s": AMD64,
|
||||||
}
|
}
|
||||||
for name, want := range cases {
|
for name, want := range cases {
|
||||||
if got := FromFilename(name); got != want {
|
if got := FromFilename(name); got != want {
|
||||||
|
|||||||
+5294
File diff suppressed because it is too large.
Load diff
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/arm64/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/arm64/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by gasm-devkit _gen; DO NOT EDIT.
|
// Code generated by gasm-sdk _gen; DO NOT EDIT.
|
||||||
// Source: cmd/internal/obj/util.go from the Go toolchain.
|
// Source: cmd/internal/obj/util.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+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/loong64/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/loong64/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+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/riscv/anames.go from the Go toolchain.
|
// Source: cmd/internal/obj/riscv/anames.go from the Go toolchain.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
|||||||
+15
-66
@@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
||||||
@@ -178,26 +178,10 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
}
|
}
|
||||||
var work, linkLine, asmObj string
|
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
defer os.RemoveAll(st.work)
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(line, "WORK="):
|
|
||||||
work = strings.TrimPrefix(line, "WORK=")
|
|
||||||
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_arm64.s") && !strings.Contains(line, "-gensymabis"):
|
|
||||||
asmObj = fieldAfter(line, "-o")
|
|
||||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
|
||||||
linkLine = line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if work == "" || asmObj == "" {
|
|
||||||
t.Skipf("could not parse build log (work=%q asmObj=%q)", work, asmObj)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(work)
|
|
||||||
|
|
||||||
// Expand $WORK in the object path.
|
// Assemble the same source with gasm and substitute the object.
|
||||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
|
||||||
|
|
||||||
// Read the toolchain-produced object and assemble the same source with gasm.
|
|
||||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -210,30 +194,16 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("AssembleFileARM64: %v", err)
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
}
|
}
|
||||||
gasmObj, err := img.GOObjectAARCH64("a64link", "main_arm64.s")
|
// The package path is "main", the prefix the Go code's references carry.
|
||||||
|
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||||
}
|
}
|
||||||
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||||
// Replace the toolchain-produced object with gasm's.
|
gasmObj, "GOARCH=arm64")
|
||||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
|
||||||
t.Fatalf("write gasm object: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-link.
|
|
||||||
if linkLine == "" {
|
|
||||||
t.Skip("could not find link command in build log")
|
|
||||||
}
|
|
||||||
// Expand $WORK in the link command.
|
|
||||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
|
||||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
|
||||||
linkCmd.Env = append(os.Environ(), "GOARCH=arm64")
|
|
||||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
|
||||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the binary exists and contains the symbol.
|
// Verify the binary exists and contains the symbol.
|
||||||
binPath := filepath.Join(dir, "prog")
|
binPath := filepath.Join(dir, "prog2")
|
||||||
if _, err := os.Stat(binPath); err != nil {
|
if _, err := os.Stat(binPath); err != nil {
|
||||||
t.Fatalf("binary not found: %v", err)
|
t.Fatalf("binary not found: %v", err)
|
||||||
}
|
}
|
||||||
@@ -296,23 +266,8 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
}
|
}
|
||||||
var work, linkLine, asmObj string
|
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
defer os.RemoveAll(st.work)
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(line, "WORK="):
|
|
||||||
work = strings.TrimPrefix(line, "WORK=")
|
|
||||||
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_arm64.s") && !strings.Contains(line, "-gensymabis"):
|
|
||||||
asmObj = fieldAfter(line, "-o")
|
|
||||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
|
||||||
linkLine = line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if work == "" || asmObj == "" || linkLine == "" {
|
|
||||||
t.Skipf("could not parse build log (work=%q asmObj=%q link=%q)", work, asmObj, linkLine)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(work)
|
|
||||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
|
||||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
|
||||||
|
|
||||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -326,19 +281,13 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("AssembleFileARM64: %v", err)
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
}
|
}
|
||||||
gasmObj, err := img.GOObjectAARCH64("a64dlink", "main_arm64.s")
|
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||||
t.Fatalf("write gasm object: %v", err)
|
gasmObj, "GOARCH=arm64")
|
||||||
}
|
binData, err := os.ReadFile(filepath.Join(dir, "prog2"))
|
||||||
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)
|
|
||||||
}
|
|
||||||
binData, err := os.ReadFile(filepath.Join(dir, "prog"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2276
-285
File diff suppressed because it is too large.
Load diff
+296
-131
@@ -33,7 +33,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// arm64RegNum returns the 5-bit register number for an AArch64 register name:
|
// arm64RegNum returns the 5-bit register number for an AArch64 register name:
|
||||||
@@ -374,6 +374,7 @@ const (
|
|||||||
a64FPair // load/store pair: LDP, STP, LDPW, STPW, FLDPD, FSTPD
|
a64FPair // load/store pair: LDP, STP, LDPW, STPW, FLDPD, FSTPD
|
||||||
a64FAcqRel // acquire/release: LDAR family, STLR family
|
a64FAcqRel // acquire/release: LDAR family, STLR family
|
||||||
a64FSys // system: BRK, SVC, DMB, DSB, ISB, DC, MRS, MSR, PRFM
|
a64FSys // system: BRK, SVC, DMB, DSB, ISB, DC, MRS, MSR, PRFM
|
||||||
|
a64FCASP // compare and swap pair: CASP
|
||||||
a64FCrypto2 // crypto 2-register: AESD, AESE, AESIMC, AESMC, SHA1H, ...
|
a64FCrypto2 // crypto 2-register: AESD, AESE, AESIMC, AESMC, SHA1H, ...
|
||||||
a64FCrypto3 // crypto 3-register: SHA1C, SHA256H, SHA512SU1, ...
|
a64FCrypto3 // crypto 3-register: SHA1C, SHA256H, SHA512SU1, ...
|
||||||
a64FSIMDV // SIMD 3-register with arrangement: VADD, VAND, VCMEQ, VZIP1, ...
|
a64FSIMDV // SIMD 3-register with arrangement: VADD, VAND, VCMEQ, VZIP1, ...
|
||||||
@@ -384,6 +385,7 @@ const (
|
|||||||
a64FDUP // SIMD element moves: VDUP, VMOV with element indices
|
a64FDUP // SIMD element moves: VDUP, VMOV with element indices
|
||||||
a64FVLDST // SIMD structure loads/stores: VLD1, VST1, VLD1R, VLD4R
|
a64FVLDST // SIMD structure loads/stores: VLD1, VST1, VLD1R, VLD4R
|
||||||
a64FShiftImm // SIMD shift by immediate: VSHL, VUSHR, VSRI
|
a64FShiftImm // SIMD shift by immediate: VSHL, VUSHR, VSRI
|
||||||
|
a64FVMoviImm // SIMD move immediate: VMOVI $imm8, Vd.B8/B16
|
||||||
a64FMoviLit // VMOVS/VMOVD/VMOVQ with a large constant (literal pool)
|
a64FMoviLit // VMOVS/VMOVD/VMOVQ with a large constant (literal pool)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -563,16 +565,12 @@ func init() {
|
|||||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfieldAlias, op: 0<<31 | 1<<29 | 0x26<<23}
|
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfieldAlias, op: 0<<31 | 1<<29 | 0x26<<23}
|
||||||
a64InstrTable["SBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x93400000}
|
a64InstrTable["SBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x93400000}
|
||||||
a64InstrTable["SBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x13000000}
|
a64InstrTable["SBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x13000000}
|
||||||
a64InstrTable["UBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x53000000}
|
a64InstrTable["UBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0xd3400000}
|
||||||
a64InstrTable["UBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x33000000}
|
a64InstrTable["UBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x53000000}
|
||||||
a64InstrTable["SBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["SBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["SBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["SBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}
|
||||||
a64InstrTable["UBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["UBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["UBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["UBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
||||||
a64InstrTable["BFI"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
|
||||||
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
|
||||||
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
|
||||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
|
||||||
|
|
||||||
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
||||||
fp3 := map[string]uint32{
|
fp3 := map[string]uint32{
|
||||||
@@ -597,6 +595,10 @@ func init() {
|
|||||||
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
||||||
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
||||||
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
||||||
|
// The half-precision conversions: FPOP1S rows with the half source
|
||||||
|
// or destination (type 3), single and double beside them.
|
||||||
|
"FCVTHS": 0x1ee24000, "FCVTHD": 0x1ee2c000,
|
||||||
|
"FCVTSH": 0x1e23c000, "FCVTDH": 0x1e63c000,
|
||||||
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
||||||
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
||||||
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
||||||
@@ -770,7 +772,7 @@ func init() {
|
|||||||
a64InstrTable["CCMNW"] = a64Enc{format: a64FCondCmp, op: 0x3a400000}
|
a64InstrTable["CCMNW"] = a64Enc{format: a64FCondCmp, op: 0x3a400000}
|
||||||
|
|
||||||
// ---- system operations ----
|
// ---- system operations ----
|
||||||
for _, m := range []string{"BRK", "SVC", "DMB", "DSB", "ISB", "CLREX", "HINT", "BTI", "HLT", "SMC", "HVC", "DCPS1", "DCPS2", "DCPS3", "DRPS", "ERET", "AUTIASP", "AUTIBSP", "AUTIA1716", "AUTIB1716", "SEVL", "SEV", "WFE", "WFI", "YIELD", "DC", "MRS", "MSR", "PRFM"} {
|
for _, m := range []string{"BRK", "SVC", "DMB", "DSB", "ISB", "CLREX", "HINT", "BTI", "HLT", "SMC", "HVC", "DCPS1", "DCPS2", "DCPS3", "DRPS", "ERET", "AUTIASP", "AUTIBSP", "AUTIA1716", "AUTIB1716", "SEVL", "SEV", "WFE", "WFI", "YIELD", "DC", "MRS", "MSR", "PRFM", "RPRFM", "SYS", "SYSL", "TLBI", "SB", "PACIASP", "PACIBSP"} {
|
||||||
a64InstrTable[m] = a64Enc{format: a64FSys}
|
a64InstrTable[m] = a64Enc{format: a64FSys}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,12 +785,20 @@ func init() {
|
|||||||
a64InstrTable["TBNZ"] = a64Enc{format: a64FTestBranch, op: 0x37000000}
|
a64InstrTable["TBNZ"] = a64Enc{format: a64FTestBranch, op: 0x37000000}
|
||||||
|
|
||||||
// ---- load/store pair (signed offset) ----
|
// ---- load/store pair (signed offset) ----
|
||||||
|
// The scale column of a64LoadTable does not reach the pair forms, so each
|
||||||
|
// entry states its own access width through the imm7 divisor the pair
|
||||||
|
// encoder derives from the opc field (8 for D, 4 for W and SW, 16 for Q).
|
||||||
a64InstrTable["LDP"] = a64Enc{format: a64FPair, op: 0xa9400000}
|
a64InstrTable["LDP"] = a64Enc{format: a64FPair, op: 0xa9400000}
|
||||||
a64InstrTable["LDPW"] = a64Enc{format: a64FPair, op: 0x29400000}
|
a64InstrTable["LDPW"] = a64Enc{format: a64FPair, op: 0x29400000}
|
||||||
|
a64InstrTable["LDPSW"] = a64Enc{format: a64FPair, op: 0x69400000}
|
||||||
a64InstrTable["STP"] = a64Enc{format: a64FPair, op: 0xa9000000}
|
a64InstrTable["STP"] = a64Enc{format: a64FPair, op: 0xa9000000}
|
||||||
a64InstrTable["STPW"] = a64Enc{format: a64FPair, op: 0x29000000}
|
a64InstrTable["STPW"] = a64Enc{format: a64FPair, op: 0x29000000}
|
||||||
a64InstrTable["FLDPD"] = a64Enc{format: a64FPair, op: 0x6d400000}
|
a64InstrTable["FLDPD"] = a64Enc{format: a64FPair, op: 0x6d400000}
|
||||||
a64InstrTable["FSTPD"] = a64Enc{format: a64FPair, op: 0x6d000000}
|
a64InstrTable["FSTPD"] = a64Enc{format: a64FPair, op: 0x6d000000}
|
||||||
|
a64InstrTable["FLDPS"] = a64Enc{format: a64FPair, op: 0x2d400000}
|
||||||
|
a64InstrTable["FSTPS"] = a64Enc{format: a64FPair, op: 0x2d000000}
|
||||||
|
a64InstrTable["FLDPQ"] = a64Enc{format: a64FPair, op: 0xad400000}
|
||||||
|
a64InstrTable["FSTPQ"] = a64Enc{format: a64FPair, op: 0xad000000}
|
||||||
|
|
||||||
// ---- acquire/release loads and stores ----
|
// ---- acquire/release loads and stores ----
|
||||||
a64InstrTable["LDAR"] = a64Enc{format: a64FAcqRel, op: 0xc8dffc00}
|
a64InstrTable["LDAR"] = a64Enc{format: a64FAcqRel, op: 0xc8dffc00}
|
||||||
@@ -806,11 +816,23 @@ func init() {
|
|||||||
lse := map[string]uint32{
|
lse := map[string]uint32{
|
||||||
"CASALD": 0xc8e0fc00,
|
"CASALD": 0xc8e0fc00,
|
||||||
"CASALW": 0x88e0fc00,
|
"CASALW": 0x88e0fc00,
|
||||||
|
"CASB": 0x08a07c00,
|
||||||
|
"CASAB": 0x08e07c00,
|
||||||
|
"CASH": 0x48a07c00,
|
||||||
|
"CASLD": 0xc8a0fc00,
|
||||||
|
"CASLH": 0x48a0fc00,
|
||||||
|
"CASAW": 0x88e07c00,
|
||||||
|
"CASAD": 0xc8e07c00,
|
||||||
|
"CASALH": 0x48e0fc00,
|
||||||
"LDADDALD": 0xf8e00000,
|
"LDADDALD": 0xf8e00000,
|
||||||
"LDADDALW": 0xb8e00000,
|
"LDADDALW": 0xb8e00000,
|
||||||
|
"LDADDAD": 0xf8a00000,
|
||||||
|
"LDADDAW": 0xb8a00000,
|
||||||
"LDCLRALB": 0x38e01000,
|
"LDCLRALB": 0x38e01000,
|
||||||
"LDCLRALW": 0xb8e01000,
|
"LDCLRALW": 0xb8e01000,
|
||||||
"LDCLRALD": 0xf8e01000,
|
"LDCLRALD": 0xf8e01000,
|
||||||
|
"LDCLRAD": 0xf8a01000,
|
||||||
|
"LDCLRAW": 0xb8a01000,
|
||||||
"LDORALB": 0x38e03000,
|
"LDORALB": 0x38e03000,
|
||||||
"LDORALW": 0xb8e03000,
|
"LDORALW": 0xb8e03000,
|
||||||
"LDORALD": 0xf8e03000,
|
"LDORALD": 0xf8e03000,
|
||||||
@@ -847,7 +869,9 @@ func init() {
|
|||||||
"LDEORAD": 0xf8a02000,
|
"LDEORAD": 0xf8a02000,
|
||||||
"LDEORAH": 0x78a02000,
|
"LDEORAH": 0x78a02000,
|
||||||
"LDEORALB": 0x38e02000,
|
"LDEORALB": 0x38e02000,
|
||||||
|
"LDEORALD": 0xf8e02000,
|
||||||
"LDEORALH": 0x78e02000,
|
"LDEORALH": 0x78e02000,
|
||||||
|
"LDEORALW": 0xb8e02000,
|
||||||
"LDEORAW": 0xb8a02000,
|
"LDEORAW": 0xb8a02000,
|
||||||
"LDEORB": 0x38202000,
|
"LDEORB": 0x38202000,
|
||||||
"LDEORD": 0xf8202000,
|
"LDEORD": 0xf8202000,
|
||||||
@@ -889,6 +913,12 @@ func init() {
|
|||||||
a64InstrTable[m] = a64Enc{format: a64FLSE, op: op}
|
a64InstrTable[m] = a64Enc{format: a64FLSE, op: op}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare and swap pair: the second register of each pair is implicit
|
||||||
|
// (Rs+1 and Rt+1), so the encoding carries Rs and Rt alone over a preset
|
||||||
|
// fixed field (asm7.go atomicCASP).
|
||||||
|
a64InstrTable["CASPD"] = a64Enc{format: a64FCASP, op: 1<<30 | 0x41<<21 | 0x1f<<10}
|
||||||
|
a64InstrTable["CASPW"] = a64Enc{format: a64FCASP, op: 0x41<<21 | 0x1f<<10}
|
||||||
|
|
||||||
// ---- carry-setting/carry-using arithmetic and widening multiply ----
|
// ---- carry-setting/carry-using arithmetic and widening multiply ----
|
||||||
// MUL and SMULH/UMULH are the MADD/MSUB layout with the accumulate
|
// MUL and SMULH/UMULH are the MADD/MSUB layout with the accumulate
|
||||||
// register preset to ZR (bits 14:10 = 11111).
|
// register preset to ZR (bits 14:10 = 11111).
|
||||||
@@ -940,11 +970,13 @@ func init() {
|
|||||||
a64InstrTable["VMOVS"] = a64Enc{format: a64FMoviLit, op: 0xbd400000}
|
a64InstrTable["VMOVS"] = a64Enc{format: a64FMoviLit, op: 0xbd400000}
|
||||||
a64InstrTable["VMOVD"] = a64Enc{format: a64FMoviLit, op: 0xfd400000}
|
a64InstrTable["VMOVD"] = a64Enc{format: a64FMoviLit, op: 0xfd400000}
|
||||||
a64InstrTable["VMOVQ"] = a64Enc{format: a64FMoviLit, op: 0x3dc00000}
|
a64InstrTable["VMOVQ"] = a64Enc{format: a64FMoviLit, op: 0x3dc00000}
|
||||||
|
a64InstrTable["VMOVI"] = a64Enc{format: a64FVMoviImm}
|
||||||
a64InstrTable["VSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 21<<10}
|
a64InstrTable["VSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 21<<10}
|
||||||
a64InstrTable["VUSHR"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 1<<10}
|
a64InstrTable["VUSHR"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 1<<10}
|
||||||
a64InstrTable["VSRI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 17<<10}
|
a64InstrTable["VSRI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 17<<10}
|
||||||
a64InstrTable["VSSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 1<<10}
|
a64InstrTable["VSSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 1<<10}
|
||||||
a64InstrTable["VSRA"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 17<<10}
|
a64InstrTable["VSRA"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 7<<10}
|
||||||
|
a64InstrTable["VUSRA"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 5<<10}
|
||||||
a64InstrTable["VSRSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 9<<10}
|
a64InstrTable["VSRSHR"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 9<<10}
|
||||||
a64InstrTable["VSLI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 21<<10}
|
a64InstrTable["VSLI"] = a64Enc{format: a64FShiftImm, op: 0x2f000000 | 21<<10}
|
||||||
a64InstrTable["VSQSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 29<<10}
|
a64InstrTable["VSQSHL"] = a64Enc{format: a64FShiftImm, op: 0x0f000000 | 29<<10}
|
||||||
@@ -957,16 +989,37 @@ func init() {
|
|||||||
a64InstrTable["VLD1R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
a64InstrTable["VLD1R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
a64InstrTable["VLD4R"] = a64Enc{format: a64FVLDST}
|
a64InstrTable["VLD4R"] = a64Enc{format: a64FVLDST}
|
||||||
a64InstrTable["VLD4R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
a64InstrTable["VLD4R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
// Multi-register structure accesses beyond VLD1/VST1: VLD2/VLD3/VLD4 and
|
||||||
|
// the replicate loads VLD2R/VLD3R, each with the post-index spelling.
|
||||||
|
a64InstrTable["VLD2"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD2.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD3"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD3.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD4"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD4.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD2R"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD2R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VLD3R"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VLD3R.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST2"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST2.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST3"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST3.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
|
a64InstrTable["VST4"] = a64Enc{format: a64FVLDST}
|
||||||
|
a64InstrTable["VST4.P"] = a64Enc{format: a64FVLDST, op: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64SimdVSpec is one arrangement-aware SIMD instruction: the 8B base word,
|
// a64SimdVSpec is one arrangement-aware SIMD instruction: the 8B base word,
|
||||||
// the set of arrangements it accepts as a bitmask over the a64Arr index and,
|
// the set of arrangements it accepts as a bitmask over the a64Arr index,
|
||||||
// for instructions that exist at a single arrangement and carry that
|
// the fixed flag for instructions that exist at a single arrangement and
|
||||||
// arrangement's bits inside the base already, the fixed flag.
|
// carry that arrangement's bits inside the base already, and the fp flag for
|
||||||
|
// the FP rows, whose size field is the single FP bit (a64FPArrBits) instead
|
||||||
|
// of the integer size.
|
||||||
type a64SimdVSpec struct {
|
type a64SimdVSpec struct {
|
||||||
base uint32
|
base uint32
|
||||||
arrs uint16
|
arrs uint16
|
||||||
fixed bool
|
fixed bool
|
||||||
|
fp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64Arr names the vector arrangements the encoders deal with, indexed by
|
// a64Arr names the vector arrangements the encoders deal with, indexed by
|
||||||
@@ -1013,11 +1066,24 @@ func a64ElemLetter(s string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// fpSimdArrs and fpAcrossArrs bound the arrangements the FP SIMD forms
|
// fpSimdArrs bounds the arrangements the FP SIMD forms accept: S and D only,
|
||||||
// accept: H, S and D widths for the pairwise data-processing, H and S for
|
// the toolchain rejecting the half-width spellings outright ("invalid
|
||||||
// the across-vector reductions.
|
// arrangement"). fpAcrossArrs bounds the across-vector reductions, which do
|
||||||
var fpSimdArrs = uint16(1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D)
|
// take the half width.
|
||||||
var fpAcrossArrs = uint16(1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S)
|
var fpSimdArrs = uint16(1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D)
|
||||||
|
var fpAcrossArrs = uint16(1 << a64Arr4S)
|
||||||
|
|
||||||
|
// a64FPArrBits carries the bits an arrangement contributes to the FP SIMD
|
||||||
|
// words: the FP size field is a single bit at bit 22 (0 for the S widths, 1
|
||||||
|
// for the D widths; the toolchain carries no half-width FP rows) and the
|
||||||
|
// 128-bit flag sits at bit 30. Word-verified against go tool asm.
|
||||||
|
var a64FPArrBits = [a64ArrCount]uint32{
|
||||||
|
a64Arr2S: 0,
|
||||||
|
a64Arr4S: 1 << 30,
|
||||||
|
a64Arr2D: 1<<30 | 1<<22,
|
||||||
|
a64Arr4H: 0,
|
||||||
|
a64Arr8H: 1 << 30,
|
||||||
|
}
|
||||||
|
|
||||||
// a64SimdQOnly names the forms whose arrangement contributes the 128-bit
|
// a64SimdQOnly names the forms whose arrangement contributes the 128-bit
|
||||||
// flag alone, without the size bits: the FP converts, the FP round-to-integral
|
// flag alone, without the size bits: the FP converts, the FP round-to-integral
|
||||||
@@ -1049,77 +1115,149 @@ var a64ArrBits = [a64ArrCount]uint32{
|
|||||||
// instructions (word = base | arrBits | Rm<<16 | Rn<<5 | Rd). Every base
|
// instructions (word = base | arrBits | Rm<<16 | Rn<<5 | Rd). Every base
|
||||||
// word and arrangement bit was read off go tool asm.
|
// word and arrangement bit was read off go tool asm.
|
||||||
var a64SimdVTable = map[string]a64SimdVSpec{
|
var a64SimdVTable = map[string]a64SimdVSpec{
|
||||||
"VADD": {0x0e208400, 0x7f, false},
|
"VADD": {0x0e208400, 0x7f, false, false},
|
||||||
"VSUB": {0x2e208400, 0x7f, false},
|
"VSUB": {0x2e208400, 0x7f, false, false},
|
||||||
"VMUL": {0x0e209c00, 0x3f, false}, // no 2D: integer multiply stops at 4S
|
"VMUL": {0x0e209c00, 0x3f, false, false}, // no 2D: integer multiply stops at 4S
|
||||||
"VAND": {0x0e201c00, 0x03, false}, // logical ops accept 8B and 16B only
|
"VAND": {0x0e201c00, 0x03, false, false}, // logical ops accept 8B and 16B only
|
||||||
"VEOR": {0x2e201c00, 0x03, false},
|
"VEOR": {0x2e201c00, 0x03, false, false},
|
||||||
"VORR": {0x0ea01c00, 0x03, false},
|
"VORR": {0x0ea01c00, 0x03, false, false},
|
||||||
"VADDP": {0x0e20bc00, 0x7f, false},
|
"VADDP": {0x0e20bc00, 0x7f, false, false},
|
||||||
"VZIP1": {0x0e003800, 0x7f, false},
|
"VZIP1": {0x0e003800, 0x7f, false, false},
|
||||||
"VZIP2": {0x0e007800, 0x7f, false},
|
"VZIP2": {0x0e007800, 0x7f, false, false},
|
||||||
"VCMEQ": {0x2e208c00, 0x7f, false},
|
"VCMEQ": {0x2e208c00, 0x7f, false, false},
|
||||||
"VCMGE": {0x0e203c00, 0x7f, false},
|
"VCMGE": {0x0e203c00, 0x7f, false, false},
|
||||||
"VCMGT": {0x0e203400, 0x7f, false},
|
"VCMGT": {0x0e203400, 0x7f, false, false},
|
||||||
"VCMHI": {0x2e203400, 0x7f, false},
|
"VCMHI": {0x2e203400, 0x7f, false, false},
|
||||||
"VCMHS": {0x2e203c00, 0x7f, false},
|
"VCMHS": {0x2e203c00, 0x7f, false, false},
|
||||||
// FP compares take H, S and D arrangements only (the toolchain rejects
|
// FP compares take S and D arrangements only (the toolchain rejects the
|
||||||
// the byte forms), and VFCMLE/VFCMLT have no register form at all.
|
// byte and half forms), and VFCMLE/VFCMLT have no register form at all.
|
||||||
"VFCMEQ": {0x0e20e400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFCMEQ": {0x0e20e400, fpSimdArrs, false, true},
|
||||||
"VFCMGE": {0x2e20e400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFCMGE": {0x2e20e400, fpSimdArrs, false, true},
|
||||||
"VFCMGT": {0x2ea0e400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFCMGT": {0x2ea0e400, fpSimdArrs, false, true},
|
||||||
// FP arithmetic shares the same arrangement restriction.
|
// FP arithmetic shares the same arrangement restriction.
|
||||||
"VFADD": {0x0e20d400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFADD": {0x0e20d400, fpSimdArrs, false, true},
|
||||||
"VFSUB": {0x0ea0d400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFSUB": {0x0ea0d400, fpSimdArrs, false, true},
|
||||||
"VFMUL": {0x2e20dc00, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMUL": {0x2e20dc00, fpSimdArrs, false, true},
|
||||||
"VFDIV": {0x2e20fc00, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFDIV": {0x2e20fc00, fpSimdArrs, false, true},
|
||||||
"VFMAX": {0x0e20f400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMAX": {0x0e20f400, fpSimdArrs, false, true},
|
||||||
"VFMIN": {0x0ea0f400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMIN": {0x0ea0f400, fpSimdArrs, false, true},
|
||||||
"VFMAXNM": {0x0e20c400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMAXNM": {0x0e20c400, fpSimdArrs, false, true},
|
||||||
"VFMINNM": {0x0ea0c400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMINNM": {0x0ea0c400, fpSimdArrs, false, true},
|
||||||
"VFMLA": {0x0e20cc00, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMLA": {0x0e20cc00, fpSimdArrs, false, true},
|
||||||
"VFMLS": {0x0ea0cc00, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMLS": {0x0ea0cc00, fpSimdArrs, false, true},
|
||||||
// Saturating, halving, polynomial and pairwise arithmetic, the logical
|
// Saturating, halving, polynomial and pairwise arithmetic, the logical
|
||||||
// VBIT/VBSL family and the FP pairwise forms: word-verified against go
|
// VBIT/VBSL family and the FP pairwise forms: word-verified against go
|
||||||
// tool asm.
|
// tool asm.
|
||||||
"VBIC": {0x0e601c00, 0x7f, false},
|
"VBIC": {0x0e601c00, 0x03, false, false}, // logical ops accept 8B and 16B only
|
||||||
"VBIF": {0x2ee01c00, 0x7f, false},
|
"VBIF": {0x2ee01c00, 0x03, false, false},
|
||||||
"VBIT": {0x6ea01c00, 0x7f, false},
|
"VBIT": {0x6ea01c00, 0x03, false, false},
|
||||||
"VBSL": {0x6e601c00, 0x7f, false},
|
"VBSL": {0x6e601c00, 0x03, false, false},
|
||||||
"VCMTST": {0x0e208c00, 0x7f, false},
|
"VCMTST": {0x0e208c00, 0x7f, false, false},
|
||||||
"VFADDP": {0x2e20d400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFADDP": {0x2e20d400, fpSimdArrs, false, true},
|
||||||
"VFMAXP": {0x2e20f400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMAXP": {0x2e20f400, fpSimdArrs, false, true},
|
||||||
"VFMINP": {0x6ea0f400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMINP": {0x6ea0f400, fpSimdArrs, false, true},
|
||||||
"VFMAXNMP": {0x2e20c400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMAXNMP": {0x2e20c400, fpSimdArrs, false, true},
|
||||||
"VFMINNMP": {0x6ea0c400, 1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D, false},
|
"VFMINNMP": {0x6ea0c400, fpSimdArrs, false, true},
|
||||||
"VMLA": {0x4ea09400, 0x7f, false},
|
"VMLA": {0x4ea09400, 0x3f, false, false}, // no 2D: integer multiply stops at 4S
|
||||||
"VMLS": {0x6ea09400, 0x7f, false},
|
"VMLS": {0x6ea09400, 0x3f, false, false},
|
||||||
"VORN": {0x4ee01c00, 0x7f, false},
|
"VORN": {0x4ee01c00, 0x03, false, false},
|
||||||
"VSHADD": {0x4ea00400, 0x7f, false},
|
"VSHADD": {0x4ea00400, 0x7f, false, false},
|
||||||
"VSRHADD": {0x4ea01400, 0x7f, false},
|
"VSRHADD": {0x4ea01400, 0x7f, false, false},
|
||||||
"VUHADD": {0x6ea00400, 0x7f, false},
|
"VUHADD": {0x6ea00400, 0x7f, false, false},
|
||||||
"VURHADD": {0x6ea01400, 0x7f, false},
|
"VURHADD": {0x6ea01400, 0x7f, false, false},
|
||||||
"VSMAX": {0x4ea06400, 0x7f, false},
|
"VSMAX": {0x4ea06400, 0x3f, false, false}, // no 2D: integer max stops at 4S
|
||||||
"VSMIN": {0x4ea06c00, 0x7f, false},
|
"VSMIN": {0x4ea06c00, 0x3f, false, false},
|
||||||
"VSMAXP": {0x4ea0a400, 0x7f, false},
|
"VSMAXP": {0x4ea0a400, 0x3f, false, false},
|
||||||
"VSMINP": {0x4ea0ac00, 0x7f, false},
|
"VSMINP": {0x4ea0ac00, 0x3f, false, false},
|
||||||
"VUMAX": {0x2e206400, 0x7f, false},
|
"VUMAX": {0x2e206400, 0x3f, false, false},
|
||||||
"VUMIN": {0x2e206c00, 0x7f, false},
|
"VUMIN": {0x2e206c00, 0x3f, false, false},
|
||||||
"VUMAXP": {0x6ea0a400, 0x7f, false},
|
"VUMAXP": {0x6ea0a400, 0x3f, false, false},
|
||||||
"VUMINP": {0x6ea0ac00, 0x7f, false},
|
"VUMINP": {0x6ea0ac00, 0x3f, false, false},
|
||||||
"VSQADD": {0x4ea00c00, 0x7f, false},
|
"VSQADD": {0x4ea00c00, 0x7f, false, false},
|
||||||
"VUQADD": {0x6ea00c00, 0x7f, false},
|
"VUQADD": {0x6ea00c00, 0x7f, false, false},
|
||||||
"VSQSUB": {0x4ea02c00, 0x7f, false},
|
"VSQSUB": {0x4ea02c00, 0x7f, false, false},
|
||||||
"VUQSUB": {0x6ea02c00, 0x7f, false},
|
"VUQSUB": {0x6ea02c00, 0x7f, false, false},
|
||||||
"VSSHL": {0x4ee04400, 0x7f, false},
|
"VSSHL": {0x0e204400, 0x7f, false, false},
|
||||||
"VUSHL": {0x6ee04400, 0x7f, false},
|
"VUSHL": {0x2e204400, 0x7f, false, false},
|
||||||
"VUZP1": {0x0e001800, 0x7f, false},
|
"VUZP1": {0x0e001800, 0x7f, false, false},
|
||||||
"VUZP2": {0x4ec05800, 0x7f, false},
|
"VUZP2": {0x4ec05800, 0x7f, false, false},
|
||||||
"VTRN1": {0x4ec02800, 0x7f, false},
|
"VTRN1": {0x4ec02800, 0x7f, false, false},
|
||||||
"VTRN2": {0x4ec06800, 0x7f, false},
|
"VTRN2": {0x4ec06800, 0x7f, false, false},
|
||||||
"VRAX1": {0xce608c00, 1 << a64Arr2D, true}, // SHA3 group, D2 only
|
"VRAX1": {0xce608c00, 1 << a64Arr2D, true, false}, // SHA3 group, D2 only
|
||||||
"VPMULL": {0x0e20e000, 1<<a64Arr8B | 1<<a64ArrD1, false},
|
"VPMULL": {0x0e20e000, 1<<a64Arr8B | 1<<a64ArrD1, false, false},
|
||||||
"VPMULL2": {0x0e20e000, 1<<a64Arr16B | 1<<a64Arr2D, false},
|
"VPMULL2": {0x0e20e000, 1<<a64Arr16B | 1<<a64Arr2D, false, false},
|
||||||
|
// Saturating shifts, register forms (the immediate spellings route to
|
||||||
|
// a64FShiftImm).
|
||||||
|
"VSQSHL": {0x0e204c00, 0x7f, false, false},
|
||||||
|
"VUQSHL": {0x2e204c00, 0x7f, false, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdNLForm classifies the narrow/long/wide SIMD families whose
|
||||||
|
// arrangement does not travel on every operand: the encoding's size and Q
|
||||||
|
// bits read off one designated operand and the element widths pair up across
|
||||||
|
// the operands.
|
||||||
|
type a64SimdNLForm uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
a64NLTwoNarrow a64SimdNLForm = iota // (Vn.wide, Vd.narrow): size/Q from Vd
|
||||||
|
a64NLTwoLong // (Vn.narrow, Vd.long): size/Q from Vn
|
||||||
|
a64NLThreeLongMul // (Vm.narrow, Vn.narrow, Vd.long): size/Q from Vn
|
||||||
|
a64NLThreeWide // (Vm.narrow, Vn.wide, Vd.wide): size/Q from Vn
|
||||||
|
a64NLThreeLongShift // ($sh, Vn.narrow, Vd.long): size/Q from Vn, immh = esize+sh
|
||||||
|
a64NLThreeNarrowShift // ($sh, Vn.wide, Vd.narrow): size/Q from Vd, immh = esize-sh
|
||||||
|
)
|
||||||
|
|
||||||
|
// a64SimdNLSpec is one narrow/long/wide instruction: the base word (U, opcode
|
||||||
|
// and fixed bits positioned) and the arrangement form. qonly marks the FCVT
|
||||||
|
// family, whose size field is fixed in the base and only the Q bit follows
|
||||||
|
// the driving arrangement.
|
||||||
|
type a64SimdNLSpec struct {
|
||||||
|
base uint32
|
||||||
|
form a64SimdNLForm
|
||||||
|
qonly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SimdNLTable holds the families the arrangement-driven three-register and
|
||||||
|
// two-register encoders cannot express. The .2 spellings force the 128-bit
|
||||||
|
// side of the pair through their operand arrangements, so the base carries no
|
||||||
|
// arrangement bits of its own.
|
||||||
|
var a64SimdNLTable = map[string]a64SimdNLSpec{
|
||||||
|
"VSHRN": {0x0f008400, a64NLThreeNarrowShift, false},
|
||||||
|
"VSHRN2": {0x0f008400, a64NLThreeNarrowShift, false},
|
||||||
|
"VSXTL": {0x0f00a400, a64NLTwoLong, false},
|
||||||
|
"VSXTL2": {0x0f00a400, a64NLTwoLong, false},
|
||||||
|
"VUXTL": {0x2f00a400, a64NLTwoLong, false},
|
||||||
|
"VUXTL2": {0x2f00a400, a64NLTwoLong, false},
|
||||||
|
"VXTN": {0x0e212800, a64NLTwoNarrow, false},
|
||||||
|
"VXTN2": {0x0e212800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTN": {0x0e214800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTN2": {0x0e214800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTUN": {0x2e212800, a64NLTwoNarrow, false},
|
||||||
|
"VSQXTUN2": {0x2e212800, a64NLTwoNarrow, false},
|
||||||
|
"VUQXTN": {0x2e214800, a64NLTwoNarrow, false},
|
||||||
|
"VUQXTN2": {0x2e214800, a64NLTwoNarrow, false},
|
||||||
|
"VFCVTN": {0x0e616800, a64NLTwoNarrow, true},
|
||||||
|
"VFCVTN2": {0x0e616800, a64NLTwoNarrow, true},
|
||||||
|
"VFCVTL": {0x0e617800, a64NLTwoLong, true},
|
||||||
|
"VFCVTL2": {0x0e617800, a64NLTwoLong, true},
|
||||||
|
"VSSHLL": {0x0f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VSSHLL2": {0x0f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUSHLL": {0x2f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUSHLL2": {0x2f00a400, a64NLThreeLongShift, false},
|
||||||
|
"VUADDW": {0x2e201000, a64NLThreeWide, false},
|
||||||
|
"VUADDW2": {0x2e201000, a64NLThreeWide, false},
|
||||||
|
"VUMULL": {0x2e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VUMULL2": {0x2e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VSMULL": {0x0e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VSMULL2": {0x0e20c000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLAL": {0x2e208000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLAL2": {0x2e208000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLAL": {0x0e208000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLAL2": {0x0e208000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLSL": {0x2e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VUMLSL2": {0x2e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLSL": {0x0e20a000, a64NLThreeLongMul, false},
|
||||||
|
"VSMLSL2": {0x0e20a000, a64NLThreeLongMul, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64SimdVZero holds the compare-against-zero words of the SIMD compares
|
// a64SimdVZero holds the compare-against-zero words of the SIMD compares
|
||||||
@@ -1145,43 +1283,43 @@ var a64SimdVZero = map[string]uint32{
|
|||||||
// (word = base | arrBits | Rn<<5 | Rd). VMOV is served from here too, with
|
// (word = base | arrBits | Rn<<5 | Rd). VMOV is served from here too, with
|
||||||
// the register pair spelling ORR Vd, Vn, Vm.
|
// the register pair spelling ORR Vd, Vn, Vm.
|
||||||
var a64SimdV2Table = map[string]a64SimdVSpec{
|
var a64SimdV2Table = map[string]a64SimdVSpec{
|
||||||
"VREV32": {0x2e200800, 1<<a64Arr8B | 1<<a64Arr16B | 1<<a64Arr4H | 1<<a64Arr8H, false},
|
"VREV32": {0x2e200800, 1<<a64Arr8B | 1<<a64Arr16B | 1<<a64Arr4H | 1<<a64Arr8H, false, false},
|
||||||
"VREV64": {0x0e200800, 0x3f, false},
|
"VREV64": {0x0e200800, 0x3f, false, false},
|
||||||
"VREV16": {0x0e201800, 1<<a64Arr8B | 1<<a64Arr16B, false},
|
"VREV16": {0x0e201800, 1<<a64Arr8B | 1<<a64Arr16B, false, false},
|
||||||
"VUADDLV": {0x2e303800, 0x3f, false},
|
"VUADDLV": {0x2e303800, 0x3f, false, false},
|
||||||
"VMOV": {0x0ea01c00, 1<<a64Arr8B | 1<<a64Arr16B, false},
|
"VMOV": {0x0ea01c00, 1<<a64Arr8B | 1<<a64Arr16B, false, false},
|
||||||
// Two-register data-processing across one arrangement.
|
// Two-register data-processing across one arrangement.
|
||||||
"VABS": {0x0e20b800, 0x7f, false},
|
"VABS": {0x0e20b800, 0x7f, false, false},
|
||||||
"VNEG": {0x2e20b800, 0x7f, false},
|
"VNEG": {0x2e20b800, 0x7f, false, false},
|
||||||
"VCLS": {0x0e204800, 0x7f, false},
|
"VCLS": {0x0e204800, 0x7f, false, false},
|
||||||
"VCLZ": {0x2e204800, 0x7f, false},
|
"VCLZ": {0x2e204800, 0x7f, false, false},
|
||||||
"VCNT": {0x0e205800, 0x7f, false},
|
"VCNT": {0x0e205800, 0x7f, false, false},
|
||||||
"VNOT": {0x2e205800, 0x7f, false},
|
"VNOT": {0x2e205800, 0x7f, false, false},
|
||||||
"VSQABS": {0x0e207800, 0x7f, false},
|
"VSQABS": {0x0e207800, 0x7f, false, false},
|
||||||
"VSQNEG": {0x2e207800, 0x7f, false},
|
"VSQNEG": {0x2e207800, 0x7f, false, false},
|
||||||
"VRBIT": {0x6e605800, 0x7f, false},
|
"VRBIT": {0x2e605800, 0x03, false, false}, // 8B and 16B only
|
||||||
"VSCVTF": {0x4e21d800, fpSimdArrs, false},
|
"VSCVTF": {0x4e21d800, fpSimdArrs, false, true},
|
||||||
"VUCVTF": {0x6e21d800, fpSimdArrs, false},
|
"VUCVTF": {0x6e21d800, fpSimdArrs, false, true},
|
||||||
"VFCVTZS": {0x4ea1b800, fpSimdArrs, false},
|
"VFCVTZS": {0x4ea1b800, fpSimdArrs, false, true},
|
||||||
"VFCVTZU": {0x6ea1b800, fpSimdArrs, false},
|
"VFCVTZU": {0x6ea1b800, fpSimdArrs, false, true},
|
||||||
"VFABS": {0x0ea0f800, fpSimdArrs, false},
|
"VFABS": {0x0ea0f800, fpSimdArrs, false, true},
|
||||||
"VFNEG": {0x2ea0f800, fpSimdArrs, false},
|
"VFNEG": {0x2ea0f800, fpSimdArrs, false, true},
|
||||||
"VFSQRT": {0x2ea1f800, fpSimdArrs, false},
|
"VFSQRT": {0x2ea1f800, fpSimdArrs, false, true},
|
||||||
"VFRINTN": {0x0e218800, fpSimdArrs, false},
|
"VFRINTN": {0x0e218800, fpSimdArrs, false, true},
|
||||||
"VFRINTP": {0x0ea18800, fpSimdArrs, false},
|
"VFRINTP": {0x0ea18800, fpSimdArrs, false, true},
|
||||||
"VFRINTM": {0x0e219800, fpSimdArrs, false},
|
"VFRINTM": {0x0e219800, fpSimdArrs, false, true},
|
||||||
"VFRINTZ": {0x0ea19800, fpSimdArrs, false},
|
"VFRINTZ": {0x0ea19800, fpSimdArrs, false, true},
|
||||||
// Across-vector reductions: the operand arrangement rides as usual and
|
// Across-vector reductions: the operand arrangement rides as usual and
|
||||||
// the destination stays a bare V register.
|
// the destination stays a bare V register.
|
||||||
"VADDV": {0x0e31b800, 0x3f, false},
|
"VADDV": {0x0e31b800, 0x3f, false, false},
|
||||||
"VSMAXV": {0x0e30a800, 0x3f, false},
|
"VSMAXV": {0x0e30a800, 0x3f, false, false},
|
||||||
"VSMINV": {0x0e31a800, 0x3f, false},
|
"VSMINV": {0x0e31a800, 0x3f, false, false},
|
||||||
"VUMAXV": {0x2e30a800, 0x3f, false},
|
"VUMAXV": {0x2e30a800, 0x3f, false, false},
|
||||||
"VUMINV": {0x2e31a800, 0x3f, false},
|
"VUMINV": {0x2e31a800, 0x3f, false, false},
|
||||||
"VFMAXV": {0x2e30f800, fpAcrossArrs, false},
|
"VFMAXV": {0x2e30f800, fpAcrossArrs, false, false},
|
||||||
"VFMINV": {0x2eb0f800, fpAcrossArrs, false},
|
"VFMINV": {0x2eb0f800, fpAcrossArrs, false, false},
|
||||||
"VFMAXNMV": {0x2e30c800, fpAcrossArrs, false},
|
"VFMAXNMV": {0x2e30c800, fpAcrossArrs, false, false},
|
||||||
"VFMINNMV": {0x2eb0c800, fpAcrossArrs, false},
|
"VFMINNMV": {0x2eb0c800, fpAcrossArrs, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64CryptoArr is the arrangement each crypto instruction's operands must
|
// a64CryptoArr is the arrangement each crypto instruction's operands must
|
||||||
@@ -1243,6 +1381,16 @@ var a64PRFOps = map[string]int{
|
|||||||
var a64VLD1Base = [5]uint32{0, 0x0c407000, 0x0c40a000, 0x0c406000, 0x0c402000}
|
var a64VLD1Base = [5]uint32{0, 0x0c407000, 0x0c40a000, 0x0c406000, 0x0c402000}
|
||||||
var a64VST1Base = [5]uint32{0, 0x0c007000, 0x0c00a000, 0x0c006000, 0x0c002000}
|
var a64VST1Base = [5]uint32{0, 0x0c007000, 0x0c00a000, 0x0c006000, 0x0c002000}
|
||||||
|
|
||||||
|
// a64VLDNBase and a64VSTNBase hold the VLD2/VLD3/VLD4 and VST2/VST3/VST4
|
||||||
|
// fixed words (indexed by register count 2..4): the opcode field at bits
|
||||||
|
// 15:12 carries the access kind.
|
||||||
|
var a64VLDNBase = [5]uint32{0, 0, 0x0c408000, 0x0c404000, 0x0c400000}
|
||||||
|
var a64VSTNBase = [5]uint32{0, 0, 0x0c008000, 0x0c004000, 0x0c000000}
|
||||||
|
|
||||||
|
// a64VLDNReplicate holds the VLD2R/VLD3R fixed words beside the existing
|
||||||
|
// VLD1R (0x0d40c000) and VLD4R (0x0d60e000) bases.
|
||||||
|
var a64VLDNReplicate = [5]uint32{0, 0x0d40c000, 0x0d60c000, 0x0d40e000, 0x0d60e000}
|
||||||
|
|
||||||
// a64Vec is a parsed vector operand: the register number, the arrangement
|
// a64Vec is a parsed vector operand: the register number, the arrangement
|
||||||
// ("" when the operand spells none) and, for element forms, the lane index.
|
// ("" when the operand spells none) and, for element forms, the lane index.
|
||||||
type a64Vec struct {
|
type a64Vec struct {
|
||||||
@@ -1363,32 +1511,49 @@ func a64VecListOf(ops []*ast.Operand, start int) (vs []a64Vec, end int, ok bool)
|
|||||||
|
|
||||||
// a64LSType describes the load/store parameters for a MOV width mnemonic.
|
// a64LSType describes the load/store parameters for a MOV width mnemonic.
|
||||||
type a64LSType struct {
|
type a64LSType struct {
|
||||||
size int // 0=byte, 1=half, 2=word, 3=dword
|
size int // 0=byte, 1=half, 2=word, 3=dword
|
||||||
V int // 0=integer, 1=FP
|
V int // 0=integer, 1=FP
|
||||||
opc int // 00=store/unsigned load, 01=store FP, 10=signed load, 11=load FP
|
opc int // 00=store/unsigned load, 01=store FP, 10=signed load, 11=load FP
|
||||||
|
scale int // access width in bytes; the unsigned offset divides by it
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64LoadTable maps MOV width mnemonics to their load/store encoding parameters.
|
// a64LoadTable maps MOV width mnemonics to their load/store encoding parameters.
|
||||||
// For loads, opc selects signed vs unsigned; for stores, we flip the opc.
|
// For loads, opc selects signed vs unsigned; for stores, we flip the opc.
|
||||||
var a64LoadTable = map[string]a64LSType{
|
var a64LoadTable = map[string]a64LSType{
|
||||||
"MOVD": {3, 0, 1}, // LDR X (64-bit, unsigned offset)
|
"MOVD": {3, 0, 1, 8}, // LDR X (64-bit, unsigned offset)
|
||||||
"MOVWU": {2, 0, 1}, // LDR W (32-bit unsigned)
|
"MOVWU": {2, 0, 1, 4}, // LDR W (32-bit unsigned)
|
||||||
"MOVW": {2, 0, 2}, // LDRSW (32-bit signed → 64-bit)
|
"MOVW": {2, 0, 2, 4}, // LDRSW (32-bit signed → 64-bit)
|
||||||
"MOVHU": {1, 0, 1}, // LDRH (16-bit unsigned)
|
"MOVHU": {1, 0, 1, 2}, // LDRH (16-bit unsigned)
|
||||||
"MOVH": {1, 0, 2}, // LDRSH (16-bit signed)
|
"MOVH": {1, 0, 2, 2}, // LDRSH (16-bit signed)
|
||||||
"MOVBU": {0, 0, 1}, // LDRB (8-bit unsigned)
|
"MOVBU": {0, 0, 1, 1}, // LDRB (8-bit unsigned)
|
||||||
"MOVB": {0, 0, 2}, // LDRSB (8-bit signed)
|
"MOVB": {0, 0, 2, 1}, // LDRSB (8-bit signed)
|
||||||
"FMOVS": {2, 1, 1}, // LDR S (32-bit FP)
|
"FMOVS": {2, 1, 1, 4}, // LDR S (32-bit FP)
|
||||||
"FMOVD": {3, 1, 1}, // LDR D (64-bit FP)
|
"FMOVD": {3, 1, 1, 8}, // LDR D (64-bit FP)
|
||||||
|
"FMOVQ": {0, 1, 3, 16}, // LDR/STR Q (128-bit FP): opc=11 selects it
|
||||||
}
|
}
|
||||||
|
|
||||||
// a64StoreOpc returns the store opc for a given load type: integer and FP
|
// a64StoreOpc returns the store opc for a given load type: integer and FP
|
||||||
// stores both encode opc=00 (the load's signedness bit sits in opc[1], which
|
// stores both encode opc=00 (the load's signedness bit sits in opc[1], which
|
||||||
// the store form clears; FP registers are selected by V, not opc).
|
// the store form clears; FP registers are selected by V, not opc). The one
|
||||||
|
// exception is the 128-bit Q width, whose store is the opc=10 spelling: with
|
||||||
|
// opc=00 the size field selects STR B instead (STR Qt is size=00, opc=10).
|
||||||
func a64StoreOpc(t a64LSType) int {
|
func a64StoreOpc(t a64LSType) int {
|
||||||
|
if t.size == 0 && t.V == 1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a64LSScale returns the byte width an access's unsigned offset divides by.
|
||||||
|
// The Q width carries its size in opc (the size field stays 0), yet scales
|
||||||
|
// by 16 like any other 128-bit access, so the exponent alone does not answer.
|
||||||
|
func a64LSScale(t a64LSType) int64 {
|
||||||
|
if t.size == 0 && t.V == 1 {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
return int64(1) << uint(t.size)
|
||||||
|
}
|
||||||
|
|
||||||
// arm64RegClass discriminates integer (R), floating-point (F) registers for
|
// arm64RegClass discriminates integer (R), floating-point (F) registers for
|
||||||
// the MOV pseudo-instruction.
|
// the MOV pseudo-instruction.
|
||||||
type arm64RegClass int
|
type arm64RegClass int
|
||||||
|
|||||||
+1076
-9
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
+115
-11
@@ -53,7 +53,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// arm64FrameInfo holds the frame layout derived from a TEXT directive.
|
// arm64FrameInfo holds the frame layout derived from a TEXT directive.
|
||||||
@@ -69,6 +69,12 @@ type arm64FrameInfo struct {
|
|||||||
// autosize below StackSmall as NOSPLIT.
|
// autosize below StackSmall as NOSPLIT.
|
||||||
needSplit bool
|
needSplit bool
|
||||||
splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig
|
splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig
|
||||||
|
|
||||||
|
// tls holds the names the file's own GLOBL declarations mark TLSBSS:
|
||||||
|
// the toolchain's aclass keys the TLS-LE load off the symbol's type
|
||||||
|
// (objabi.STLSBSS), which only the file's declarations reveal. A nil
|
||||||
|
// map answers no, the plain static-symbol load.
|
||||||
|
tls map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// arm64ComputeFrame derives the frame layout for a TEXT function.
|
// arm64ComputeFrame derives the frame layout for a TEXT function.
|
||||||
@@ -87,18 +93,28 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
|
|||||||
if fi.frame != 0 || !fi.leaf {
|
if fi.frame != 0 || !fi.leaf {
|
||||||
fi.autosize = fi.frame + 8 // space for the saved LR
|
fi.autosize = fi.frame + 8 // space for the saved LR
|
||||||
// The toolchain always adds an extrasize: 8 when the total leaves a
|
// The toolchain always adds an extrasize: 8 when the total leaves a
|
||||||
// 16-byte alignment gap, another 16 when already aligned.
|
// 16-byte alignment gap, another 16 when already aligned. An
|
||||||
switch fi.autosize % 16 {
|
// autosize of zero (the $-8 convention included) is frameless and
|
||||||
case 8:
|
// takes neither.
|
||||||
fi.autosize += 8
|
if fi.autosize != 0 {
|
||||||
case 0:
|
switch fi.autosize % 16 {
|
||||||
fi.autosize += 16
|
case 8:
|
||||||
default:
|
fi.autosize += 8
|
||||||
// The toolchain rejects unaligned frames; round up so such
|
case 0:
|
||||||
// sources still assemble.
|
fi.autosize += 16
|
||||||
fi.autosize += 16 - (fi.autosize % 16)
|
default:
|
||||||
|
// The toolchain rejects unaligned frames; round up so such
|
||||||
|
// sources still assemble.
|
||||||
|
fi.autosize += 16 - (fi.autosize % 16)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fi.autosize == 0 {
|
||||||
|
// The NOFRAME shape: the toolchain forces the leaf mark on any
|
||||||
|
// autosize-zero function (calls included), so nothing saves LR and
|
||||||
|
// no stack-split guard runs.
|
||||||
|
fi.leaf = true
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case fi.noSplit:
|
case fi.noSplit:
|
||||||
case fi.autosize < stackSmall && fi.leaf:
|
case fi.autosize < stackSmall && fi.leaf:
|
||||||
@@ -301,6 +317,37 @@ func arm64Return(fi arm64FrameInfo) []byte {
|
|||||||
return a64WordsLE(ws...)
|
return a64WordsLE(ws...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64RetInstr encodes a RET. The plain form runs the frame epilogue and
|
||||||
|
// branches to LR; RET Rn runs the epilogue and branches to the register
|
||||||
|
// (asm7.go case 78); RET sym(SB) runs the epilogue and branches to the
|
||||||
|
// symbol with the call relocation, the toolchain's retJMP tail call.
|
||||||
|
func arm64RetInstr(fi arm64FrameInfo, ops []*ast.Operand, relocs *[]Reloc) []byte {
|
||||||
|
out := arm64Return(fi)
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
op := ops[0]
|
||||||
|
switch {
|
||||||
|
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" && op.Addr.Base == "" && op.Addr.Index == "":
|
||||||
|
if rn := arm64RegNum(op.Addr.Sym.Name); rn >= 0 {
|
||||||
|
// The operand form replaces the default BR LR word.
|
||||||
|
return append(out[:len(out)-4], a64wordLE(0xd65f0000|uint32(rn)<<5)...)
|
||||||
|
}
|
||||||
|
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" && op.Addr.Base == "" && op.Addr.Index == "":
|
||||||
|
if relocs != nil {
|
||||||
|
*relocs = append(*relocs, Reloc{
|
||||||
|
Off: 0,
|
||||||
|
After: 4,
|
||||||
|
Name: op.Addr.Sym.Name,
|
||||||
|
Addend: op.Addr.Sym.Offset,
|
||||||
|
Kind: RelArm64Branch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return append(out[:len(out)-4], a64wordLE(0x14000000)...) // B sym(SB)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// arm64PrologueSpadjPC returns the function-relative byte offset where the
|
// arm64PrologueSpadjPC returns the function-relative byte offset where the
|
||||||
// prologue has finished decrementing SP (the delta becomes autosize).
|
// prologue has finished decrementing SP (the delta becomes autosize).
|
||||||
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
||||||
@@ -362,6 +409,59 @@ func arm64ResolvePseudo(sym *ast.Symbol, fi arm64FrameInfo) (base int, off int32
|
|||||||
return -1, 0
|
return -1, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrValue returns the SP-relative displacement a $sym+off(FP)
|
||||||
|
// or $sym+off(SP) immediate-address operand stands for, the toolchain's
|
||||||
|
// aclass arithmetic (asm7.go): a parameter reference sits autosize+8 above
|
||||||
|
// the hardware SP, and a pseudo-SP reference sits frame+8 above it, the
|
||||||
|
// alignment padding cancelling out of the autosize.
|
||||||
|
func arm64FrameAddrValue(sym *ast.Symbol, fi arm64FrameInfo) int64 {
|
||||||
|
switch sym.Pseudo {
|
||||||
|
case "FP":
|
||||||
|
return sym.Offset + int64(fi.autosize) + 8
|
||||||
|
default: // SP
|
||||||
|
return sym.Offset + int64(fi.frame) + 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64IsAddcon reports whether v is an addcon value (asm7.go isaddcon): an
|
||||||
|
// unsigned imm12, or a multiple of 4096 whose shifted form fits imm12.
|
||||||
|
func arm64IsAddcon(v int64) bool {
|
||||||
|
if v < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v&0xFFF == 0 {
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return v <= 0xFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrWords returns the word sequence of the toolchain's optab
|
||||||
|
// case 4 for a frame-relative address (the C_AACON and C_AACON2 rows): one
|
||||||
|
// ADD/SUB imm12 word inside the addcon band, SUB carrying a negative
|
||||||
|
// displacement, and otherwise the hi<<12 word from SP followed by the low
|
||||||
|
// word added in place. The 24-bit band never reaches the REGTMP
|
||||||
|
// materialisation the ADD/SUB immediate ladder uses: aclass classifies the
|
||||||
|
// address straight into C_AACON2.
|
||||||
|
func arm64FrameAddrWords(v int64, rd int) []uint32 {
|
||||||
|
word := func(v int64, rn int) uint32 {
|
||||||
|
op := uint32(0) // ADD
|
||||||
|
if v < 0 {
|
||||||
|
op = 1 // SUB
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
sh := uint32(0)
|
||||||
|
if v&0xFFF000 != 0 { // asm7.go oaddi: the shift form when low 12 bits are clear
|
||||||
|
sh = 1
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return a64AddSub(1, op, 0, sh, uint32(v), uint32(rn), uint32(rd))
|
||||||
|
}
|
||||||
|
if arm64IsAddcon(v) || arm64IsAddcon(-v) {
|
||||||
|
return []uint32{word(v, 31)}
|
||||||
|
}
|
||||||
|
return []uint32{word(v&^int64(0xFFF), 31), word(v&0xFFF, rd)}
|
||||||
|
}
|
||||||
|
|
||||||
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
||||||
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
||||||
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
||||||
@@ -454,6 +554,10 @@ func arm64GuardBytes(fi arm64FrameInfo, blockStart int) []byte {
|
|||||||
return a64WordsLE(ws...)
|
return a64WordsLE(ws...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64MoreStackBlockLen is the byte length of arm64MoreStackBlock: the
|
||||||
|
// saved LR, the BL and the branch back, three words whatever the target.
|
||||||
|
const arm64MoreStackBlockLen = 12
|
||||||
|
|
||||||
// arm64MoreStackBlock emits the trailing block: MOVD R30, R3 (save LR),
|
// arm64MoreStackBlock emits the trailing block: MOVD R30, R3 (save LR),
|
||||||
// BL runtime.morestack_noctxt, B back to the function start. The BL carries
|
// BL runtime.morestack_noctxt, B back to the function start. The BL carries
|
||||||
// the R_CALLARM64 relocation.
|
// the R_CALLARM64 relocation.
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assembleArm64Source parses src and assembles it for arm64, returning the
|
||||||
|
// first function's words little-endian.
|
||||||
|
func assembleArm64Source(t *testing.T, src string) []uint32 {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("test_arm64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("got %d funcs, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
return wordsOf(img.Code[img.Funcs[0].Offset : img.Funcs[0].Offset+img.Funcs[0].Size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrEncoding(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·fr(SB), NOSPLIT, $432-24
|
||||||
|
MOVD $argframe+0(FP), R3
|
||||||
|
MOVD $big+4096(FP), R4
|
||||||
|
MOVD $ret-8(FP), R2
|
||||||
|
MOVD $x-64(FP), R5
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// Prologue (4: large frame) then the body at words 4..9, the toolchain's
|
||||||
|
// own encodings for the same statements:
|
||||||
|
// ADD $456, RSP, R3 (456 = 448 + 8 + 0)
|
||||||
|
// ADD $(1<<12), RSP, R4 (4552, the hi<<12 half)
|
||||||
|
// ADD $456, R4, R4 (then the lo half)
|
||||||
|
// ADD $448, RSP, R2 (448 - 8 + 8)
|
||||||
|
// ADD $392, RSP, R5 (448 - 64 + 8)
|
||||||
|
// ADD $0, RSP, R19 (the SP register move)
|
||||||
|
// ADD $0, R20, RSP
|
||||||
|
want := []uint32{
|
||||||
|
0xd10703f4, 0xa93ffa9d, 0x9100029f, 0xd10023fd,
|
||||||
|
0x910723e3, 0x914007e4, 0x91072084, 0x910703e2,
|
||||||
|
0x910623e5, 0x910003f3, 0x9100029f,
|
||||||
|
}
|
||||||
|
if len(ws) < len(want) {
|
||||||
|
t.Fatalf("got %d words, want at least %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrSizes(t *testing.T) {
|
||||||
|
// One imm12 word inside the addcon band, two inside the 24-bit band.
|
||||||
|
tests := []struct {
|
||||||
|
v int64
|
||||||
|
verb int
|
||||||
|
}{
|
||||||
|
{456, 1},
|
||||||
|
{0xFFF, 1},
|
||||||
|
{0x1000, 1}, // the shifted imm12 form
|
||||||
|
{0x1005, 2}, // the hi<<12 plus lo pair
|
||||||
|
{0xFFFFFF, 2},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := len(arm64FrameAddrWords(tt.v, 3))
|
||||||
|
if got != tt.verb {
|
||||||
|
t.Errorf("arm64FrameAddrWords(%d) took %d words, want %d", tt.v, got, tt.verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ws := arm64FrameAddrWords(0x1000, 3); len(ws) != 1 || ws[0] != 0x914007e3 {
|
||||||
|
t.Errorf("arm64FrameAddrWords(0x1000) = %08x, want the shifted ADD 914007e3", ws[0])
|
||||||
|
}
|
||||||
|
if !arm64IsAddcon(0xFFF) || arm64IsAddcon(0x1001) || arm64IsAddcon(-1) {
|
||||||
|
t.Error("arm64IsAddcon misclassifies the band edges")
|
||||||
|
}
|
||||||
|
fp := &ast.Symbol{Name: "x", Pseudo: "FP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(fp, arm64FrameInfo{autosize: 448}); v != 464 {
|
||||||
|
t.Errorf("FP address: got %d, want 464", v)
|
||||||
|
}
|
||||||
|
sp := &ast.Symbol{Name: "x", Pseudo: "SP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(sp, arm64FrameInfo{frame: 432}); v != 448 {
|
||||||
|
t.Errorf("SP address: got %d, want 448", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrRejects(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"width", `TEXT ·f(SB), NOSPLIT, $16-8
|
||||||
|
MOVW $x+0(FP), R7
|
||||||
|
RET
|
||||||
|
`, "illegal combination",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pool band", `TEXT ·f(SB), NOSPLIT, $20000000-8
|
||||||
|
MOVD $x+20000000(FP), R7
|
||||||
|
RET
|
||||||
|
`, "literal pool",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("test_arm64.s", tt.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
_, err := AssembleFileARM64(f)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%s: no error, want one naming %q", tt.name, tt.want)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("%s: error %q, want it to name %q", tt.name, err, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64SPMoveEncoding(t *testing.T) {
|
||||||
|
src := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
MOVD ZR, R4
|
||||||
|
MOVD RSP, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// The SP register moves ride ADD $0; the zero move stays ORR.
|
||||||
|
want := []uint32{
|
||||||
|
0x910003f3, // ADD $0, RSP, R19
|
||||||
|
0x9100029f, // ADD $0, R20, RSP
|
||||||
|
0xaa1f03e4, // ORR R4, ZR, ZR
|
||||||
|
0x910003ff, // ADD $0, RSP, RSP
|
||||||
|
0xd65f03c0, // RET
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rej := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVW RSP, R7
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("test_arm64.s", rej)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFileARM64(f); err == nil || !strings.Contains(err.Error(), "illegal combination") {
|
||||||
|
t.Errorf("MOVW RSP: error %v, want an illegal-combination refusal", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64AliasLiveness(t *testing.T) {
|
||||||
|
// A define inside a dead conditional branch must not become an alias:
|
||||||
|
// go_tls.h's `#ifdef GOARCH_arm` block defines LR as R14, which would
|
||||||
|
// silently renumber the link register on arm64, where LR is R30.
|
||||||
|
src := `#define RARG R5
|
||||||
|
#ifdef GOARCH_arm
|
||||||
|
#define LR R14
|
||||||
|
#endif
|
||||||
|
TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD LR, R0
|
||||||
|
MOVD RARG, R1
|
||||||
|
MOVD R14, R2
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0xaa1e03e0, // ORR R0, ZR, R30: LR stayed the link register
|
||||||
|
0xaa0503e1, // ORR R1, ZR, R5: the live alias applied
|
||||||
|
0xaa0e03e2, // ORR R2, ZR, R14: R14 is R14
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64ADRNoChainChase(t *testing.T) {
|
||||||
|
// The toolchain's jump-to-jump collapse rewrites branch targets only:
|
||||||
|
// a B to a chain-leading label is redirected, an ADR to the same label
|
||||||
|
// resolves to the label itself.
|
||||||
|
src := `TEXT ·adrchain(SB), NOSPLIT, $0-0
|
||||||
|
B a
|
||||||
|
a:
|
||||||
|
B b
|
||||||
|
b:
|
||||||
|
ADR a, R0
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0x14000002, // B +2: chased through a to b
|
||||||
|
0x14000001, // B +1: a's own jump to b
|
||||||
|
0x10ffffe0, // ADR a, R0: -4, the label itself, unchased
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var le = binary.LittleEndian
|
||||||
|
|
||||||
|
// kindSTEXTFIPS is objabi's STEXTFIPS: the fips140 packages' text kind.
|
||||||
|
const kindSTEXTFIPS = 2
|
||||||
|
|
||||||
|
// gorootARM64Packages names the GOROOT packages whose arm64 assembly the
|
||||||
|
// parity harness pins: every *_arm64.s of each package, as the real build
|
||||||
|
// assembles it, the package's generated go_asm.h included. Together they
|
||||||
|
// carry the heavy real-world shapes: the runtime's TLS and stack plumbing,
|
||||||
|
// the cryptographic kernels, big-number arithmetic and the bytealg search
|
||||||
|
// loops.
|
||||||
|
var gorootARM64Packages = []string{
|
||||||
|
"runtime",
|
||||||
|
"internal/bytealg",
|
||||||
|
"internal/cpu",
|
||||||
|
"internal/chacha8rand",
|
||||||
|
"internal/runtime/maps",
|
||||||
|
"reflect",
|
||||||
|
"math/big",
|
||||||
|
"hash/crc32",
|
||||||
|
"crypto/md5",
|
||||||
|
"crypto/sha1",
|
||||||
|
"crypto/internal/fips140/aes",
|
||||||
|
"crypto/internal/fips140/aes/gcm",
|
||||||
|
"crypto/internal/fips140/bigmod",
|
||||||
|
"crypto/internal/fips140/nistec",
|
||||||
|
"crypto/internal/fips140/sha256",
|
||||||
|
"crypto/internal/fips140/sha512",
|
||||||
|
"crypto/internal/fips140/sha3",
|
||||||
|
"crypto/internal/fips140/subtle",
|
||||||
|
}
|
||||||
|
|
||||||
|
// gorootARM64GapFiles names the files kept out of the byte parity set by
|
||||||
|
// known, pre-existing gaps, each with the reason. A file here is skipped,
|
||||||
|
// not silently dropped: the gaps are findings, and closing one is a matter
|
||||||
|
// of removing its entry and watching the file pin itself.
|
||||||
|
var gorootARM64GapFiles = map[string]string{
|
||||||
|
"runtime/asm_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge",
|
||||||
|
"runtime/sys_linux_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge (cgoSigtramp, clone)",
|
||||||
|
"runtime/preempt_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge (asyncPreempt)",
|
||||||
|
"runtime/race_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue shape diverges (racecallbackthunk)",
|
||||||
|
"runtime/rt0_linux_arm64.s": "the #ifdef GOOS selection diverges: gasm keeps a word the toolchain drops",
|
||||||
|
}
|
||||||
|
|
||||||
|
// gorootOtherGOOS matches the file names of the ports the linux build never
|
||||||
|
// assembles: the harness pins the linux arm64 set.
|
||||||
|
var gorootOtherGOOS = regexp.MustCompile(`_(darwin|ios|freebsd|netbsd|openbsd|windows|android|plan9|aix|js|wasip1)_`)
|
||||||
|
|
||||||
|
// TestGOROOTARM64Parity assembles each package's arm64 files with gasm and
|
||||||
|
// with the installed toolchain and holds the functions' bytes equal,
|
||||||
|
// relocation sites masked. The toolchain side needs the go_asm.h the build
|
||||||
|
// generates for the package, so the harness rebuilds it once with -work and
|
||||||
|
// harvests the header the compiler wrote; the -gcflags flag exists only to
|
||||||
|
// make that rebuild happen, the header's constants do not depend on it.
|
||||||
|
func TestGOROOTARM64Parity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle and per-package rebuild: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
out, err := exec.Command(goBin, "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("go env GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
goroot := strings.TrimSpace(string(out))
|
||||||
|
include := filepath.Join(goroot, "pkg", "include")
|
||||||
|
|
||||||
|
totalFns, totalBytes := 0, 0
|
||||||
|
for _, pkg := range gorootARM64Packages {
|
||||||
|
t.Run(pkg, func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
work := harvestGoAsm(t, goBin, pkg)
|
||||||
|
defer os.RemoveAll(work)
|
||||||
|
headers, err := filepath.Glob(filepath.Join(work, "b*", "go_asm.h"))
|
||||||
|
if err != nil || len(headers) == 0 {
|
||||||
|
t.Fatalf("no generated go_asm.h under %s", work)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go_asm.h"), mustRead(t, headers[0]), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
files, err := filepath.Glob(filepath.Join(goroot, "src", pkg, "*_arm64.s"))
|
||||||
|
if err != nil || len(files) == 0 {
|
||||||
|
t.Fatalf("no arm64 assembly found for %s", pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
base := filepath.Base(f)
|
||||||
|
if gorootOtherGOOS.MatchString(base) {
|
||||||
|
continue // another port's file: the linux build never assembles it
|
||||||
|
}
|
||||||
|
t.Run(base, func(t *testing.T) {
|
||||||
|
if reason, gap := gorootARM64GapFiles[pkg+"/"+base]; gap {
|
||||||
|
t.Skip(reason)
|
||||||
|
}
|
||||||
|
// The parser side: the file's own directory resolves the
|
||||||
|
// package's headers, the harvested directory carries
|
||||||
|
// go_asm.h, and the platform conditionals read the same
|
||||||
|
// predefines the go command drives go tool asm with.
|
||||||
|
src := string(mustRead(t, f))
|
||||||
|
af, errs := parser.ParseWithOptions(f, src, parser.Options{
|
||||||
|
Expand: true,
|
||||||
|
IncludeDirs: []string{dir, filepath.Join(goroot, "src", pkg), include},
|
||||||
|
Predefines: map[string]string{
|
||||||
|
"GOARCH_arm64": "1",
|
||||||
|
"GOOS_linux": "1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(af)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The oracle side: the build's own invocation, the
|
||||||
|
// generated header directory first.
|
||||||
|
objPath := filepath.Join(t.TempDir(), "oracle.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm",
|
||||||
|
"-I", dir, "-I", filepath.Join(goroot, "src", pkg), "-I", include,
|
||||||
|
"-D", "GOOS_linux", "-D", "GOARCH_arm64", "-std",
|
||||||
|
"-p", pkg, "-o", objPath, f)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
if oout, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("go tool asm %s: %v\n%s", base, err, oout)
|
||||||
|
}
|
||||||
|
byLocal := oracleFuncText(t, mustRead(t, objPath))
|
||||||
|
|
||||||
|
// The object's symdef order is the source order, gasm's
|
||||||
|
// function list too, so same-named functions pair up in
|
||||||
|
// definition order: a file-local kernel beside its
|
||||||
|
// package-level twin (runtime·racefuncenter and
|
||||||
|
// racefuncenter<>) carries the plain name twice in the
|
||||||
|
// object and the reader cannot see the locality.
|
||||||
|
seen := map[string]int{}
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
bodies := byLocal[fn.Name]
|
||||||
|
idx := seen[fn.Name]
|
||||||
|
seen[fn.Name] = idx + 1
|
||||||
|
if idx >= len(bodies) {
|
||||||
|
keys := make([]string, 0, len(byLocal))
|
||||||
|
for name := range byLocal {
|
||||||
|
keys = append(keys, name)
|
||||||
|
}
|
||||||
|
t.Errorf("%s: not in the oracle output (%d functions: %s)",
|
||||||
|
fn.Name, len(byLocal), strings.Join(keys, ", "))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
goCode := maskCode(append([]byte(nil), bodies[idx]...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
for w := 0; w < cmpLen/4; w++ {
|
||||||
|
g := le.Uint32(gasmCode[w*4:])
|
||||||
|
o := le.Uint32(goCode[w*4:])
|
||||||
|
if g != o {
|
||||||
|
t.Errorf("%s: word %d (offset %d) differs: gasm %08x go %08x", fn.Name, w, w*4, g, o)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(goCode) > len(gasmCode) {
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Errorf("%s: non-zero trailing bytes in the oracle output", fn.Name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalFns++
|
||||||
|
totalBytes += len(gasmCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Logf("GOROOT arm64 parity: %d functions, %d bytes identical", totalFns, totalBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracleFuncText extracts every TEXT function of a toolchain object, the
|
||||||
|
// non-package and the hashed (file-local) definitions both, keyed by the
|
||||||
|
// local name: GOROOT keeps several kernels file-local (cmpbody<>,
|
||||||
|
// encryptBlockAsm<>), and those ride the hashed definition blocks the
|
||||||
|
// non-package reader never sees. The value is the functions' bodies in
|
||||||
|
// symdef order: a file-local kernel beside its package-level twin carries
|
||||||
|
// the same plain name twice (the object reader cannot see the locality),
|
||||||
|
// and the encoder pairs them up in definition order.
|
||||||
|
func oracleFuncText(t *testing.T, obj []byte) map[string][][]byte {
|
||||||
|
t.Helper()
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
data := v.blk(blkData)
|
||||||
|
didx := v.blk(blkDataIdx)
|
||||||
|
out := make(map[string][][]byte)
|
||||||
|
di := 0
|
||||||
|
for _, bi := range []int{blkSymdef, blkHashed64def, blkHasheddef, blkNonpkgdef} {
|
||||||
|
for _, s := range v.syms(bi) {
|
||||||
|
// STEXT and STEXTFIPS both: the fips140 packages' text carries
|
||||||
|
// the FIPS kind in Go 1.27 and up.
|
||||||
|
if (s.typ == kindSTEXT || s.typ == kindSTEXTFIPS) && s.size > 0 && 4*di+8 <= len(didx) {
|
||||||
|
off := le.Uint32(didx[4*di:])
|
||||||
|
if int(off)+int(s.size) <= len(data) {
|
||||||
|
name := s.name
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
name = after
|
||||||
|
}
|
||||||
|
out[name] = append(out[name], data[off:int(off)+int(s.size)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
di++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// harvestGoAsm rebuilds pkg once with -work and returns the work directory
|
||||||
|
// holding the compiler's generated go_asm.h. A fully cached build leaves
|
||||||
|
// the work directory empty, so the compile action is given a unique, inert
|
||||||
|
// flag value each run (the inlining level never touches the header's
|
||||||
|
// constants) and re-runs for the target package alone.
|
||||||
|
func harvestGoAsm(t *testing.T, goBin, pkg string) string {
|
||||||
|
t.Helper()
|
||||||
|
nonce := time.Now().UnixNano() % 1000000
|
||||||
|
cmd := exec.Command(goBin, "build", "-x", "-work",
|
||||||
|
"-gcflags", pkg+"=-N", "-gcflags", pkg+"=-l=7"+strconv.FormatInt(nonce, 10),
|
||||||
|
"-o", "/dev/null", pkg)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rebuild %s: %v\n%s", pkg, err, out)
|
||||||
|
}
|
||||||
|
m := regexp.MustCompile(`WORK=(\S+)`).FindSubmatch(out)
|
||||||
|
if m == nil {
|
||||||
|
t.Fatalf("rebuild %s: no WORK directory in the build log", pkg)
|
||||||
|
}
|
||||||
|
return string(m[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustRead reads path, failing the test when it cannot.
|
||||||
|
func mustRead(t *testing.T, path string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The mid-function literal pool flush, pinned against the toolchain. A
|
||||||
|
// kernel of distinct pooled stores walks the conservative displacement bound
|
||||||
|
// (asm7.go's maxPCDisp) inside one body, so the pool must drain exactly
|
||||||
|
// where cmd/internal/obj/arm64's checkpool drains it: the bytes, the flush
|
||||||
|
// points and the reach of every load literal are the toolchain's own.
|
||||||
|
|
||||||
|
// pooledKernel renders a kernel of n distinct pooled stores: each offset
|
||||||
|
// sits a step of 8 past the aligned split band (every fourth one aligned,
|
||||||
|
// but 0x2000000+8i+4 never is), so every statement pools its own four-byte
|
||||||
|
// word and expands to eight instruction bytes, the densest walk towards the
|
||||||
|
// distance bound an arm64 body can make. head carries the TEXT line for the
|
||||||
|
// splitting variant, tail the closing statements.
|
||||||
|
func pooledKernel(head string, n int, tail string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("#include \"textflag.h\"\n")
|
||||||
|
if head != "" {
|
||||||
|
b.WriteString(head)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
b.WriteString("TEXT \u00b7poolmid(SB), NOSPLIT, $0-0\n")
|
||||||
|
for i := range n {
|
||||||
|
b.WriteString("\tMOVD\tR1, ")
|
||||||
|
b.WriteString(decimal(0x2000000 + 8*i + 4))
|
||||||
|
b.WriteString("(R2)\n")
|
||||||
|
}
|
||||||
|
b.WriteString(tail)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// decimal formats v in decimal.
|
||||||
|
func decimal(v int) string {
|
||||||
|
if v == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
var buf [20]byte
|
||||||
|
i := len(buf)
|
||||||
|
for v > 0 {
|
||||||
|
i--
|
||||||
|
buf[i] = byte('0' + v%10)
|
||||||
|
v /= 10
|
||||||
|
}
|
||||||
|
return string(buf[i:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// assemblePooled parses and assembles a generated kernel, returning the
|
||||||
|
// image and the single function's code words.
|
||||||
|
func assemblePooled(t *testing.T, head string, n int, tail string) (*Image, []uint32) {
|
||||||
|
t.Helper()
|
||||||
|
src := pooledKernel(head, n, tail)
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "poolmid_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse(path, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("functions = %d, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
return img, leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64BranchWord reports whether w is an unconditional B: op 000101 in bits
|
||||||
|
// 31..26, the encoding the flush guards ride.
|
||||||
|
func a64BranchWord(w uint32) bool {
|
||||||
|
return w>>26 == 0x05
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64BranchTarget decodes a B word's target byte offset from its own pc.
|
||||||
|
func a64BranchTarget(w uint32, pc int) int {
|
||||||
|
d := int(w & 0x03FFFFFF)
|
||||||
|
if d&(1<<25) != 0 {
|
||||||
|
d |= ^0x03FFFFFF
|
||||||
|
}
|
||||||
|
return pc + 4*d
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64LoadLiteral reports whether w is a LDR W/X literal (the pool's loads
|
||||||
|
// into REGTMP: word forms 0x18 and 0x58 in the top byte) and decodes its
|
||||||
|
// target byte displacement.
|
||||||
|
func a64LoadLiteral(w uint32) (int, bool) {
|
||||||
|
if w>>24 != 0x18 && w>>24 != 0x58 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
d := int(w>>5) & 0x7FFFF
|
||||||
|
if d&(1<<18) != 0 {
|
||||||
|
d |= ^0x7FFFF
|
||||||
|
}
|
||||||
|
return d * 4, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestArm64PoolFlushStructure assembles the distance-bound kernel and pins
|
||||||
|
// the flush structure on gasm's own image: exactly one mid-body branch over
|
||||||
|
// a drained segment, no code words inside it, every load literal inside the
|
||||||
|
// conservative displacement bound, and the segment's byte range free of line
|
||||||
|
// rows: the pool words carry the flushing statement's source line, so the
|
||||||
|
// pc-line tables see no delta across them.
|
||||||
|
func TestArm64PoolFlushStructure(t *testing.T) {
|
||||||
|
const n = 44000 // one flush: the bound arrives at about 43690 statements
|
||||||
|
img, words := assemblePooled(t, "", n, "\tRET\n")
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
|
||||||
|
var branches []int
|
||||||
|
for i, w := range words {
|
||||||
|
if a64BranchWord(w) {
|
||||||
|
branches = append(branches, i*4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(branches) != 1 {
|
||||||
|
t.Fatalf("branch words = %d, want exactly the one flush guard", len(branches))
|
||||||
|
}
|
||||||
|
branchPC := branches[0]
|
||||||
|
target := a64BranchTarget(words[branchPC/4], branchPC)
|
||||||
|
segStart, segEnd := branchPC+4, target
|
||||||
|
if segEnd <= segStart || segEnd%4 != 0 {
|
||||||
|
t.Fatalf("flush branch target %d leaves no legal word range after %d", target, branchPC)
|
||||||
|
}
|
||||||
|
if segEnd+4 > len(words)*4 {
|
||||||
|
t.Fatalf("flush branch target %d runs past the image (%d words)", target, len(words))
|
||||||
|
}
|
||||||
|
// The drained segment holds pool words only: four-byte constants, no
|
||||||
|
// branch opcodes, no literal loads.
|
||||||
|
for off := segStart; off < segEnd; off += 4 {
|
||||||
|
w := words[off/4]
|
||||||
|
if a64BranchWord(w) {
|
||||||
|
t.Fatalf("word at %d inside the drained segment is a branch: %08x", off, w)
|
||||||
|
}
|
||||||
|
if _, lit := a64LoadLiteral(w); lit {
|
||||||
|
t.Fatalf("word at %d inside the drained segment is a literal load: %08x", off, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every load literal resolves inside the conservative bound and inside
|
||||||
|
// the image: the property the flush exists to keep.
|
||||||
|
for i, w := range words {
|
||||||
|
d, ok := a64LoadLiteral(w)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pc := i * 4
|
||||||
|
if pc+d < 0 || pc+d >= len(words)*4 {
|
||||||
|
t.Fatalf("literal load at %d targets %d, outside the image", pc, pc+d)
|
||||||
|
}
|
||||||
|
if d >= a64MaxPCDisp || d <= -a64MaxPCDisp {
|
||||||
|
t.Fatalf("literal load at %d displaces %d, outside the conservative bound", pc, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The drained words carry no line rows of their own: the toolchain gives
|
||||||
|
// them the flushing statement's Pos so the pc-line tables see no delta.
|
||||||
|
for _, l := range fn.Lines {
|
||||||
|
if l.Offset >= segStart && l.Offset < segEnd {
|
||||||
|
t.Fatalf("line row at %d sits inside the drained segment [%d, %d)", l.Offset, segStart, segEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestArm64PoolFlushDifferential assembles the kernel family with gasm and
|
||||||
|
// with the installed toolchain and holds the bytes equal, the toolchain's
|
||||||
|
// tail alignment padding excepted: one flush, a fall-through end behind the
|
||||||
|
// UNDEF guard, two flushes, and a splitting function whose guard prefix and
|
||||||
|
// morestack block sit behind a shifted pool.
|
||||||
|
func TestArm64PoolFlushDifferential(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
|
for _, k := range []struct {
|
||||||
|
name string
|
||||||
|
head string
|
||||||
|
n int
|
||||||
|
tail string
|
||||||
|
}{
|
||||||
|
{"one flush, RET end", "", 44000, "\tRET\n"},
|
||||||
|
{"one flush, UNDEF end", "", 44000, ""},
|
||||||
|
{"two flushes", "", 90000, "\tRET\n"},
|
||||||
|
{"split function", "TEXT \u00b7poolmid(SB), $8-0", 44000, "\tRET\n"},
|
||||||
|
} {
|
||||||
|
t.Run(k.name, func(t *testing.T) {
|
||||||
|
if k.head != "" {
|
||||||
|
// The split variant spells its own TEXT: the generator's
|
||||||
|
// NOSPLIT line must give way to it.
|
||||||
|
runPoolFlushCase(t, pooledKernelFor(k.head, k.n, k.tail))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runPoolFlushCase(t, pooledKernel("", k.n, k.tail))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pooledKernelFor renders the kernel with an explicit TEXT line, the split
|
||||||
|
// variant's shape: no NOSPLIT, so the frame forces the stack-split guard.
|
||||||
|
func pooledKernelFor(text string, n int, tail string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("#include \"textflag.h\"\n")
|
||||||
|
b.WriteString(text)
|
||||||
|
b.WriteString("\n")
|
||||||
|
for i := range n {
|
||||||
|
b.WriteString("\tMOVD\tR1, ")
|
||||||
|
b.WriteString(decimal(0x2000000 + 8*i + 4))
|
||||||
|
b.WriteString("(R2)\n")
|
||||||
|
}
|
||||||
|
b.WriteString(tail)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runPoolFlushCase assembles one generated kernel both ways and compares
|
||||||
|
// the function's bytes, relocation sites masked, the toolchain's trailing
|
||||||
|
// alignment zeros excepted.
|
||||||
|
func runPoolFlushCase(t *testing.T, src string) {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "poolmid_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, errs := parser.Parse(path, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
gt := oracleFuncCode(t, toolAsmObject(t, path, "arm64"))
|
||||||
|
byLocal := make(map[string][]byte, len(gt))
|
||||||
|
for name, code := range gt {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
name = after
|
||||||
|
}
|
||||||
|
byLocal[name] = code
|
||||||
|
}
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
goCode, ok := byLocal[fn.Name]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: not in the oracle output (%d functions)", fn.Name, len(gt))
|
||||||
|
}
|
||||||
|
goCode = maskCode(append([]byte(nil), goCode...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
for w := 0; w < cmpLen/4; w++ {
|
||||||
|
g := binary.LittleEndian.Uint32(gasmCode[w*4:])
|
||||||
|
o := binary.LittleEndian.Uint32(goCode[w*4:])
|
||||||
|
if g != o {
|
||||||
|
t.Fatalf("%s: word %d (offset %d) differs: gasm %08x go %08x", fn.Name, w, w*4, g, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: prefixes equal but lengths differ (gasm %d, oracle %d)", fn.Name, len(gasmCode), len(goCode))
|
||||||
|
}
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Fatalf("%s: non-zero trailing bytes in the oracle output", fn.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
-1
@@ -7,7 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseArm64File is a helper assembling one arm64 source file.
|
// parseArm64File is a helper assembling one arm64 source file.
|
||||||
@@ -124,3 +124,59 @@ func TestArm64GOObjRelocTypes(t *testing.T) {
|
|||||||
// The detailed layout is covered by the goobj tests; here we only pin
|
// The detailed layout is covered by the goobj tests; here we only pin
|
||||||
// that emission succeeds with the new relocation kinds in play.
|
// that emission succeeds with the new relocation kinds in play.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestArm64TLSLoad pins the local-exec TLS load: a symbol the file's own
|
||||||
|
// GLOBL marks TLSBSS loads as one MOVZ word carrying the R_ARM64_TLS_LE
|
||||||
|
// relocation (asm7.go case 69), the shape `go tool asm` emits for the
|
||||||
|
// runtime's tls_g accesses. A non-TLS GLOBL keeps the ADRP+LDR pair.
|
||||||
|
func TestArm64TLSLoad(t *testing.T) {
|
||||||
|
img := parseArm64File(t, "#include \"textflag.h\"\n\n"+
|
||||||
|
"TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+
|
||||||
|
"\tMOVD tlsvar(SB), R0\n"+
|
||||||
|
"\tMOVD plain(SB), R1\n"+
|
||||||
|
"\tRET\n"+
|
||||||
|
"GLOBL tlsvar(SB), TLSBSS, $8\n"+
|
||||||
|
"GLOBL plain(SB), NOPTR, $8\n")
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
if fn.Size != 4+8+4 {
|
||||||
|
t.Fatalf("function size = %d, want 16", fn.Size)
|
||||||
|
}
|
||||||
|
if w := binary.LittleEndian.Uint32(img.Code[fn.Offset:]); w != 0xd2800000 {
|
||||||
|
t.Errorf("TLS load word = %08x, want MOVZ 0 (d2800000)", w)
|
||||||
|
}
|
||||||
|
var tlsSeen, plainSeen bool
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.Name != "tlsvar" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tlsSeen = true
|
||||||
|
if r.Kind != RelArm64TLSLE {
|
||||||
|
t.Errorf("tlsvar reloc kind = %v, want RelArm64TLSLE", r.Kind)
|
||||||
|
}
|
||||||
|
if r.Off != 0 {
|
||||||
|
t.Errorf("tlsvar reloc off = %d, want 0", r.Off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.Name == "plain" && r.Kind == RelArm64LDST64 {
|
||||||
|
plainSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !tlsSeen {
|
||||||
|
t.Error("no tlsvar relocation recorded")
|
||||||
|
}
|
||||||
|
if !plainSeen {
|
||||||
|
t.Error("the plain GLOBL load lost its ADRP+LDR relocation")
|
||||||
|
}
|
||||||
|
// The other widths have no TLS row: the toolchain refuses them.
|
||||||
|
f, errs := parser.Parse("k_arm64.s", "TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+
|
||||||
|
"\tMOVW tlsvar(SB), R0\n"+
|
||||||
|
"\tRET\n"+
|
||||||
|
"GLOBL tlsvar(SB), TLSBSS, $8\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFileARM64(f); err == nil {
|
||||||
|
t.Error("MOVW of a TLS symbol assembled, want an illegal combination")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,600 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// arm64 system registers and system-instruction aliases.
|
||||||
|
//
|
||||||
|
// The tables are transcribed from the data the Go toolchain itself carries
|
||||||
|
// (cmd/internal/obj/arm64/sysRegEnc.go and the sysInstFields map of asm7.go),
|
||||||
|
// which the ARM ARM defines: every system register is the packed field set
|
||||||
|
// op0<<19 | op1<<16 | CRn<<12 | CRm<<8 | op2<<5, and the read/write flags are
|
||||||
|
// the toolchain's own access classification. The encoding tables live here so
|
||||||
|
// the encoder stays testable against the GOROOT testdata word for word.
|
||||||
|
|
||||||
|
// a64SysReg is one system register: the packed encoding fields and the
|
||||||
|
// directions the register supports.
|
||||||
|
type a64SysReg struct {
|
||||||
|
v uint32
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SysRegs maps the system register names the toolchain knows to their
|
||||||
|
// encodings. MRS reads 0xd5300000 | v | Rd and MSR writes
|
||||||
|
// 0xd5100000 | v | Rt.
|
||||||
|
var a64SysRegs = map[string]a64SysReg{
|
||||||
|
"ACTLR_EL1": a64SysReg{0x181020, true, true},
|
||||||
|
"AFSR0_EL1": a64SysReg{0x185100, true, true},
|
||||||
|
"AFSR1_EL1": a64SysReg{0x185120, true, true},
|
||||||
|
"AIDR_EL1": a64SysReg{0x1900e0, true, false},
|
||||||
|
"AMAIR_EL1": a64SysReg{0x18a300, true, true},
|
||||||
|
"AMCFGR_EL0": a64SysReg{0x1bd220, true, false},
|
||||||
|
"AMCGCR_EL0": a64SysReg{0x1bd240, true, false},
|
||||||
|
"AMCNTENCLR0_EL0": a64SysReg{0x1bd280, true, true},
|
||||||
|
"AMCNTENCLR1_EL0": a64SysReg{0x1bd300, true, true},
|
||||||
|
"AMCNTENSET0_EL0": a64SysReg{0x1bd2a0, true, true},
|
||||||
|
"AMCNTENSET1_EL0": a64SysReg{0x1bd320, true, true},
|
||||||
|
"AMCR_EL0": a64SysReg{0x1bd200, true, true},
|
||||||
|
"AMEVCNTR00_EL0": a64SysReg{0x1bd400, true, true},
|
||||||
|
"AMEVCNTR01_EL0": a64SysReg{0x1bd420, true, true},
|
||||||
|
"AMEVCNTR02_EL0": a64SysReg{0x1bd440, true, true},
|
||||||
|
"AMEVCNTR03_EL0": a64SysReg{0x1bd460, true, true},
|
||||||
|
"AMEVCNTR04_EL0": a64SysReg{0x1bd480, true, true},
|
||||||
|
"AMEVCNTR05_EL0": a64SysReg{0x1bd4a0, true, true},
|
||||||
|
"AMEVCNTR06_EL0": a64SysReg{0x1bd4c0, true, true},
|
||||||
|
"AMEVCNTR07_EL0": a64SysReg{0x1bd4e0, true, true},
|
||||||
|
"AMEVCNTR08_EL0": a64SysReg{0x1bd500, true, true},
|
||||||
|
"AMEVCNTR09_EL0": a64SysReg{0x1bd520, true, true},
|
||||||
|
"AMEVCNTR010_EL0": a64SysReg{0x1bd540, true, true},
|
||||||
|
"AMEVCNTR011_EL0": a64SysReg{0x1bd560, true, true},
|
||||||
|
"AMEVCNTR012_EL0": a64SysReg{0x1bd580, true, true},
|
||||||
|
"AMEVCNTR013_EL0": a64SysReg{0x1bd5a0, true, true},
|
||||||
|
"AMEVCNTR014_EL0": a64SysReg{0x1bd5c0, true, true},
|
||||||
|
"AMEVCNTR015_EL0": a64SysReg{0x1bd5e0, true, true},
|
||||||
|
"AMEVCNTR10_EL0": a64SysReg{0x1bdc00, true, true},
|
||||||
|
"AMEVCNTR11_EL0": a64SysReg{0x1bdc20, true, true},
|
||||||
|
"AMEVCNTR12_EL0": a64SysReg{0x1bdc40, true, true},
|
||||||
|
"AMEVCNTR13_EL0": a64SysReg{0x1bdc60, true, true},
|
||||||
|
"AMEVCNTR14_EL0": a64SysReg{0x1bdc80, true, true},
|
||||||
|
"AMEVCNTR15_EL0": a64SysReg{0x1bdca0, true, true},
|
||||||
|
"AMEVCNTR16_EL0": a64SysReg{0x1bdcc0, true, true},
|
||||||
|
"AMEVCNTR17_EL0": a64SysReg{0x1bdce0, true, true},
|
||||||
|
"AMEVCNTR18_EL0": a64SysReg{0x1bdd00, true, true},
|
||||||
|
"AMEVCNTR19_EL0": a64SysReg{0x1bdd20, true, true},
|
||||||
|
"AMEVCNTR110_EL0": a64SysReg{0x1bdd40, true, true},
|
||||||
|
"AMEVCNTR111_EL0": a64SysReg{0x1bdd60, true, true},
|
||||||
|
"AMEVCNTR112_EL0": a64SysReg{0x1bdd80, true, true},
|
||||||
|
"AMEVCNTR113_EL0": a64SysReg{0x1bdda0, true, true},
|
||||||
|
"AMEVCNTR114_EL0": a64SysReg{0x1bddc0, true, true},
|
||||||
|
"AMEVCNTR115_EL0": a64SysReg{0x1bdde0, true, true},
|
||||||
|
"AMEVTYPER00_EL0": a64SysReg{0x1bd600, true, false},
|
||||||
|
"AMEVTYPER01_EL0": a64SysReg{0x1bd620, true, false},
|
||||||
|
"AMEVTYPER02_EL0": a64SysReg{0x1bd640, true, false},
|
||||||
|
"AMEVTYPER03_EL0": a64SysReg{0x1bd660, true, false},
|
||||||
|
"AMEVTYPER04_EL0": a64SysReg{0x1bd680, true, false},
|
||||||
|
"AMEVTYPER05_EL0": a64SysReg{0x1bd6a0, true, false},
|
||||||
|
"AMEVTYPER06_EL0": a64SysReg{0x1bd6c0, true, false},
|
||||||
|
"AMEVTYPER07_EL0": a64SysReg{0x1bd6e0, true, false},
|
||||||
|
"AMEVTYPER08_EL0": a64SysReg{0x1bd700, true, false},
|
||||||
|
"AMEVTYPER09_EL0": a64SysReg{0x1bd720, true, false},
|
||||||
|
"AMEVTYPER010_EL0": a64SysReg{0x1bd740, true, false},
|
||||||
|
"AMEVTYPER011_EL0": a64SysReg{0x1bd760, true, false},
|
||||||
|
"AMEVTYPER012_EL0": a64SysReg{0x1bd780, true, false},
|
||||||
|
"AMEVTYPER013_EL0": a64SysReg{0x1bd7a0, true, false},
|
||||||
|
"AMEVTYPER014_EL0": a64SysReg{0x1bd7c0, true, false},
|
||||||
|
"AMEVTYPER015_EL0": a64SysReg{0x1bd7e0, true, false},
|
||||||
|
"AMEVTYPER10_EL0": a64SysReg{0x1bde00, true, true},
|
||||||
|
"AMEVTYPER11_EL0": a64SysReg{0x1bde20, true, true},
|
||||||
|
"AMEVTYPER12_EL0": a64SysReg{0x1bde40, true, true},
|
||||||
|
"AMEVTYPER13_EL0": a64SysReg{0x1bde60, true, true},
|
||||||
|
"AMEVTYPER14_EL0": a64SysReg{0x1bde80, true, true},
|
||||||
|
"AMEVTYPER15_EL0": a64SysReg{0x1bdea0, true, true},
|
||||||
|
"AMEVTYPER16_EL0": a64SysReg{0x1bdec0, true, true},
|
||||||
|
"AMEVTYPER17_EL0": a64SysReg{0x1bdee0, true, true},
|
||||||
|
"AMEVTYPER18_EL0": a64SysReg{0x1bdf00, true, true},
|
||||||
|
"AMEVTYPER19_EL0": a64SysReg{0x1bdf20, true, true},
|
||||||
|
"AMEVTYPER110_EL0": a64SysReg{0x1bdf40, true, true},
|
||||||
|
"AMEVTYPER111_EL0": a64SysReg{0x1bdf60, true, true},
|
||||||
|
"AMEVTYPER112_EL0": a64SysReg{0x1bdf80, true, true},
|
||||||
|
"AMEVTYPER113_EL0": a64SysReg{0x1bdfa0, true, true},
|
||||||
|
"AMEVTYPER114_EL0": a64SysReg{0x1bdfc0, true, true},
|
||||||
|
"AMEVTYPER115_EL0": a64SysReg{0x1bdfe0, true, true},
|
||||||
|
"AMUSERENR_EL0": a64SysReg{0x1bd260, true, true},
|
||||||
|
"APDAKeyHi_EL1": a64SysReg{0x182220, true, true},
|
||||||
|
"APDAKeyLo_EL1": a64SysReg{0x182200, true, true},
|
||||||
|
"APDBKeyHi_EL1": a64SysReg{0x182260, true, true},
|
||||||
|
"APDBKeyLo_EL1": a64SysReg{0x182240, true, true},
|
||||||
|
"APGAKeyHi_EL1": a64SysReg{0x182320, true, true},
|
||||||
|
"APGAKeyLo_EL1": a64SysReg{0x182300, true, true},
|
||||||
|
"APIAKeyHi_EL1": a64SysReg{0x182120, true, true},
|
||||||
|
"APIAKeyLo_EL1": a64SysReg{0x182100, true, true},
|
||||||
|
"APIBKeyHi_EL1": a64SysReg{0x182160, true, true},
|
||||||
|
"APIBKeyLo_EL1": a64SysReg{0x182140, true, true},
|
||||||
|
"CCSIDR2_EL1": a64SysReg{0x190040, true, false},
|
||||||
|
"CCSIDR_EL1": a64SysReg{0x190000, true, false},
|
||||||
|
"CLIDR_EL1": a64SysReg{0x190020, true, false},
|
||||||
|
"CNTFRQ_EL0": a64SysReg{0x1be000, true, true},
|
||||||
|
"CNTKCTL_EL1": a64SysReg{0x18e100, true, true},
|
||||||
|
"CNTP_CTL_EL0": a64SysReg{0x1be220, true, true},
|
||||||
|
"CNTP_CVAL_EL0": a64SysReg{0x1be240, true, true},
|
||||||
|
"CNTP_TVAL_EL0": a64SysReg{0x1be200, true, true},
|
||||||
|
"CNTPCT_EL0": a64SysReg{0x1be020, true, false},
|
||||||
|
"CNTPS_CTL_EL1": a64SysReg{0x1fe220, true, true},
|
||||||
|
"CNTPS_CVAL_EL1": a64SysReg{0x1fe240, true, true},
|
||||||
|
"CNTPS_TVAL_EL1": a64SysReg{0x1fe200, true, true},
|
||||||
|
"CNTV_CTL_EL0": a64SysReg{0x1be320, true, true},
|
||||||
|
"CNTV_CVAL_EL0": a64SysReg{0x1be340, true, true},
|
||||||
|
"CNTV_TVAL_EL0": a64SysReg{0x1be300, true, true},
|
||||||
|
"CNTVCT_EL0": a64SysReg{0x1be040, true, false},
|
||||||
|
"CONTEXTIDR_EL1": a64SysReg{0x18d020, true, true},
|
||||||
|
"CPACR_EL1": a64SysReg{0x181040, true, true},
|
||||||
|
"CSSELR_EL1": a64SysReg{0x1a0000, true, true},
|
||||||
|
"CTR_EL0": a64SysReg{0x1b0020, true, false},
|
||||||
|
"CurrentEL": a64SysReg{0x184240, true, false},
|
||||||
|
"DAIF": a64SysReg{0x1b4220, true, true},
|
||||||
|
"DBGAUTHSTATUS_EL1": a64SysReg{0x107ec0, true, false},
|
||||||
|
"DBGBCR0_EL1": a64SysReg{0x1000a0, true, true},
|
||||||
|
"DBGBCR1_EL1": a64SysReg{0x1001a0, true, true},
|
||||||
|
"DBGBCR2_EL1": a64SysReg{0x1002a0, true, true},
|
||||||
|
"DBGBCR3_EL1": a64SysReg{0x1003a0, true, true},
|
||||||
|
"DBGBCR4_EL1": a64SysReg{0x1004a0, true, true},
|
||||||
|
"DBGBCR5_EL1": a64SysReg{0x1005a0, true, true},
|
||||||
|
"DBGBCR6_EL1": a64SysReg{0x1006a0, true, true},
|
||||||
|
"DBGBCR7_EL1": a64SysReg{0x1007a0, true, true},
|
||||||
|
"DBGBCR8_EL1": a64SysReg{0x1008a0, true, true},
|
||||||
|
"DBGBCR9_EL1": a64SysReg{0x1009a0, true, true},
|
||||||
|
"DBGBCR10_EL1": a64SysReg{0x100aa0, true, true},
|
||||||
|
"DBGBCR11_EL1": a64SysReg{0x100ba0, true, true},
|
||||||
|
"DBGBCR12_EL1": a64SysReg{0x100ca0, true, true},
|
||||||
|
"DBGBCR13_EL1": a64SysReg{0x100da0, true, true},
|
||||||
|
"DBGBCR14_EL1": a64SysReg{0x100ea0, true, true},
|
||||||
|
"DBGBCR15_EL1": a64SysReg{0x100fa0, true, true},
|
||||||
|
"DBGBVR0_EL1": a64SysReg{0x100080, true, true},
|
||||||
|
"DBGBVR1_EL1": a64SysReg{0x100180, true, true},
|
||||||
|
"DBGBVR2_EL1": a64SysReg{0x100280, true, true},
|
||||||
|
"DBGBVR3_EL1": a64SysReg{0x100380, true, true},
|
||||||
|
"DBGBVR4_EL1": a64SysReg{0x100480, true, true},
|
||||||
|
"DBGBVR5_EL1": a64SysReg{0x100580, true, true},
|
||||||
|
"DBGBVR6_EL1": a64SysReg{0x100680, true, true},
|
||||||
|
"DBGBVR7_EL1": a64SysReg{0x100780, true, true},
|
||||||
|
"DBGBVR8_EL1": a64SysReg{0x100880, true, true},
|
||||||
|
"DBGBVR9_EL1": a64SysReg{0x100980, true, true},
|
||||||
|
"DBGBVR10_EL1": a64SysReg{0x100a80, true, true},
|
||||||
|
"DBGBVR11_EL1": a64SysReg{0x100b80, true, true},
|
||||||
|
"DBGBVR12_EL1": a64SysReg{0x100c80, true, true},
|
||||||
|
"DBGBVR13_EL1": a64SysReg{0x100d80, true, true},
|
||||||
|
"DBGBVR14_EL1": a64SysReg{0x100e80, true, true},
|
||||||
|
"DBGBVR15_EL1": a64SysReg{0x100f80, true, true},
|
||||||
|
"DBGCLAIMCLR_EL1": a64SysReg{0x1079c0, true, true},
|
||||||
|
"DBGCLAIMSET_EL1": a64SysReg{0x1078c0, true, true},
|
||||||
|
"DBGDTR_EL0": a64SysReg{0x130400, true, true},
|
||||||
|
"DBGDTRRX_EL0": a64SysReg{0x130500, true, false},
|
||||||
|
"DBGDTRTX_EL0": a64SysReg{0x130500, false, true},
|
||||||
|
"DBGPRCR_EL1": a64SysReg{0x101480, true, true},
|
||||||
|
"DBGWCR0_EL1": a64SysReg{0x1000e0, true, true},
|
||||||
|
"DBGWCR1_EL1": a64SysReg{0x1001e0, true, true},
|
||||||
|
"DBGWCR2_EL1": a64SysReg{0x1002e0, true, true},
|
||||||
|
"DBGWCR3_EL1": a64SysReg{0x1003e0, true, true},
|
||||||
|
"DBGWCR4_EL1": a64SysReg{0x1004e0, true, true},
|
||||||
|
"DBGWCR5_EL1": a64SysReg{0x1005e0, true, true},
|
||||||
|
"DBGWCR6_EL1": a64SysReg{0x1006e0, true, true},
|
||||||
|
"DBGWCR7_EL1": a64SysReg{0x1007e0, true, true},
|
||||||
|
"DBGWCR8_EL1": a64SysReg{0x1008e0, true, true},
|
||||||
|
"DBGWCR9_EL1": a64SysReg{0x1009e0, true, true},
|
||||||
|
"DBGWCR10_EL1": a64SysReg{0x100ae0, true, true},
|
||||||
|
"DBGWCR11_EL1": a64SysReg{0x100be0, true, true},
|
||||||
|
"DBGWCR12_EL1": a64SysReg{0x100ce0, true, true},
|
||||||
|
"DBGWCR13_EL1": a64SysReg{0x100de0, true, true},
|
||||||
|
"DBGWCR14_EL1": a64SysReg{0x100ee0, true, true},
|
||||||
|
"DBGWCR15_EL1": a64SysReg{0x100fe0, true, true},
|
||||||
|
"DBGWVR0_EL1": a64SysReg{0x1000c0, true, true},
|
||||||
|
"DBGWVR1_EL1": a64SysReg{0x1001c0, true, true},
|
||||||
|
"DBGWVR2_EL1": a64SysReg{0x1002c0, true, true},
|
||||||
|
"DBGWVR3_EL1": a64SysReg{0x1003c0, true, true},
|
||||||
|
"DBGWVR4_EL1": a64SysReg{0x1004c0, true, true},
|
||||||
|
"DBGWVR5_EL1": a64SysReg{0x1005c0, true, true},
|
||||||
|
"DBGWVR6_EL1": a64SysReg{0x1006c0, true, true},
|
||||||
|
"DBGWVR7_EL1": a64SysReg{0x1007c0, true, true},
|
||||||
|
"DBGWVR8_EL1": a64SysReg{0x1008c0, true, true},
|
||||||
|
"DBGWVR9_EL1": a64SysReg{0x1009c0, true, true},
|
||||||
|
"DBGWVR10_EL1": a64SysReg{0x100ac0, true, true},
|
||||||
|
"DBGWVR11_EL1": a64SysReg{0x100bc0, true, true},
|
||||||
|
"DBGWVR12_EL1": a64SysReg{0x100cc0, true, true},
|
||||||
|
"DBGWVR13_EL1": a64SysReg{0x100dc0, true, true},
|
||||||
|
"DBGWVR14_EL1": a64SysReg{0x100ec0, true, true},
|
||||||
|
"DBGWVR15_EL1": a64SysReg{0x100fc0, true, true},
|
||||||
|
"DCZID_EL0": a64SysReg{0x1b00e0, true, false},
|
||||||
|
"DISR_EL1": a64SysReg{0x18c120, true, true},
|
||||||
|
"DIT": a64SysReg{0x1b42a0, true, true},
|
||||||
|
"DLR_EL0": a64SysReg{0x1b4520, true, true},
|
||||||
|
"DSPSR_EL0": a64SysReg{0x1b4500, true, true},
|
||||||
|
"ELR_EL1": a64SysReg{0x184020, true, true},
|
||||||
|
"ERRIDR_EL1": a64SysReg{0x185300, true, false},
|
||||||
|
"ERRSELR_EL1": a64SysReg{0x185320, true, true},
|
||||||
|
"ERXADDR_EL1": a64SysReg{0x185460, true, true},
|
||||||
|
"ERXCTLR_EL1": a64SysReg{0x185420, true, true},
|
||||||
|
"ERXFR_EL1": a64SysReg{0x185400, true, false},
|
||||||
|
"ERXMISC0_EL1": a64SysReg{0x185500, true, true},
|
||||||
|
"ERXMISC1_EL1": a64SysReg{0x185520, true, true},
|
||||||
|
"ERXMISC2_EL1": a64SysReg{0x185540, true, true},
|
||||||
|
"ERXMISC3_EL1": a64SysReg{0x185560, true, true},
|
||||||
|
"ERXPFGCDN_EL1": a64SysReg{0x1854c0, true, true},
|
||||||
|
"ERXPFGCTL_EL1": a64SysReg{0x1854a0, true, true},
|
||||||
|
"ERXPFGF_EL1": a64SysReg{0x185480, true, false},
|
||||||
|
"ERXSTATUS_EL1": a64SysReg{0x185440, true, true},
|
||||||
|
"ESR_EL1": a64SysReg{0x185200, true, true},
|
||||||
|
"FAR_EL1": a64SysReg{0x186000, true, true},
|
||||||
|
"FPCR": a64SysReg{0x1b4400, true, true},
|
||||||
|
"FPSR": a64SysReg{0x1b4420, true, true},
|
||||||
|
"GCR_EL1": a64SysReg{0x1810c0, true, true},
|
||||||
|
"GMID_EL1": a64SysReg{0x31400, true, false},
|
||||||
|
"ICC_AP0R0_EL1": a64SysReg{0x18c880, true, true},
|
||||||
|
"ICC_AP0R1_EL1": a64SysReg{0x18c8a0, true, true},
|
||||||
|
"ICC_AP0R2_EL1": a64SysReg{0x18c8c0, true, true},
|
||||||
|
"ICC_AP0R3_EL1": a64SysReg{0x18c8e0, true, true},
|
||||||
|
"ICC_AP1R0_EL1": a64SysReg{0x18c900, true, true},
|
||||||
|
"ICC_AP1R1_EL1": a64SysReg{0x18c920, true, true},
|
||||||
|
"ICC_AP1R2_EL1": a64SysReg{0x18c940, true, true},
|
||||||
|
"ICC_AP1R3_EL1": a64SysReg{0x18c960, true, true},
|
||||||
|
"ICC_ASGI1R_EL1": a64SysReg{0x18cbc0, false, true},
|
||||||
|
"ICC_BPR0_EL1": a64SysReg{0x18c860, true, true},
|
||||||
|
"ICC_BPR1_EL1": a64SysReg{0x18cc60, true, true},
|
||||||
|
"ICC_CTLR_EL1": a64SysReg{0x18cc80, true, true},
|
||||||
|
"ICC_DIR_EL1": a64SysReg{0x18cb20, false, true},
|
||||||
|
"ICC_EOIR0_EL1": a64SysReg{0x18c820, false, true},
|
||||||
|
"ICC_EOIR1_EL1": a64SysReg{0x18cc20, false, true},
|
||||||
|
"ICC_HPPIR0_EL1": a64SysReg{0x18c840, true, false},
|
||||||
|
"ICC_HPPIR1_EL1": a64SysReg{0x18cc40, true, false},
|
||||||
|
"ICC_IAR0_EL1": a64SysReg{0x18c800, true, false},
|
||||||
|
"ICC_IAR1_EL1": a64SysReg{0x18cc00, true, false},
|
||||||
|
"ICC_IGRPEN0_EL1": a64SysReg{0x18ccc0, true, true},
|
||||||
|
"ICC_IGRPEN1_EL1": a64SysReg{0x18cce0, true, true},
|
||||||
|
"ICC_PMR_EL1": a64SysReg{0x184600, true, true},
|
||||||
|
"ICC_RPR_EL1": a64SysReg{0x18cb60, true, false},
|
||||||
|
"ICC_SGI0R_EL1": a64SysReg{0x18cbe0, false, true},
|
||||||
|
"ICC_SGI1R_EL1": a64SysReg{0x18cba0, false, true},
|
||||||
|
"ICC_SRE_EL1": a64SysReg{0x18cca0, true, true},
|
||||||
|
"ICV_AP0R0_EL1": a64SysReg{0x18c880, true, true},
|
||||||
|
"ICV_AP0R1_EL1": a64SysReg{0x18c8a0, true, true},
|
||||||
|
"ICV_AP0R2_EL1": a64SysReg{0x18c8c0, true, true},
|
||||||
|
"ICV_AP0R3_EL1": a64SysReg{0x18c8e0, true, true},
|
||||||
|
"ICV_AP1R0_EL1": a64SysReg{0x18c900, true, true},
|
||||||
|
"ICV_AP1R1_EL1": a64SysReg{0x18c920, true, true},
|
||||||
|
"ICV_AP1R2_EL1": a64SysReg{0x18c940, true, true},
|
||||||
|
"ICV_AP1R3_EL1": a64SysReg{0x18c960, true, true},
|
||||||
|
"ICV_BPR0_EL1": a64SysReg{0x18c860, true, true},
|
||||||
|
"ICV_BPR1_EL1": a64SysReg{0x18cc60, true, true},
|
||||||
|
"ICV_CTLR_EL1": a64SysReg{0x18cc80, true, true},
|
||||||
|
"ICV_DIR_EL1": a64SysReg{0x18cb20, false, true},
|
||||||
|
"ICV_EOIR0_EL1": a64SysReg{0x18c820, false, true},
|
||||||
|
"ICV_EOIR1_EL1": a64SysReg{0x18cc20, false, true},
|
||||||
|
"ICV_HPPIR0_EL1": a64SysReg{0x18c840, true, false},
|
||||||
|
"ICV_HPPIR1_EL1": a64SysReg{0x18cc40, true, false},
|
||||||
|
"ICV_IAR0_EL1": a64SysReg{0x18c800, true, false},
|
||||||
|
"ICV_IAR1_EL1": a64SysReg{0x18cc00, true, false},
|
||||||
|
"ICV_IGRPEN0_EL1": a64SysReg{0x18ccc0, true, true},
|
||||||
|
"ICV_IGRPEN1_EL1": a64SysReg{0x18cce0, true, true},
|
||||||
|
"ICV_PMR_EL1": a64SysReg{0x184600, true, true},
|
||||||
|
"ICV_RPR_EL1": a64SysReg{0x18cb60, true, false},
|
||||||
|
"ID_AA64AFR0_EL1": a64SysReg{0x180580, true, false},
|
||||||
|
"ID_AA64AFR1_EL1": a64SysReg{0x1805a0, true, false},
|
||||||
|
"ID_AA64DFR0_EL1": a64SysReg{0x180500, true, false},
|
||||||
|
"ID_AA64DFR1_EL1": a64SysReg{0x180520, true, false},
|
||||||
|
"ID_AA64ISAR0_EL1": a64SysReg{0x180600, true, false},
|
||||||
|
"ID_AA64ISAR1_EL1": a64SysReg{0x180620, true, false},
|
||||||
|
"ID_AA64MMFR0_EL1": a64SysReg{0x180700, true, false},
|
||||||
|
"ID_AA64MMFR1_EL1": a64SysReg{0x180720, true, false},
|
||||||
|
"ID_AA64MMFR2_EL1": a64SysReg{0x180740, true, false},
|
||||||
|
"ID_AA64PFR0_EL1": a64SysReg{0x180400, true, false},
|
||||||
|
"ID_AA64PFR1_EL1": a64SysReg{0x180420, true, false},
|
||||||
|
"ID_AA64ZFR0_EL1": a64SysReg{0x180480, true, false},
|
||||||
|
"ID_AFR0_EL1": a64SysReg{0x180160, true, false},
|
||||||
|
"ID_DFR0_EL1": a64SysReg{0x180140, true, false},
|
||||||
|
"ID_ISAR0_EL1": a64SysReg{0x180200, true, false},
|
||||||
|
"ID_ISAR1_EL1": a64SysReg{0x180220, true, false},
|
||||||
|
"ID_ISAR2_EL1": a64SysReg{0x180240, true, false},
|
||||||
|
"ID_ISAR3_EL1": a64SysReg{0x180260, true, false},
|
||||||
|
"ID_ISAR4_EL1": a64SysReg{0x180280, true, false},
|
||||||
|
"ID_ISAR5_EL1": a64SysReg{0x1802a0, true, false},
|
||||||
|
"ID_ISAR6_EL1": a64SysReg{0x1802e0, true, false},
|
||||||
|
"ID_MMFR0_EL1": a64SysReg{0x180180, true, false},
|
||||||
|
"ID_MMFR1_EL1": a64SysReg{0x1801a0, true, false},
|
||||||
|
"ID_MMFR2_EL1": a64SysReg{0x1801c0, true, false},
|
||||||
|
"ID_MMFR3_EL1": a64SysReg{0x1801e0, true, false},
|
||||||
|
"ID_MMFR4_EL1": a64SysReg{0x1802c0, true, false},
|
||||||
|
"ID_PFR0_EL1": a64SysReg{0x180100, true, false},
|
||||||
|
"ID_PFR1_EL1": a64SysReg{0x180120, true, false},
|
||||||
|
"ID_PFR2_EL1": a64SysReg{0x180380, true, false},
|
||||||
|
"ISR_EL1": a64SysReg{0x18c100, true, false},
|
||||||
|
"LORC_EL1": a64SysReg{0x18a460, true, true},
|
||||||
|
"LOREA_EL1": a64SysReg{0x18a420, true, true},
|
||||||
|
"LORID_EL1": a64SysReg{0x18a4e0, true, false},
|
||||||
|
"LORN_EL1": a64SysReg{0x18a440, true, true},
|
||||||
|
"LORSA_EL1": a64SysReg{0x18a400, true, true},
|
||||||
|
"MAIR_EL1": a64SysReg{0x18a200, true, true},
|
||||||
|
"MDCCINT_EL1": a64SysReg{0x100200, true, true},
|
||||||
|
"MDCCSR_EL0": a64SysReg{0x130100, true, false},
|
||||||
|
"MDRAR_EL1": a64SysReg{0x101000, true, false},
|
||||||
|
"MDSCR_EL1": a64SysReg{0x100240, true, true},
|
||||||
|
"MIDR_EL1": a64SysReg{0x180000, true, false},
|
||||||
|
"MPAM0_EL1": a64SysReg{0x18a520, true, true},
|
||||||
|
"MPAM1_EL1": a64SysReg{0x18a500, true, true},
|
||||||
|
"MPAMIDR_EL1": a64SysReg{0x18a480, true, false},
|
||||||
|
"MPIDR_EL1": a64SysReg{0x1800a0, true, false},
|
||||||
|
"MVFR0_EL1": a64SysReg{0x180300, true, false},
|
||||||
|
"MVFR1_EL1": a64SysReg{0x180320, true, false},
|
||||||
|
"MVFR2_EL1": a64SysReg{0x180340, true, false},
|
||||||
|
"NZCV": a64SysReg{0x1b4200, true, true},
|
||||||
|
"OSDLR_EL1": a64SysReg{0x101380, true, true},
|
||||||
|
"OSDTRRX_EL1": a64SysReg{0x100040, true, true},
|
||||||
|
"OSDTRTX_EL1": a64SysReg{0x100340, true, true},
|
||||||
|
"OSECCR_EL1": a64SysReg{0x100640, true, true},
|
||||||
|
"OSLAR_EL1": a64SysReg{0x101080, false, true},
|
||||||
|
"OSLSR_EL1": a64SysReg{0x101180, true, false},
|
||||||
|
"PAN": a64SysReg{0x184260, true, true},
|
||||||
|
"PAR_EL1": a64SysReg{0x187400, true, true},
|
||||||
|
"PMBIDR_EL1": a64SysReg{0x189ae0, true, false},
|
||||||
|
"PMBLIMITR_EL1": a64SysReg{0x189a00, true, true},
|
||||||
|
"PMBPTR_EL1": a64SysReg{0x189a20, true, true},
|
||||||
|
"PMBSR_EL1": a64SysReg{0x189a60, true, true},
|
||||||
|
"PMCCFILTR_EL0": a64SysReg{0x1befe0, true, true},
|
||||||
|
"PMCCNTR_EL0": a64SysReg{0x1b9d00, true, true},
|
||||||
|
"PMCEID0_EL0": a64SysReg{0x1b9cc0, true, false},
|
||||||
|
"PMCEID1_EL0": a64SysReg{0x1b9ce0, true, false},
|
||||||
|
"PMCNTENCLR_EL0": a64SysReg{0x1b9c40, true, true},
|
||||||
|
"PMCNTENSET_EL0": a64SysReg{0x1b9c20, true, true},
|
||||||
|
"PMCR_EL0": a64SysReg{0x1b9c00, true, true},
|
||||||
|
"PMEVCNTR0_EL0": a64SysReg{0x1be800, true, true},
|
||||||
|
"PMEVCNTR1_EL0": a64SysReg{0x1be820, true, true},
|
||||||
|
"PMEVCNTR2_EL0": a64SysReg{0x1be840, true, true},
|
||||||
|
"PMEVCNTR3_EL0": a64SysReg{0x1be860, true, true},
|
||||||
|
"PMEVCNTR4_EL0": a64SysReg{0x1be880, true, true},
|
||||||
|
"PMEVCNTR5_EL0": a64SysReg{0x1be8a0, true, true},
|
||||||
|
"PMEVCNTR6_EL0": a64SysReg{0x1be8c0, true, true},
|
||||||
|
"PMEVCNTR7_EL0": a64SysReg{0x1be8e0, true, true},
|
||||||
|
"PMEVCNTR8_EL0": a64SysReg{0x1be900, true, true},
|
||||||
|
"PMEVCNTR9_EL0": a64SysReg{0x1be920, true, true},
|
||||||
|
"PMEVCNTR10_EL0": a64SysReg{0x1be940, true, true},
|
||||||
|
"PMEVCNTR11_EL0": a64SysReg{0x1be960, true, true},
|
||||||
|
"PMEVCNTR12_EL0": a64SysReg{0x1be980, true, true},
|
||||||
|
"PMEVCNTR13_EL0": a64SysReg{0x1be9a0, true, true},
|
||||||
|
"PMEVCNTR14_EL0": a64SysReg{0x1be9c0, true, true},
|
||||||
|
"PMEVCNTR15_EL0": a64SysReg{0x1be9e0, true, true},
|
||||||
|
"PMEVCNTR16_EL0": a64SysReg{0x1bea00, true, true},
|
||||||
|
"PMEVCNTR17_EL0": a64SysReg{0x1bea20, true, true},
|
||||||
|
"PMEVCNTR18_EL0": a64SysReg{0x1bea40, true, true},
|
||||||
|
"PMEVCNTR19_EL0": a64SysReg{0x1bea60, true, true},
|
||||||
|
"PMEVCNTR20_EL0": a64SysReg{0x1bea80, true, true},
|
||||||
|
"PMEVCNTR21_EL0": a64SysReg{0x1beaa0, true, true},
|
||||||
|
"PMEVCNTR22_EL0": a64SysReg{0x1beac0, true, true},
|
||||||
|
"PMEVCNTR23_EL0": a64SysReg{0x1beae0, true, true},
|
||||||
|
"PMEVCNTR24_EL0": a64SysReg{0x1beb00, true, true},
|
||||||
|
"PMEVCNTR25_EL0": a64SysReg{0x1beb20, true, true},
|
||||||
|
"PMEVCNTR26_EL0": a64SysReg{0x1beb40, true, true},
|
||||||
|
"PMEVCNTR27_EL0": a64SysReg{0x1beb60, true, true},
|
||||||
|
"PMEVCNTR28_EL0": a64SysReg{0x1beb80, true, true},
|
||||||
|
"PMEVCNTR29_EL0": a64SysReg{0x1beba0, true, true},
|
||||||
|
"PMEVCNTR30_EL0": a64SysReg{0x1bebc0, true, true},
|
||||||
|
"PMEVTYPER0_EL0": a64SysReg{0x1bec00, true, true},
|
||||||
|
"PMEVTYPER1_EL0": a64SysReg{0x1bec20, true, true},
|
||||||
|
"PMEVTYPER2_EL0": a64SysReg{0x1bec40, true, true},
|
||||||
|
"PMEVTYPER3_EL0": a64SysReg{0x1bec60, true, true},
|
||||||
|
"PMEVTYPER4_EL0": a64SysReg{0x1bec80, true, true},
|
||||||
|
"PMEVTYPER5_EL0": a64SysReg{0x1beca0, true, true},
|
||||||
|
"PMEVTYPER6_EL0": a64SysReg{0x1becc0, true, true},
|
||||||
|
"PMEVTYPER7_EL0": a64SysReg{0x1bece0, true, true},
|
||||||
|
"PMEVTYPER8_EL0": a64SysReg{0x1bed00, true, true},
|
||||||
|
"PMEVTYPER9_EL0": a64SysReg{0x1bed20, true, true},
|
||||||
|
"PMEVTYPER10_EL0": a64SysReg{0x1bed40, true, true},
|
||||||
|
"PMEVTYPER11_EL0": a64SysReg{0x1bed60, true, true},
|
||||||
|
"PMEVTYPER12_EL0": a64SysReg{0x1bed80, true, true},
|
||||||
|
"PMEVTYPER13_EL0": a64SysReg{0x1beda0, true, true},
|
||||||
|
"PMEVTYPER14_EL0": a64SysReg{0x1bedc0, true, true},
|
||||||
|
"PMEVTYPER15_EL0": a64SysReg{0x1bede0, true, true},
|
||||||
|
"PMEVTYPER16_EL0": a64SysReg{0x1bee00, true, true},
|
||||||
|
"PMEVTYPER17_EL0": a64SysReg{0x1bee20, true, true},
|
||||||
|
"PMEVTYPER18_EL0": a64SysReg{0x1bee40, true, true},
|
||||||
|
"PMEVTYPER19_EL0": a64SysReg{0x1bee60, true, true},
|
||||||
|
"PMEVTYPER20_EL0": a64SysReg{0x1bee80, true, true},
|
||||||
|
"PMEVTYPER21_EL0": a64SysReg{0x1beea0, true, true},
|
||||||
|
"PMEVTYPER22_EL0": a64SysReg{0x1beec0, true, true},
|
||||||
|
"PMEVTYPER23_EL0": a64SysReg{0x1beee0, true, true},
|
||||||
|
"PMEVTYPER24_EL0": a64SysReg{0x1bef00, true, true},
|
||||||
|
"PMEVTYPER25_EL0": a64SysReg{0x1bef20, true, true},
|
||||||
|
"PMEVTYPER26_EL0": a64SysReg{0x1bef40, true, true},
|
||||||
|
"PMEVTYPER27_EL0": a64SysReg{0x1bef60, true, true},
|
||||||
|
"PMEVTYPER28_EL0": a64SysReg{0x1bef80, true, true},
|
||||||
|
"PMEVTYPER29_EL0": a64SysReg{0x1befa0, true, true},
|
||||||
|
"PMEVTYPER30_EL0": a64SysReg{0x1befc0, true, true},
|
||||||
|
"PMINTENCLR_EL1": a64SysReg{0x189e40, true, true},
|
||||||
|
"PMINTENSET_EL1": a64SysReg{0x189e20, true, true},
|
||||||
|
"PMMIR_EL1": a64SysReg{0x189ec0, true, false},
|
||||||
|
"PMOVSCLR_EL0": a64SysReg{0x1b9c60, true, true},
|
||||||
|
"PMOVSSET_EL0": a64SysReg{0x1b9e60, true, true},
|
||||||
|
"PMSCR_EL1": a64SysReg{0x189900, true, true},
|
||||||
|
"PMSELR_EL0": a64SysReg{0x1b9ca0, true, true},
|
||||||
|
"PMSEVFR_EL1": a64SysReg{0x1899a0, true, true},
|
||||||
|
"PMSFCR_EL1": a64SysReg{0x189980, true, true},
|
||||||
|
"PMSICR_EL1": a64SysReg{0x189940, true, true},
|
||||||
|
"PMSIDR_EL1": a64SysReg{0x1899e0, true, false},
|
||||||
|
"PMSIRR_EL1": a64SysReg{0x189960, true, true},
|
||||||
|
"PMSLATFR_EL1": a64SysReg{0x1899c0, true, true},
|
||||||
|
"PMSWINC_EL0": a64SysReg{0x1b9c80, false, true},
|
||||||
|
"PMUSERENR_EL0": a64SysReg{0x1b9e00, true, true},
|
||||||
|
"PMXEVCNTR_EL0": a64SysReg{0x1b9d40, true, true},
|
||||||
|
"PMXEVTYPER_EL0": a64SysReg{0x1b9d20, true, true},
|
||||||
|
"REVIDR_EL1": a64SysReg{0x1800c0, true, false},
|
||||||
|
"RGSR_EL1": a64SysReg{0x1810a0, true, true},
|
||||||
|
"RMR_EL1": a64SysReg{0x18c040, true, true},
|
||||||
|
"RNDR": a64SysReg{0x1b2400, true, false},
|
||||||
|
"RNDRRS": a64SysReg{0x1b2420, true, false},
|
||||||
|
"RVBAR_EL1": a64SysReg{0x18c020, true, false},
|
||||||
|
"SCTLR_EL1": a64SysReg{0x181000, true, true},
|
||||||
|
"SCXTNUM_EL0": a64SysReg{0x1bd0e0, true, true},
|
||||||
|
"SCXTNUM_EL1": a64SysReg{0x18d0e0, true, true},
|
||||||
|
"SP_EL0": a64SysReg{0x184100, true, true},
|
||||||
|
"SP_EL1": a64SysReg{0x1c4100, true, true},
|
||||||
|
"SPSel": a64SysReg{0x184200, true, true},
|
||||||
|
"SPSR_abt": a64SysReg{0x1c4320, true, true},
|
||||||
|
"SPSR_EL1": a64SysReg{0x184000, true, true},
|
||||||
|
"SPSR_fiq": a64SysReg{0x1c4360, true, true},
|
||||||
|
"SPSR_irq": a64SysReg{0x1c4300, true, true},
|
||||||
|
"SPSR_und": a64SysReg{0x1c4340, true, true},
|
||||||
|
"SSBS": a64SysReg{0x1b42c0, true, true},
|
||||||
|
"TCO": a64SysReg{0x1b42e0, true, true},
|
||||||
|
"TCR_EL1": a64SysReg{0x182040, true, true},
|
||||||
|
"TFSR_EL1": a64SysReg{0x185600, true, true},
|
||||||
|
"TFSRE0_EL1": a64SysReg{0x185620, true, true},
|
||||||
|
"TPIDR_EL0": a64SysReg{0x1bd040, true, true},
|
||||||
|
"TPIDR_EL1": a64SysReg{0x18d080, true, true},
|
||||||
|
"TPIDRRO_EL0": a64SysReg{0x1bd060, true, true},
|
||||||
|
"TRFCR_EL1": a64SysReg{0x181220, true, true},
|
||||||
|
"TTBR0_EL1": a64SysReg{0x182000, true, true},
|
||||||
|
"TTBR1_EL1": a64SysReg{0x182020, true, true},
|
||||||
|
"UAO": a64SysReg{0x184280, true, true},
|
||||||
|
"VBAR_EL1": a64SysReg{0x18c000, true, true},
|
||||||
|
"ZCR_EL1": a64SysReg{0x181200, true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64SysInst is one TLBI alias: the fields the SYS encoding carries beside
|
||||||
|
// the fixed op0 = 01 and CRn = 8.
|
||||||
|
type a64SysInst struct {
|
||||||
|
op1, cm, op2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64TLBIOps maps the TLBI operation names to their fields; the register
|
||||||
|
// operand is optional and defaults to ZR.
|
||||||
|
var a64TLBIOps = map[string]a64SysInst{
|
||||||
|
"ALLE1": {0x4, 0x7, 0x4},
|
||||||
|
"ALLE1IS": {0x4, 0x3, 0x4},
|
||||||
|
"ALLE1OS": {0x4, 0x1, 0x4},
|
||||||
|
"ALLE2": {0x4, 0x7, 0x0},
|
||||||
|
"ALLE2IS": {0x4, 0x3, 0x0},
|
||||||
|
"ALLE2OS": {0x4, 0x1, 0x0},
|
||||||
|
"ALLE3": {0x6, 0x7, 0x0},
|
||||||
|
"ALLE3IS": {0x6, 0x3, 0x0},
|
||||||
|
"ALLE3OS": {0x6, 0x1, 0x0},
|
||||||
|
"ASIDE1": {0x0, 0x7, 0x2},
|
||||||
|
"ASIDE1IS": {0x0, 0x3, 0x2},
|
||||||
|
"ASIDE1OS": {0x0, 0x1, 0x2},
|
||||||
|
"IPAS2E1": {0x4, 0x4, 0x1},
|
||||||
|
"IPAS2E1IS": {0x4, 0x0, 0x1},
|
||||||
|
"IPAS2E1OS": {0x4, 0x4, 0x0},
|
||||||
|
"IPAS2LE1": {0x4, 0x4, 0x5},
|
||||||
|
"IPAS2LE1IS": {0x4, 0x0, 0x5},
|
||||||
|
"IPAS2LE1OS": {0x4, 0x4, 0x4},
|
||||||
|
"RIPAS2E1": {0x4, 0x4, 0x2},
|
||||||
|
"RIPAS2E1IS": {0x4, 0x0, 0x2},
|
||||||
|
"RIPAS2E1OS": {0x4, 0x4, 0x3},
|
||||||
|
"RIPAS2LE1": {0x4, 0x4, 0x6},
|
||||||
|
"RIPAS2LE1IS": {0x4, 0x0, 0x6},
|
||||||
|
"RIPAS2LE1OS": {0x4, 0x4, 0x7},
|
||||||
|
"RVAAE1": {0x0, 0x6, 0x3},
|
||||||
|
"RVAAE1IS": {0x0, 0x2, 0x3},
|
||||||
|
"RVAAE1OS": {0x0, 0x5, 0x3},
|
||||||
|
"RVAALE1": {0x0, 0x6, 0x7},
|
||||||
|
"RVAALE1IS": {0x0, 0x2, 0x7},
|
||||||
|
"RVAALE1OS": {0x0, 0x5, 0x7},
|
||||||
|
"RVAE1": {0x0, 0x6, 0x1},
|
||||||
|
"RVAE1IS": {0x0, 0x2, 0x1},
|
||||||
|
"RVAE1OS": {0x0, 0x5, 0x1},
|
||||||
|
"RVAE2": {0x4, 0x6, 0x1},
|
||||||
|
"RVAE2IS": {0x4, 0x2, 0x1},
|
||||||
|
"RVAE2OS": {0x4, 0x5, 0x1},
|
||||||
|
"RVAE3": {0x6, 0x6, 0x1},
|
||||||
|
"RVAE3IS": {0x6, 0x2, 0x1},
|
||||||
|
"RVAE3OS": {0x6, 0x5, 0x1},
|
||||||
|
"RVALE1": {0x0, 0x6, 0x5},
|
||||||
|
"RVALE1IS": {0x0, 0x2, 0x5},
|
||||||
|
"RVALE1OS": {0x0, 0x5, 0x5},
|
||||||
|
"RVALE2": {0x4, 0x6, 0x5},
|
||||||
|
"RVALE2IS": {0x4, 0x2, 0x5},
|
||||||
|
"RVALE2OS": {0x4, 0x5, 0x5},
|
||||||
|
"RVALE3": {0x6, 0x6, 0x5},
|
||||||
|
"RVALE3IS": {0x6, 0x2, 0x5},
|
||||||
|
"RVALE3OS": {0x6, 0x5, 0x5},
|
||||||
|
"VAAE1": {0x0, 0x7, 0x3},
|
||||||
|
"VAAE1IS": {0x0, 0x3, 0x3},
|
||||||
|
"VAAE1OS": {0x0, 0x1, 0x3},
|
||||||
|
"VAALE1": {0x0, 0x7, 0x7},
|
||||||
|
"VAALE1IS": {0x0, 0x3, 0x7},
|
||||||
|
"VAALE1OS": {0x0, 0x1, 0x7},
|
||||||
|
"VAE1": {0x0, 0x7, 0x1},
|
||||||
|
"VAE1IS": {0x0, 0x3, 0x1},
|
||||||
|
"VAE1OS": {0x0, 0x1, 0x1},
|
||||||
|
"VAE2": {0x4, 0x7, 0x1},
|
||||||
|
"VAE2IS": {0x4, 0x3, 0x1},
|
||||||
|
"VAE2OS": {0x4, 0x1, 0x1},
|
||||||
|
"VAE3": {0x6, 0x7, 0x1},
|
||||||
|
"VAE3IS": {0x6, 0x3, 0x1},
|
||||||
|
"VAE3OS": {0x6, 0x1, 0x1},
|
||||||
|
"VALE1": {0x0, 0x7, 0x5},
|
||||||
|
"VALE1IS": {0x0, 0x3, 0x5},
|
||||||
|
"VALE1OS": {0x0, 0x1, 0x5},
|
||||||
|
"VALE2": {0x4, 0x7, 0x5},
|
||||||
|
"VALE2IS": {0x4, 0x3, 0x5},
|
||||||
|
"VALE2OS": {0x4, 0x1, 0x5},
|
||||||
|
"VALE3": {0x6, 0x7, 0x5},
|
||||||
|
"VALE3IS": {0x6, 0x3, 0x5},
|
||||||
|
"VALE3OS": {0x6, 0x1, 0x5},
|
||||||
|
"VMALLE1": {0x0, 0x7, 0x0},
|
||||||
|
"VMALLE1IS": {0x0, 0x3, 0x0},
|
||||||
|
"VMALLE1OS": {0x0, 0x1, 0x0},
|
||||||
|
"VMALLS12E1": {0x4, 0x7, 0x6},
|
||||||
|
"VMALLS12E1IS": {0x4, 0x3, 0x6},
|
||||||
|
"VMALLS12E1OS": {0x4, 0x1, 0x6},
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64TLBITakesReg reports whether a TLBI operation spells a by-address
|
||||||
|
// invalidation that carries the address in its optional second register
|
||||||
|
// (asm7.go's sysInstFields hasOperand2). The whole-entry spellings are
|
||||||
|
// exactly the VMALL-prefixed and the ALL-prefixed ones; everything else
|
||||||
|
// (VAE*, VAAE*, VALE*, RVAA*, RVAE*, ASIDE1*, IPAS2*, RIPAS2*) is
|
||||||
|
// by-address and takes the register.
|
||||||
|
func arm64TLBITakesReg(name string) bool {
|
||||||
|
return !strings.HasPrefix(name, "VMALL") && !strings.HasPrefix(name, "ALL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64DCOps2 maps the DC operation names to their fields; the register
|
||||||
|
// operand is mandatory.
|
||||||
|
var a64DCOps2 = map[string]a64SysInst{
|
||||||
|
"CGDSW": {0x0, 0xa, 0x6},
|
||||||
|
"CGDVAC": {0x3, 0xa, 0x5},
|
||||||
|
"CGDVADP": {0x3, 0xd, 0x5},
|
||||||
|
"CGDVAP": {0x3, 0xc, 0x5},
|
||||||
|
"CGSW": {0x0, 0xa, 0x4},
|
||||||
|
"CGVAC": {0x3, 0xa, 0x3},
|
||||||
|
"CGVADP": {0x3, 0xd, 0x3},
|
||||||
|
"CGVAP": {0x3, 0xc, 0x3},
|
||||||
|
"CIGDSW": {0x0, 0xe, 0x6},
|
||||||
|
"CIGDVAC": {0x3, 0xe, 0x5},
|
||||||
|
"CIGSW": {0x0, 0xe, 0x4},
|
||||||
|
"CIGVAC": {0x3, 0xe, 0x3},
|
||||||
|
"CISW": {0x0, 0xe, 0x2},
|
||||||
|
"CIVAC": {0x3, 0xe, 0x1},
|
||||||
|
"CSW": {0x0, 0xa, 0x2},
|
||||||
|
"CVAC": {0x3, 0xa, 0x1},
|
||||||
|
"CVADP": {0x3, 0xd, 0x1},
|
||||||
|
"CVAP": {0x3, 0xc, 0x1},
|
||||||
|
"CVAU": {0x3, 0xb, 0x1},
|
||||||
|
"GVA": {0x3, 0x4, 0x3},
|
||||||
|
"GZVA": {0x3, 0x4, 0x4},
|
||||||
|
"IGDSW": {0x0, 0x6, 0x6},
|
||||||
|
"IGDVAC": {0x0, 0x6, 0x5},
|
||||||
|
"IGSW": {0x0, 0x6, 0x4},
|
||||||
|
"IGVAC": {0x0, 0x6, 0x3},
|
||||||
|
"ISW": {0x0, 0x6, 0x2},
|
||||||
|
"IVAC": {0x0, 0x6, 0x1},
|
||||||
|
"ZVA": {0x3, 0x4, 0x1},
|
||||||
|
}
|
||||||
|
|
||||||
|
// a64RPRFOps maps the range-prefetch operation names to their 6-bit values.
|
||||||
|
var a64RPRFOps = map[string]uint32{
|
||||||
|
"PLDKEEP": 0,
|
||||||
|
"PLDSTRM": 4,
|
||||||
|
"PSTKEEP": 1,
|
||||||
|
"PSTSTRM": 5,
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestARM64SysRegsDifferential proves the whole system-register table against
|
||||||
|
// the toolchain at once: one TEXT whose body reads every register the table
|
||||||
|
// carries (and writes every writable one), assembled by gasm and by
|
||||||
|
// go tool asm, must agree byte for byte. A single wrong op0/op1/CRn/CRm/op2
|
||||||
|
// packing names its register through the first differing word.
|
||||||
|
func TestARM64SysRegsDifferential(t *testing.T) {
|
||||||
|
names := make([]string, 0, len(a64SysRegs))
|
||||||
|
for name := range a64SysRegs {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
slices.Sort(names)
|
||||||
|
|
||||||
|
var body strings.Builder
|
||||||
|
for i, name := range names {
|
||||||
|
// R18 is the arm64 platform register and R29-R31 carry dedicated
|
||||||
|
// meanings; a plain read/write destination keeps to R0-R17.
|
||||||
|
reg := fmt.Sprintf("R%d", i%18)
|
||||||
|
if a64SysRegs[name].read {
|
||||||
|
body.WriteString(fmt.Sprintf("\tMRS %s, %s\n", name, reg))
|
||||||
|
}
|
||||||
|
if a64SysRegs[name].write {
|
||||||
|
body.WriteString(fmt.Sprintf("\tMSR %s, %s\n", reg, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·sysregs(SB), NOSPLIT, $0\n" + body.String() + "\tRET\n"
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "sysregs_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertARM64Differential(t, path, src, "sysregs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestARM64FamiliesDifferential pins the non-sysreg families the arm64
|
||||||
|
// campaign added: the LSE compare-and-swap pairs, the VMOVI immediate, the
|
||||||
|
// SIMD narrow/long shift pairs, the VLD2/VLD3/VLD4 and VST2/VST3/VST4
|
||||||
|
// structure accesses with their post-index and replicate forms, LDPSW, the
|
||||||
|
// pointer-authentication hint and the DC maintenance operation. Every
|
||||||
|
// spelling is the toolchain's own, taken from its arm64 testdata, and the
|
||||||
|
// bytes must agree word for word.
|
||||||
|
func TestARM64FamiliesDifferential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·families(SB), NOSPLIT, $0
|
||||||
|
CASPD (R2, R3), (R2), (R8, R9)
|
||||||
|
CASPW (R6, R7), (R8), (R4, R5)
|
||||||
|
VMOVI $82, V0.B16
|
||||||
|
VMOVI $146, V22.B16
|
||||||
|
VSSHLL $0, V1.B8, V2.H8
|
||||||
|
VSSHLL $7, V1.B8, V2.H8
|
||||||
|
VSSHLL2 $0, V1.B16, V2.H8
|
||||||
|
VSHRN $7, V1.H8, V0.B8
|
||||||
|
VSHRN2 $31, V1.D2, V0.S4
|
||||||
|
VLD2 (R29), [V23.H8, V24.H8]
|
||||||
|
VLD2.P 16(R0), [V18.B8, V19.B8]
|
||||||
|
VLD2.P (R1)(R2), [V15.S2, V16.S2]
|
||||||
|
VLD3 (R27), [V11.S4, V12.S4, V13.S4]
|
||||||
|
VLD3.P 48(RSP), [V11.S4, V12.S4, V13.S4]
|
||||||
|
VLD4 (R15), [V10.H4, V11.H4, V12.H4, V13.H4]
|
||||||
|
VLD4.P 32(R24), [V31.B8, V0.B8, V1.B8, V2.B8]
|
||||||
|
VLD1R (R1), [V9.B8]
|
||||||
|
VLD1R.P (R0), [V0.B16]
|
||||||
|
VLD1R.P 2(R1), [V2.H4]
|
||||||
|
VLD2R (R15), [V15.H4, V16.H4]
|
||||||
|
VLD2R.P 16(R0), [V0.D2, V1.D2]
|
||||||
|
VLD4R (R0), [V0.B8, V1.B8, V2.B8, V3.B8]
|
||||||
|
VLD4R.P 16(RSP), [V31.S4, V0.S4, V1.S4, V2.S4]
|
||||||
|
VST2 [V22.H8, V23.H8], (R23)
|
||||||
|
VST2.P [V14.H4, V15.H4], 16(R17)
|
||||||
|
VST2.P [V14.H4, V15.H4], (R3)(R17)
|
||||||
|
VST3 [V1.D2, V2.D2, V3.D2], (R11)
|
||||||
|
VST3.P [V18.S4, V19.S4, V20.S4], 48(R25)
|
||||||
|
VST4 [V22.D2, V23.D2, V24.D2, V25.D2], (R3)
|
||||||
|
VST4.P [V14.D2, V15.D2, V16.D2, V17.D2], 64(R15)
|
||||||
|
LDPSW (R0), (R1, R2)
|
||||||
|
LDPSW 4(R0), (R1, R2)
|
||||||
|
LDPSW -4(R0), (R1, R2)
|
||||||
|
PACIASP
|
||||||
|
DC IVAC, R1
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "families_arm64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertARM64Differential(t, path, src, "families")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertARM64Differential assembles the same source with gasm and with the
|
||||||
|
// toolchain for arm64 and requires the named function's code bytes to agree.
|
||||||
|
// The live oracle is a deliberate-run comparison, so -short skips it (the
|
||||||
|
// push pipeline's mode); the golden bytes of the individual encoders are
|
||||||
|
// pinned separately in every mode.
|
||||||
|
func assertARM64Differential(t *testing.T, path, src, fn string) {
|
||||||
|
t.Helper()
|
||||||
|
oracle := oracleFuncCode(t, toolAsmObject(t, path, "arm64"))
|
||||||
|
|
||||||
|
// The oracle keys its functions by the qualified object name
|
||||||
|
// (pkg.name); match on the local part.
|
||||||
|
want := map[string][]byte{}
|
||||||
|
for name, code := range oracle {
|
||||||
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||||||
|
want[after] = code
|
||||||
|
} else {
|
||||||
|
want[name] = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if want[fn] == nil {
|
||||||
|
t.Fatalf("the oracle object carries no function %q (has %v)", fn, keysOf(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
f, perrs := parser.Parse(path, src)
|
||||||
|
if len(perrs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", perrs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
got := trimTrailingZeroWords(img.Code)
|
||||||
|
wantB := trimTrailingZeroWords(want[fn])
|
||||||
|
if len(got) != len(wantB) {
|
||||||
|
t.Fatalf("gasm %d bytes, oracle %d bytes", len(got), len(wantB))
|
||||||
|
}
|
||||||
|
for i := range wantB {
|
||||||
|
if got[i] != wantB[i] {
|
||||||
|
t.Fatalf("word %d differs: gasm %08x, oracle %08x", i/4,
|
||||||
|
binary.LittleEndian.Uint32(got[i:i+4]), binary.LittleEndian.Uint32(wantB[i:i+4]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimTrailingZeroWords drops whole zero words off the end of a code span:
|
||||||
|
// an object pads a function to its alignment, and the raw image does not.
|
||||||
|
// A difference in the middle survives the trim untouched.
|
||||||
|
func trimTrailingZeroWords(b []byte) []byte {
|
||||||
|
for len(b) >= 4 {
|
||||||
|
last := b[len(b)-4:]
|
||||||
|
if last[0]|last[1]|last[2]|last[3] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = b[:len(b)-4]
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
+256
-23
@@ -8,7 +8,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assemble encodes the body of a TEXT function into x86-64 machine code,
|
// Assemble encodes the body of a TEXT function into x86-64 machine code,
|
||||||
@@ -543,11 +544,27 @@ func pcJumpOffset(op *ast.Operand) (int, bool) {
|
|||||||
// pcJumpTarget resolves a numeric jump at statement index j: N counts the
|
// pcJumpTarget resolves a numeric jump at statement index j: N counts the
|
||||||
// instruction statements after the jump itself (N = 0 is the jump's own
|
// instruction statements after the jump itself (N = 0 is the jump's own
|
||||||
// address, the classic park loop), and the target is the start of the Nth
|
// address, the classic park loop), and the target is the start of the Nth
|
||||||
// one. It reports false when the count runs past the end of the function.
|
// one. A negative N counts the same way backwards, before the jump: the
|
||||||
|
// exit loops write JMP -3(PC) to land three instructions earlier. Labels
|
||||||
|
// count not, in either direction. It reports false when the count runs
|
||||||
|
// past the end of the function, or before its first instruction.
|
||||||
func pcJumpTarget(t *ast.Text, j, n int, pcs []int) (int, bool) {
|
func pcJumpTarget(t *ast.Text, j, n int, pcs []int) (int, bool) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return pcs[j], true
|
return pcs[j], true
|
||||||
}
|
}
|
||||||
|
if n < 0 {
|
||||||
|
seen := 0
|
||||||
|
for k := j - 1; k >= 0; k-- {
|
||||||
|
if _, ok := t.Body[k].(*ast.Instr); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen--
|
||||||
|
if seen == n {
|
||||||
|
return pcs[k], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
seen := 0
|
seen := 0
|
||||||
for k := j + 1; k < len(t.Body); k++ {
|
for k := j + 1; k < len(t.Body); k++ {
|
||||||
if _, ok := t.Body[k].(*ast.Instr); !ok {
|
if _, ok := t.Body[k].(*ast.Instr); !ok {
|
||||||
@@ -792,17 +809,43 @@ func isJumpMnemonic(mnem string) bool {
|
|||||||
if mnem == "JMP" || mnem == "CALL" {
|
if mnem == "JMP" || mnem == "CALL" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if isLoopMnemonic(mnem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
_, ok := condCode(mnem)
|
_, ok := condCode(mnem)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isLoopMnemonic reports the LOOP family, rel8 alone (E0-E2).
|
||||||
|
func isLoopMnemonic(mnem string) bool {
|
||||||
|
switch mnem {
|
||||||
|
case "LOOP", "LOOPE", "LOOPNE":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// loopOpcode maps the LOOP family to its E0-E2 opcode.
|
||||||
|
func loopOpcode(mnem string) byte {
|
||||||
|
switch mnem {
|
||||||
|
case "LOOPE":
|
||||||
|
return 0xE1
|
||||||
|
case "LOOPNE":
|
||||||
|
return 0xE0
|
||||||
|
}
|
||||||
|
return 0xE2
|
||||||
|
}
|
||||||
|
|
||||||
// jumpSize returns the length of a jump instruction in the requested form:
|
// jumpSize returns the length of a jump instruction in the requested form:
|
||||||
// short (rel8) where available, otherwise the rel32 form. CALL is always
|
// short (rel8) where available, otherwise the rel32 form. CALL is always
|
||||||
// rel32.
|
// rel32; the LOOP family is rel8 alone.
|
||||||
func jumpSize(mnem string, long bool) int {
|
func jumpSize(mnem string, long bool) int {
|
||||||
if mnem == "CALL" {
|
if mnem == "CALL" {
|
||||||
return 5 // opcode + rel32
|
return 5 // opcode + rel32
|
||||||
}
|
}
|
||||||
|
if isLoopMnemonic(mnem) {
|
||||||
|
return 2 // opcode + rel8, the only form
|
||||||
|
}
|
||||||
if !long {
|
if !long {
|
||||||
return 2 // opcode + rel8
|
return 2 // opcode + rel8
|
||||||
}
|
}
|
||||||
@@ -887,6 +930,25 @@ func encodeInstr(s *ast.Instr, pc int, offsets map[string]int, fi frameInfo, lon
|
|||||||
|
|
||||||
func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch, []floatPoolEntry, error) {
|
func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch, []floatPoolEntry, error) {
|
||||||
mnemUpper := strings.ToUpper(s.Mnemonic.Text)
|
mnemUpper := strings.ToUpper(s.Mnemonic.Text)
|
||||||
|
|
||||||
|
// The extended-instruction layer: a statement whose mnemonic is
|
||||||
|
// registered in the extension registry and that the scalar paths cannot
|
||||||
|
// encode goes through the registry, before operandFromAST would reject
|
||||||
|
// operands only the layer reads (asm/amd64_ext.go). The registry's
|
||||||
|
// Feature field stays metadata at assembly time: the assembler has no
|
||||||
|
// CPU, and the toolchain does not gate assembly on CPU features, so
|
||||||
|
// every registered feature assembles.
|
||||||
|
if extops, pinned, convErr := amd64ExtStatement(mnemUpper, s.Operands); pinned {
|
||||||
|
if convErr != nil {
|
||||||
|
return nil, nil, nil, convErr
|
||||||
|
}
|
||||||
|
code, encErr := EncodeExtension(arch.AMD64, mnemUpper, extops...)
|
||||||
|
if encErr != nil {
|
||||||
|
return nil, nil, nil, encErr
|
||||||
|
}
|
||||||
|
return code, nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
if mnemUpper == "FUNCDATA" || mnemUpper == "PCDATA" {
|
if mnemUpper == "FUNCDATA" || mnemUpper == "PCDATA" {
|
||||||
code, err := encodeBookkeeping(mnemUpper, s)
|
code, err := encodeBookkeeping(mnemUpper, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -929,6 +991,16 @@ func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch
|
|||||||
if (mnemUpper == "MOVQ" || mnemUpper == "MOVL") && len(s.Operands) == 2 && isBareTLS(s.Operands[0]) {
|
if (mnemUpper == "MOVQ" || mnemUpper == "MOVL") && len(s.Operands) == 2 && isBareTLS(s.Operands[0]) {
|
||||||
return encodeTLSBaseLoad(s, fi, link)
|
return encodeTLSBaseLoad(s, fi, link)
|
||||||
}
|
}
|
||||||
|
// The old paired-register shift spelling, SHLL CX, R11:AX (a colon
|
||||||
|
// between the two registers), is the toolchain's SHLD family: SHLDL CL,
|
||||||
|
// AX, R11 with the count register first, the paired source in the reg
|
||||||
|
// field and the pair's head in r/m.
|
||||||
|
if code, ps, err := encodeColonShift(s, mnemUpper, fi, link); code != nil || err != nil {
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return code, ps, nil, nil
|
||||||
|
}
|
||||||
_, size := splitSize(mnemUpper)
|
_, size := splitSize(mnemUpper)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 8
|
size = 8
|
||||||
@@ -1035,6 +1107,66 @@ func encodeBookkeeping(upper string, s *ast.Instr) ([]byte, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeColonShift encodes the paired-register shift spellings, SHLx CX,
|
||||||
|
// dst:src: the toolchain reads them as the SHLD family (double-precision
|
||||||
|
// shift by CL), reg = the paired source, r/m = the pair's head. The second
|
||||||
|
// operand's raw text carries the colon; ok reports the spelling was found.
|
||||||
|
func encodeColonShift(s *ast.Instr, mnemUpper string, fi frameInfo, link *linkInfo) ([]byte, []sbPatch, error) {
|
||||||
|
base, _ := strings.CutPrefix(mnemUpper, "SHL")
|
||||||
|
if base == mnemUpper || len(s.Operands) != 2 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
_, size := splitSize(mnemUpper)
|
||||||
|
raw := strings.ReplaceAll(s.Operands[1].Raw, " ", "")
|
||||||
|
head, tail, ok := strings.Cut(raw, ":")
|
||||||
|
if !ok || head == "" || tail == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
headReg, ok1 := ParseReg(head)
|
||||||
|
srcReg, ok2 := ParseReg(tail)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return nil, nil, fmt.Errorf("%s: invalid paired register %q", mnemUpper, s.Operands[1].Raw)
|
||||||
|
}
|
||||||
|
cnt, err := operandFromAST(mnemUpper, s.Operands[0], size, fi, link)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
cntReg, ok := cnt.(Reg)
|
||||||
|
if !ok || cntReg.idx != 1 {
|
||||||
|
return nil, nil, fmt.Errorf("%s: the paired-register form counts in CL", mnemUpper)
|
||||||
|
}
|
||||||
|
// SHLD r/m, reg, CL: 0F A5 (REX.W for the 64-bit width).
|
||||||
|
e := &enc{}
|
||||||
|
i := &instr{rexW: size == 8, opcode: []byte{0x0F, 0xA5}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, srcReg, headReg, size); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := e.emit(i); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ps := make([]sbPatch, len(e.patches))
|
||||||
|
for j, p := range e.patches {
|
||||||
|
ps[j] = sbPatch{off: p.off, name: p.name, addend: p.addend}
|
||||||
|
}
|
||||||
|
return e.out, ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailingIndexGroup recovers a trailing "(index*scale)" or "(index)" group
|
||||||
|
// from an operand's raw text: the symbol-pseudo parse returns before the
|
||||||
|
// index group, so foo(SP)(AX*1) keeps its index only in the spelling.
|
||||||
|
func trailingIndexGroup(raw string) (string, int, bool) {
|
||||||
|
compact := strings.ReplaceAll(raw, " ", "")
|
||||||
|
if !strings.HasSuffix(compact, ")") {
|
||||||
|
return "", 0, false
|
||||||
|
}
|
||||||
|
open := strings.LastIndex(compact, "(")
|
||||||
|
if open < 2 || !strings.Contains(compact[:open], ")") {
|
||||||
|
return "", 0, false // one group alone: no trailing index
|
||||||
|
}
|
||||||
|
name, scale, _, ok := cutParenGroup(compact[open:])
|
||||||
|
return name, scale, ok
|
||||||
|
}
|
||||||
|
|
||||||
// encodeJump encodes a JMP/CALL/Jcc with a relative offset resolved from the
|
// encodeJump encodes a JMP/CALL/Jcc with a relative offset resolved from the
|
||||||
// target label or from a numeric ±N(PC) instruction count, in the short
|
// target label or from a numeric ±N(PC) instruction count, in the short
|
||||||
// (rel8) or long (rel32) form. numTarget is the resolved byte offset of a
|
// (rel8) or long (rel32) form. numTarget is the resolved byte offset of a
|
||||||
@@ -1069,9 +1201,15 @@ func encodeJump(s *ast.Instr, mnem string, pc int, offsets map[string]int, long
|
|||||||
if mnem == "JMP" {
|
if mnem == "JMP" {
|
||||||
return []byte{0xEB, byte(int8(rel))}, nil
|
return []byte{0xEB, byte(int8(rel))}, nil
|
||||||
}
|
}
|
||||||
|
if isLoopMnemonic(mnem) {
|
||||||
|
return []byte{loopOpcode(mnem), byte(int8(rel))}, nil
|
||||||
|
}
|
||||||
cc, _ := condCode(mnem)
|
cc, _ := condCode(mnem)
|
||||||
return []byte{0x70 + byte(cc), byte(int8(rel))}, nil
|
return []byte{0x70 + byte(cc), byte(int8(rel))}, nil
|
||||||
}
|
}
|
||||||
|
if isLoopMnemonic(mnem) {
|
||||||
|
return nil, fmt.Errorf("%s has no long form", mnem)
|
||||||
|
}
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "JMP":
|
case "JMP":
|
||||||
return append([]byte{0xE9}, le32(rel)...), nil
|
return append([]byte{0xE9}, le32(rel)...), nil
|
||||||
@@ -1123,6 +1261,72 @@ func labelName(op *ast.Operand) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jumpOperand returns the branch-target operand of a JMP/CALL, rewriting the
|
||||||
|
// `*`-prefixed indirect spellings (JMP *(R12), JMP *4(SP)) into their plain
|
||||||
|
// memory form. The star marks an indirect target and changes no bytes; the
|
||||||
|
// address parser leaves the operand's address empty because of the leading
|
||||||
|
// star, so the fields are rebuilt from the raw text onto a copy of the
|
||||||
|
// operand, never on the shared syntax tree.
|
||||||
|
func jumpOperand(s *ast.Instr) *ast.Operand {
|
||||||
|
if len(s.Operands) != 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
op := s.Operands[0]
|
||||||
|
compact := strings.ReplaceAll(op.Raw, " ", "")
|
||||||
|
inner, ok := strings.CutPrefix(compact, "*")
|
||||||
|
if !ok {
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
var addr ast.Address
|
||||||
|
if i := strings.IndexByte(inner, '('); i > 0 {
|
||||||
|
v, err := strconv.ParseInt(inner[:i], 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
addr.Offset, addr.HasOff = v, true
|
||||||
|
inner = inner[i:]
|
||||||
|
}
|
||||||
|
base, _, rest, ok := cutParenGroup(inner)
|
||||||
|
if !ok {
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
if base != "" {
|
||||||
|
addr.Base = base
|
||||||
|
}
|
||||||
|
if rest != "" {
|
||||||
|
idx, scale, _, ok := cutParenGroup(rest)
|
||||||
|
if ok && idx != "" {
|
||||||
|
addr.Index = idx
|
||||||
|
addr.Scale = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := *op
|
||||||
|
c.Addr = addr
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// cutParenGroup splits a leading "(name)" or "(name*n)" off s, returning the
|
||||||
|
// inner text, the scale it names (1 when the group spells no multiplier) and
|
||||||
|
// the remainder.
|
||||||
|
func cutParenGroup(s string) (name string, scale int, rest string, ok bool) {
|
||||||
|
if !strings.HasPrefix(s, "(") {
|
||||||
|
return "", 0, "", false
|
||||||
|
}
|
||||||
|
i := strings.IndexByte(s, ')')
|
||||||
|
if i < 0 {
|
||||||
|
return "", 0, "", false
|
||||||
|
}
|
||||||
|
inner, rest := s[1:i], s[i+1:]
|
||||||
|
if before, after, ok := strings.Cut(inner, "*"); ok {
|
||||||
|
n, err := strconv.Atoi(after)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, "", false
|
||||||
|
}
|
||||||
|
return before, n, rest, true
|
||||||
|
}
|
||||||
|
return inner, 1, rest, true
|
||||||
|
}
|
||||||
|
|
||||||
// indirectJumpTarget reports whether the JMP/CALL operand addresses a
|
// indirectJumpTarget reports whether the JMP/CALL operand addresses a
|
||||||
// register or a memory location rather than a label or a static symbol.
|
// register or a memory location rather than a label or a static symbol.
|
||||||
// A bare identifier is a register when the register table knows the name and
|
// A bare identifier is a register when the register table knows the name and
|
||||||
@@ -1131,7 +1335,14 @@ func indirectJumpTarget(s *ast.Instr) bool {
|
|||||||
if len(s.Operands) != 1 || s.Operands[0].Kind != ast.OpAddr {
|
if len(s.Operands) != 1 || s.Operands[0].Kind != ast.OpAddr {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
a := s.Operands[0].Addr
|
op := jumpOperand(s)
|
||||||
|
if op == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if op != s.Operands[0] {
|
||||||
|
return true // the star marker spells an indirect target
|
||||||
|
}
|
||||||
|
a := op.Addr
|
||||||
// ±N(PC) is the numeric relative form, the PC counts instructions from
|
// ±N(PC) is the numeric relative form, the PC counts instructions from
|
||||||
// the branch: relative, not indirect.
|
// the branch: relative, not indirect.
|
||||||
if a.Base == "PC" || a.Index == "PC" {
|
if a.Base == "PC" || a.Index == "PC" {
|
||||||
@@ -1149,18 +1360,20 @@ func indirectJumpTarget(s *ast.Instr) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encodeIndirectJump assembles a JMP/CALL through a register or memory
|
// encodeIndirectJump assembles a JMP/CALL through a register or memory
|
||||||
// operand, which carries no relocation and no label to resolve.
|
// operand, which carries no relocation and no label to resolve. The
|
||||||
|
// `*`-prefixed spellings go through jumpOperand first, their star rebuilt
|
||||||
|
// into a plain memory operand.
|
||||||
func encodeIndirectJump(s *ast.Instr, mnem string) ([]byte, error) {
|
func encodeIndirectJump(s *ast.Instr, mnem string) ([]byte, error) {
|
||||||
ops := make([]Operand, len(s.Operands))
|
op := s.Operands[0]
|
||||||
for i, op := range s.Operands {
|
if cleaned := jumpOperand(s); cleaned != nil {
|
||||||
o, err := operandFromAST(mnem, op, 8, frameInfo{}, nil)
|
op = cleaned
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
o, err := operandFromAST(mnem, op, 8, frameInfo{}, nil)
|
||||||
}
|
if err != nil {
|
||||||
ops[i] = o
|
return nil, err
|
||||||
}
|
}
|
||||||
e := &enc{}
|
e := &enc{}
|
||||||
if err := e.encodeIndirectBranch(mnem, ops); err != nil {
|
if err := e.encodeIndirectBranch(mnem, []Operand{o}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e.out, nil
|
return e.out, nil
|
||||||
@@ -1229,31 +1442,51 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
|
|||||||
off := a.Sym.Offset + fi.fpAdjust
|
off := a.Sym.Offset + fi.fpAdjust
|
||||||
return Mem{Base: spReg, Disp: off, HasBase: true, Size: size}, nil
|
return Mem{Base: spReg, Disp: off, HasBase: true, Size: size}, nil
|
||||||
}
|
}
|
||||||
// SP-relative local: x-N(SP) → (spAdjust + offset)(SP).
|
// SP-relative local: x-N(SP) → (spAdjust + offset)(SP), keeping a scaled
|
||||||
|
// index beside the virtual stack pointer (foo(SP)(AX*1)). The
|
||||||
|
// symbol-pseudo parse returns before the index group, so the index
|
||||||
|
// is recovered from the raw text when the address lacks it.
|
||||||
if a.Sym != nil && a.Sym.Pseudo == "SP" && a.Base == "" {
|
if a.Sym != nil && a.Sym.Pseudo == "SP" && a.Base == "" {
|
||||||
off := fi.spAdjust + a.Sym.Offset
|
off := fi.spAdjust + a.Sym.Offset
|
||||||
return Mem{Base: spReg, Disp: off, HasBase: true, Size: size}, nil
|
m := Mem{Base: spReg, Disp: off, HasBase: true, Size: size}
|
||||||
|
if name, scale, ok := trailingIndexGroup(op.Raw); ok {
|
||||||
|
idx, ok := ParseReg(name)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown index register %q", name)
|
||||||
|
}
|
||||||
|
m.Index = idx
|
||||||
|
m.Scale = scale
|
||||||
|
m.HasIndex = true
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
// SB (global symbol): a symbol defined in the same file (GLOBL) is
|
// SB (global symbol): a symbol defined in the same file (GLOBL) is
|
||||||
// encoded RIP-relative and resolved by the file-level layout;
|
// encoded RIP-relative and resolved by the file-level layout;
|
||||||
// anything not defined here needs object-file emission.
|
// anything not defined here needs object-file emission. A static
|
||||||
|
// (file-local) spelling of an undefined symbol defers the same way
|
||||||
|
// the toolchain does: the relocation names it and the linker decides.
|
||||||
if a.Sym != nil && a.Sym.Pseudo == "SB" {
|
if a.Sym != nil && a.Sym.Pseudo == "SB" {
|
||||||
if link == nil || link.symbols == nil {
|
if link == nil || link.symbols == nil {
|
||||||
return nil, fmt.Errorf("symbol %q needs file-level assembly (AssembleFile)", a.Sym.Name)
|
return nil, fmt.Errorf("symbol %q needs file-level assembly (AssembleFile)", a.Sym.Name)
|
||||||
}
|
}
|
||||||
if !link.symbols[a.Sym.Name] {
|
if !link.symbols[a.Sym.Name] && !link.allowExternal {
|
||||||
if a.Sym.Static {
|
return nil, fmt.Errorf("external symbol %q needs object-file emission", a.Sym.Name)
|
||||||
return nil, fmt.Errorf("undefined symbol %q", a.Sym.Name)
|
|
||||||
}
|
|
||||||
if !link.allowExternal {
|
|
||||||
return nil, fmt.Errorf("external symbol %q needs object-file emission", a.Sym.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sbMem{size: size, name: a.Sym.Name, addend: a.Sym.Offset}, nil
|
return sbMem{size: size, name: a.Sym.Name, addend: a.Sym.Offset}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory with a real base register: (base), off(base), (base)(index*scale).
|
// Memory with a real base register: (base), off(base), (base)(index*scale).
|
||||||
if a.Base != "" {
|
if a.Base != "" {
|
||||||
|
// The TLS pseudo-base, off(TLS): the segment-prefixed absolute
|
||||||
|
// the thread-local access lowers to, 64 8B 04 25 with its
|
||||||
|
// R_TLS_LE patch site on the disp32.
|
||||||
|
if a.Base == "TLS" {
|
||||||
|
seg := byte(0x64) // FS on linux, freebsd, plan9
|
||||||
|
if link != nil && link.goos == "windows" {
|
||||||
|
seg = 0x65 // GS
|
||||||
|
}
|
||||||
|
return TLSMem{Disp: a.Offset, Size: size, Seg: seg}, nil
|
||||||
|
}
|
||||||
// Segment-absolute: 0x30(GS) and 0x28(FS), the windows TLS
|
// Segment-absolute: 0x30(GS) and 0x28(FS), the windows TLS
|
||||||
// spellings. The segment override prefixes a disp32 absolute
|
// spellings. The segment override prefixes a disp32 absolute
|
||||||
// reference with no relocation.
|
// reference with no relocation.
|
||||||
|
|||||||
+54
-2
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstText parses src and returns its first TEXT function.
|
// firstText parses src and returns its first TEXT function.
|
||||||
@@ -370,6 +370,58 @@ end:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAssembleNumericPCJumps pins the numeric ±N(PC) branch operands: N
|
||||||
|
// counts instruction statements, skipping labels, in both directions (the
|
||||||
|
// runtime's exit loops write JMP -3(PC)), N = 0 parks on the jump itself.
|
||||||
|
func TestAssembleNumericPCJumps(t *testing.T) {
|
||||||
|
fn := firstText(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·exit(SB), NOSPLIT, $0
|
||||||
|
MOVB $1, AL
|
||||||
|
lab:
|
||||||
|
MOVB $2, AL
|
||||||
|
MOVB $3, AL
|
||||||
|
JMP -3(PC)
|
||||||
|
MOVB $4, AL
|
||||||
|
park:
|
||||||
|
JMP 0(PC)
|
||||||
|
MOVB $5, AL
|
||||||
|
JMP 2(PC)
|
||||||
|
MOVB $6, AL
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code, _, err := Assemble(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Assemble: %v", err)
|
||||||
|
}
|
||||||
|
// From the Go-assembled function:
|
||||||
|
// MOVB $1, AL b001
|
||||||
|
// MOVB $2, AL b002
|
||||||
|
// MOVB $3, AL b003
|
||||||
|
// JMP -3(PC) ebf8 (three instructions back, past lab:)
|
||||||
|
// MOVB $4, AL b004
|
||||||
|
// JMP 0(PC) ebfe (the park loop)
|
||||||
|
// MOVB $5, AL b005
|
||||||
|
// JMP 2(PC) eb02 (over MOVB $6 to the RET)
|
||||||
|
// MOVB $6, AL b006
|
||||||
|
// RET c3
|
||||||
|
want := []byte{
|
||||||
|
0xb0, 0x01,
|
||||||
|
0xb0, 0x02,
|
||||||
|
0xb0, 0x03,
|
||||||
|
0xeb, 0xf8,
|
||||||
|
0xb0, 0x04,
|
||||||
|
0xeb, 0xfe,
|
||||||
|
0xb0, 0x05,
|
||||||
|
0xeb, 0x02,
|
||||||
|
0xb0, 0x06,
|
||||||
|
0xc3,
|
||||||
|
}
|
||||||
|
if hexBytes(code) != hexBytes(want) {
|
||||||
|
t.Errorf("numeric-PC mismatch:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAssemblePrefetch(t *testing.T) {
|
func TestAssemblePrefetch(t *testing.T) {
|
||||||
fn := firstText(t, `
|
fn := firstText(t, `
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The object-file tests share one source: two exported functions, one
|
// The object-file tests share one source: two exported functions, one
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ const (
|
|||||||
rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset)
|
rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset)
|
||||||
rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction)
|
rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction)
|
||||||
rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset)
|
rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset)
|
||||||
|
// R_AARCH64_TLSLE_MOVW_TPREL_G0 (debug/elf 547): the local-exec TLS
|
||||||
|
// load's MOVZ field, the module offset at bits [15:0].
|
||||||
|
rArm64TLSLEMovwTprelG0 = 547
|
||||||
// R_AARCH64_ABS32 (debug/elf 258): the absolute 32-bit address of a
|
// 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)
|
// symbol, the R_ADDR shape a 4-byte DATA field carries. ABS64 (257)
|
||||||
// lives with the DWARF fixup constants as rAARCH64Abs64.
|
// lives with the DWARF fixup constants as rAARCH64Abs64.
|
||||||
@@ -131,6 +134,11 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
||||||
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend},
|
||||||
)
|
)
|
||||||
|
case RelArm64TLSLE:
|
||||||
|
// The local-exec MOVZ: one relocation over the imm16 field.
|
||||||
|
relas = append(relas, elfRela{
|
||||||
|
off: uint64(fn.Offset + r.Off), typ: rArm64TLSLEMovwTprelG0, sym: idx, addend: r.Addend,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestELFLOONG64Object checks the structure of the emitted LoongArch ELF64
|
// TestELFLOONG64Object checks the structure of the emitted LoongArch ELF64
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const (
|
|||||||
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
||||||
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
||||||
rRISCVPCRELLO12S = 25 // R_RISCV_PCREL_LO12_S
|
rRISCVPCRELLO12S = 25 // R_RISCV_PCREL_LO12_S
|
||||||
|
rRISCVTPRELHI20 = 29 // R_RISCV_TPREL_HI20
|
||||||
|
rRISCVTPRELLO12I = 30 // R_RISCV_TPREL_LO12_I
|
||||||
// R_RISCV_32 (debug/elf 1): the absolute 32-bit address of a symbol,
|
// 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
|
// the R_ADDR shape a 4-byte DATA field carries. R_RISCV_64 (2) lives
|
||||||
// with the DWARF fixup constants as rRISCVAbs64.
|
// with the DWARF fixup constants as rRISCVAbs64.
|
||||||
@@ -128,6 +130,15 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
)
|
)
|
||||||
case RelRISCVJal:
|
case RelRISCVJal:
|
||||||
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
||||||
|
case RelRISCVTLSLE:
|
||||||
|
// The local-exec pair splits into the TPREL HI20 on the LUI
|
||||||
|
// and the TPREL LO12_I on the ADDIW, both against the symbol
|
||||||
|
// (a thread offset, not PC-relative, so the LO12 needs no
|
||||||
|
// label indirection).
|
||||||
|
relas = append(relas,
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVTPRELHI20, sym: idx, addend: r.Addend},
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVTPRELLO12I, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestELFRISCVObjectDataRelocation checks that a symbol-valued DATA field
|
// TestELFRISCVObjectDataRelocation checks that a symbol-valued DATA field
|
||||||
|
|||||||
+28
-10
@@ -16,15 +16,35 @@ import "strings"
|
|||||||
func Encodable(mnemonic string) bool {
|
func Encodable(mnemonic string) bool {
|
||||||
upper := strings.ToUpper(mnemonic)
|
upper := strings.ToUpper(mnemonic)
|
||||||
|
|
||||||
|
// The corpus families first, mirroring encode()'s dispatch order: a
|
||||||
|
// family owning the name decides encodability whatever the suffix split
|
||||||
|
// would make of it.
|
||||||
|
if amd64FamilyEncodable(upper) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Fixed-name instructions (no size suffix).
|
// Fixed-name instructions (no size suffix).
|
||||||
switch upper {
|
switch upper {
|
||||||
case "RET", "NOP", "CALL", "JMP",
|
case "RET", "NOP", "CALL", "JMP",
|
||||||
"POPFQ", "PUSHFQ", "INT", "LDMXCSR", "STMXCSR", "CMPSD", "SHA256RNDS2",
|
"POPFQ", "PUSHFQ", "INT", "LDMXCSR", "STMXCSR", "CMPSD", "SHA256RNDS2",
|
||||||
|
// The SSE compare family sharing CMPSD's predicate-last shape, the
|
||||||
|
// far return with its stack pop, the loop family, the bank-crossing
|
||||||
|
// MMX moves and the one-operand system controls.
|
||||||
|
"CMPSS", "CMPPS", "CMPPD", "RETFL",
|
||||||
|
"LOOP", "LOOPE", "LOOPNE",
|
||||||
|
"MOVDQ2Q", "MOVQ2DQ",
|
||||||
|
"ENDBR64", "CLWB", "TPAUSE", "UMONITOR", "UMWAIT", "RDPID", "CLDEMOTE",
|
||||||
// The literal-data pseudo-ops, the accepted-and-ignored END and
|
// The literal-data pseudo-ops, the accepted-and-ignored END and
|
||||||
// bookkeeping statements, and the SP adjust.
|
// bookkeeping statements, and the SP adjust.
|
||||||
"BYTE", "WORD", "LONG", "QUAD", "END", "ADJSP", "FUNCDATA", "PCDATA":
|
"BYTE", "WORD", "LONG", "QUAD", "END", "ADJSP", "FUNCDATA", "PCDATA":
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if _, ok := sysUnaryTable[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := sseStoreOnly[upper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if _, ok := noOperandTable[upper]; ok {
|
if _, ok := noOperandTable[upper]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -42,16 +62,14 @@ func Encodable(mnemonic string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// CMOV carries size then condition (CMOVLGT); SET carries the condition
|
// CMOV carries size then condition (CMOVLGT), or the renderer's
|
||||||
// alone (SETNE). The size letter is checked exactly as encodeCmov does,
|
// condition alone (CMOVLE) with the width from the operand; SET carries
|
||||||
// so a spelling like CMOVBGT is not reported encodable when Encode
|
// the condition alone (SETNE). cmovCondition checks the suffix exactly
|
||||||
// would reject it.
|
// as encodeCmov does, so a spelling like CMOVBGT is not reported
|
||||||
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 {
|
// encodable when Encode would reject it.
|
||||||
switch rest[0] {
|
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok {
|
||||||
case 'W', 'L', 'Q':
|
if _, _, ok := cmovCondition(rest); ok {
|
||||||
if _, ok := jccMap[rest[1:]]; ok {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
||||||
|
|||||||
+107
-4
@@ -70,12 +70,33 @@ type encPatch struct {
|
|||||||
func (e *enc) encode(mnem string, ops []Operand) error {
|
func (e *enc) encode(mnem string, ops []Operand) error {
|
||||||
upper := strings.ToUpper(mnem)
|
upper := strings.ToUpper(mnem)
|
||||||
|
|
||||||
|
// The corpus families (x87, the system and string controls, XSAVE)
|
||||||
|
// dispatch on the full name from the amd64 family files before the
|
||||||
|
// fixed-name switch, the way their tables spell the mnemonics out.
|
||||||
|
if handled, err := e.encodeAmd64Family(upper, ops); handled {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Fixed-name instructions (no size suffix).
|
// Fixed-name instructions (no size suffix).
|
||||||
switch {
|
switch {
|
||||||
case upper == "RET":
|
case upper == "RET":
|
||||||
|
// RET sym(SB), the absolute return: the toolchain encodes it as a
|
||||||
|
// tail jump, E9 rel32 with a call relocation against the symbol.
|
||||||
|
if len(ops) == 1 {
|
||||||
|
if m, ok := ops[0].(sbMem); ok {
|
||||||
|
return e.emit(&instr{opcode: []byte{0xE9}, modrm: -1, sib: -1, disp: le32(0), sb: &sbRef{name: m.name, addend: m.addend}})
|
||||||
|
}
|
||||||
|
return fmt.Errorf("RET: unsupported operand")
|
||||||
|
}
|
||||||
|
if len(ops) != 0 {
|
||||||
|
return fmt.Errorf("RET expects no operands, got %d", len(ops))
|
||||||
|
}
|
||||||
return e.encodeRet()
|
return e.encodeRet()
|
||||||
case upper == "NOP":
|
case upper == "NOP":
|
||||||
return e.emit(&instr{opcode: []byte{0x90}, modrm: -1, sib: -1})
|
// The toolchain consumes every NOP statement as a pseudo and emits
|
||||||
|
// nothing for it, operands included (a bare NOP, NOP AX and
|
||||||
|
// NOP sym(SB) all vanish from the object).
|
||||||
|
return nil
|
||||||
case upper == "CALL" || upper == "JMP":
|
case upper == "CALL" || upper == "JMP":
|
||||||
// Through a register or memory: FF /2 (CALL) or FF /4 (JMP).
|
// Through a register or memory: FF /2 (CALL) or FF /4 (JMP).
|
||||||
// Anything else is a rel32 against a label resolved by the assembler.
|
// Anything else is a rel32 against a label resolved by the assembler.
|
||||||
@@ -102,6 +123,15 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
return e.emit(&instr{opcode: op, modrm: -1, sib: -1})
|
return e.emit(&instr{opcode: op, modrm: -1, sib: -1})
|
||||||
}
|
}
|
||||||
|
// One-operand system instructions whose reg field is a fixed digit:
|
||||||
|
// the cache and wait controls under 0F AE/0F 1C and the RDPID read.
|
||||||
|
if m, ok := sysUnaryTable[upper]; ok {
|
||||||
|
return e.encodeSysUnary(upper, m, ops)
|
||||||
|
}
|
||||||
|
// The store-only SSE moves (the non-temporal store).
|
||||||
|
if m, ok := sseStoreOnly[upper]; ok {
|
||||||
|
return e.encodeSSEStoreOnly(upper, m, ops)
|
||||||
|
}
|
||||||
// POPFQ/PUSHFQ are exact names: the bare POPF/PUSHF and the L spellings
|
// POPFQ/PUSHFQ are exact names: the bare POPF/PUSHF and the L spellings
|
||||||
// are rejected by go tool asm in 64-bit mode, so they stay unsupported.
|
// are rejected by go tool asm in 64-bit mode, so they stay unsupported.
|
||||||
switch upper {
|
switch upper {
|
||||||
@@ -117,14 +147,70 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
return e.emit(&instr{opcode: []byte{0x9C}, modrm: -1, sib: -1})
|
return e.emit(&instr{opcode: []byte{0x9C}, modrm: -1, sib: -1})
|
||||||
case "INT":
|
case "INT":
|
||||||
return e.encodeInt(ops)
|
return e.encodeInt(ops)
|
||||||
|
// The LOOP family outside the assembler's label settlement: the operand
|
||||||
|
// is the already-computed rel8 (E0-E2).
|
||||||
|
case "LOOP", "LOOPE", "LOOPNE":
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 operand, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok || !fits8(int64(imm)) {
|
||||||
|
return fmt.Errorf("%s: relative offset must be a signed byte", upper)
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{loopOpcode(upper)}, modrm: -1, sib: -1, imm: []byte{byte(int8(imm))}})
|
||||||
case "LDMXCSR":
|
case "LDMXCSR":
|
||||||
return e.encodeMxcsr(2, ops)
|
return e.encodeMxcsr(2, ops)
|
||||||
case "STMXCSR":
|
case "STMXCSR":
|
||||||
return e.encodeMxcsr(3, ops)
|
return e.encodeMxcsr(3, ops)
|
||||||
// CMPSD is the scalar double compare, whose predicate immediate comes
|
// CMPSD is the scalar double compare, whose predicate immediate comes
|
||||||
// LAST in Plan 9 order (src, dst, $imm).
|
// LAST in Plan 9 order (src, dst, $imm); the family shares the shape.
|
||||||
case "CMPSD":
|
case "CMPSD":
|
||||||
return e.encodeCmpsd(ops)
|
return e.encodeSSECmp("CMPSD", 0xF2, ops)
|
||||||
|
case "CMPSS":
|
||||||
|
return e.encodeSSECmp("CMPSS", 0xF3, ops)
|
||||||
|
case "CMPPS":
|
||||||
|
return e.encodeSSECmp("CMPPS", 0x00, ops)
|
||||||
|
case "CMPPD":
|
||||||
|
return e.encodeSSECmp("CMPPD", 0x66, ops)
|
||||||
|
// RETFL pops the immediate's worth of bytes after the far return
|
||||||
|
// (LRET iw: CA imm16), the toolchain's RETF spelling with a stack
|
||||||
|
// adjustment.
|
||||||
|
case "RETFL":
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("RETFL expects 1 operand, got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("RETFL expects an immediate")
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0xCA}, modrm: -1, sib: -1, imm: le16(int64(imm))})
|
||||||
|
// MOVDQ2Q/MOVQ2DQ cross the MMX and XMM banks, and each direction
|
||||||
|
// carries its own mandatory prefix: the toolchain renders F3 0F D6 as
|
||||||
|
// MOVQ2DQ (the MMX source, XMM destination) and F2 0F D6 as MOVDQ2Q
|
||||||
|
// (the XMM source, MMX destination), the register in the reg field, the
|
||||||
|
// other bank's in r/m.
|
||||||
|
case "MOVDQ2Q", "MOVQ2DQ":
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, ok1 := ops[0].(Reg)
|
||||||
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return fmt.Errorf("%s takes register operands alone", upper)
|
||||||
|
}
|
||||||
|
if upper == "MOVDQ2Q" && (!srcReg.isVec() || !dstReg.mmx) ||
|
||||||
|
upper == "MOVQ2DQ" && (!srcReg.mmx || !dstReg.isVec()) {
|
||||||
|
return fmt.Errorf("%s crosses the XMM and MMX banks in that order", upper)
|
||||||
|
}
|
||||||
|
prefix := byte(0xF2)
|
||||||
|
if upper == "MOVQ2DQ" {
|
||||||
|
prefix = 0xF3
|
||||||
|
}
|
||||||
|
i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, srcReg, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
// SHA256RNDS2 carries the round constant in a literal X0 first operand.
|
// SHA256RNDS2 carries the round constant in a literal X0 first operand.
|
||||||
case "SHA256RNDS2":
|
case "SHA256RNDS2":
|
||||||
return e.encodeSha256rnds2(ops)
|
return e.encodeSha256rnds2(ops)
|
||||||
@@ -171,6 +257,19 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
base, size := splitSize(upper)
|
base, size := splitSize(upper)
|
||||||
|
// The scalar families that own byte forms settle the width against the
|
||||||
|
// register operands here, before the unsuffixed 64-bit default applies,
|
||||||
|
// so a literal Q suffix stays distinguishable from no suffix at all
|
||||||
|
// (CRC32 DL, R11 is the byte form; CRC32Q DL, R11 is a conflict).
|
||||||
|
if byteFormBase[base] {
|
||||||
|
w, err := operandWidth(mnem, size, widthOperands(base, ops))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w != 0 {
|
||||||
|
size = w
|
||||||
|
}
|
||||||
|
}
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
||||||
}
|
}
|
||||||
@@ -220,8 +319,12 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
case "MOV":
|
case "MOV":
|
||||||
return e.encodeMov(ops, size)
|
return e.encodeMov(ops, size)
|
||||||
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
|
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
|
||||||
// REX.W and all.
|
// REX.W and all. The alias takes no byte register either, the same
|
||||||
|
// conflict rule the Q-suffixed spelling answers to.
|
||||||
case "MOVD":
|
case "MOVD":
|
||||||
|
if _, err := operandWidth(mnem, 8, ops); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return e.encodeMov(ops, 8)
|
return e.encodeMov(ops, 8)
|
||||||
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
|
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
|
||||||
return e.encodeALU(aluOp[base], ops, size)
|
return e.encodeALU(aluOp[base], ops, size)
|
||||||
|
|||||||
+172
-7
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// decode encodes an instruction and decodes it back, returning the decoded
|
// decode encodes an instruction and decodes it back, returning the decoded
|
||||||
@@ -265,7 +265,11 @@ func TestImul(t *testing.T) {
|
|||||||
|
|
||||||
func TestControl(t *testing.T) {
|
func TestControl(t *testing.T) {
|
||||||
checkSyntax(t, "ret", "RET")
|
checkSyntax(t, "ret", "RET")
|
||||||
checkSyntax(t, "nop", "NOP")
|
// NOP contributes nothing on amd64, consumed whole by the toolchain as
|
||||||
|
// a pseudo; only the bytes pin it (no decodable instruction remains).
|
||||||
|
if code, err := Encode("NOP"); err != nil || len(code) != 0 {
|
||||||
|
t.Errorf("NOP: bytes %x (err %v), want empty", code, err)
|
||||||
|
}
|
||||||
checkOp(t, x86asm.JMP, "JMP", Imm(0))
|
checkOp(t, x86asm.JMP, "JMP", Imm(0))
|
||||||
checkOp(t, x86asm.CALL, "CALL", Imm(0))
|
checkOp(t, x86asm.CALL, "CALL", Imm(0))
|
||||||
checkOp(t, x86asm.JGE, "JGE", Imm(0))
|
checkOp(t, x86asm.JGE, "JGE", Imm(0))
|
||||||
@@ -407,6 +411,16 @@ func TestScalarGroundTruth(t *testing.T) {
|
|||||||
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
||||||
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
||||||
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
||||||
|
// The renderer's condition spellings carry no size letter; the width
|
||||||
|
// rides the operand registers and the bytes match the toolchain's own
|
||||||
|
// size-prefixed spellings (CMOVQLE/CMOVLLE/CMOVWLE pinned from go tool
|
||||||
|
// asm). CMOVLE with the 16-bit registers reproduces the disasm
|
||||||
|
// fixture's 660f4e13 row byte for byte.
|
||||||
|
{"CMOVLE (BX),DX", "CMOVLE", []Operand{Ptr(BX, 0, 2), DX}, "660f4e13", "CMOVLE"},
|
||||||
|
{"CMOVQLE AX,BX", "CMOVQLE", []Operand{Reg{idx: 0, size: 8}, Reg{idx: 3, size: 8}}, "480f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVLLE AX,BX", "CMOVLLE", []Operand{Reg{idx: 0, size: 4}, Reg{idx: 3, size: 4}}, "0f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVWLE AX,BX", "CMOVWLE", []Operand{AX, BX}, "660f4ed8", "CMOVLE"},
|
||||||
|
{"CMOVB AL,CL", "CMOVB", []Operand{AL, CL}, "0f42c8", "CMOVB"},
|
||||||
{"SETNE AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
{"SETNE AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
||||||
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
||||||
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
||||||
@@ -591,14 +605,15 @@ func TestImmediateTruncation(t *testing.T) {
|
|||||||
|
|
||||||
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
||||||
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
||||||
// letter) is not reported as encodable.
|
// letter) is not reported as encodable. The condition-name spellings the
|
||||||
|
// renderer prints (CMOVB, CMOVLE) encode with the width from the operands.
|
||||||
func TestEncodableCmovSize(t *testing.T) {
|
func TestEncodableCmovSize(t *testing.T) {
|
||||||
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOVB", "CMOV", "CMOVWXX"} {
|
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOV", "CMOVWXX"} {
|
||||||
if Encodable(m) {
|
if Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = true, want false", m)
|
t.Errorf("Encodable(%q) = true, want false", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ"} {
|
for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ", "CMOVB", "CMOVLE"} {
|
||||||
if !Encodable(m) {
|
if !Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = false, want true", m)
|
t.Errorf("Encodable(%q) = false, want true", m)
|
||||||
}
|
}
|
||||||
@@ -1193,7 +1208,7 @@ func TestBookkeepingGroundTruth(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
want := "90c3"
|
want := "c3"
|
||||||
if got := hexCompact(img.Code); got != want {
|
if got := hexCompact(img.Code); got != want {
|
||||||
t.Errorf("body %s, want %s (the bookkeeping lines contribute nothing)", got, want)
|
t.Errorf("body %s, want %s (the bookkeeping lines contribute nothing)", got, want)
|
||||||
}
|
}
|
||||||
@@ -1221,3 +1236,153 @@ func mustParse(t *testing.T, src string) *ast.File {
|
|||||||
}
|
}
|
||||||
return f
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
-18
@@ -856,37 +856,41 @@ type evexMoveSpec struct {
|
|||||||
vecOK bool // the non-memory operand may be a vector register
|
vecOK bool // the non-memory operand may be a vector register
|
||||||
xmmOnly bool // wider than XMM registers are rejected
|
xmmOnly bool // wider than XMM registers are rejected
|
||||||
nds3 bool // a three-operand register form exists (VMOVSD/VMOVSS)
|
nds3 bool // a three-operand register form exists (VMOVSD/VMOVSS)
|
||||||
|
gprOK bool // the r/m side may be a general-purpose register (VMOVQ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexMoveTable maps an upper-case EVEX move mnemonic to its encoding.
|
// evexMoveTable maps an upper-case EVEX move mnemonic to its encoding.
|
||||||
var evexMoveTable = map[string]evexMoveSpec{
|
var evexMoveTable = map[string]evexMoveSpec{
|
||||||
// EVEX.128/256/512.F3.0F.W0, unaligned integer move.
|
// EVEX.128/256/512.F3.0F.W0, unaligned integer move.
|
||||||
"VMOVDQU32": {1, 2, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false},
|
"VMOVDQU32": {1, 2, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F3.0F.W1, unaligned qword move.
|
// EVEX.128/256/512.F3.0F.W1, unaligned qword move.
|
||||||
"VMOVDQU64": {1, 2, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false},
|
"VMOVDQU64": {1, 2, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F2.0F.W0, unaligned byte move (byte/word moves use the
|
// EVEX.128/256/512.F2.0F.W0, unaligned byte move (byte/word moves use the
|
||||||
// F2 prefix, dword/qword moves F3; the element size only changes the tuple
|
// F2 prefix, dword/qword moves F3; the element size only changes the tuple
|
||||||
// semantics).
|
// semantics).
|
||||||
"VMOVDQU8": {1, 3, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false},
|
"VMOVDQU8": {1, 3, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.F2.0F.W1, unaligned word move (shares the qword
|
// EVEX.128/256/512.F2.0F.W1, unaligned word move (shares the qword
|
||||||
// encoding).
|
// encoding).
|
||||||
"VMOVDQU16": {1, 3, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false},
|
"VMOVDQU16": {1, 3, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.66.0F.W1, unaligned packed double move.
|
// EVEX.128/256/512.66.0F.W1, unaligned packed double move.
|
||||||
"VMOVUPD": {1, 1, 0x10, 0x11, 1, [3]int{16, 32, 64}, true, false, false},
|
"VMOVUPD": {1, 1, 0x10, 0x11, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512, aligned packed moves.
|
// EVEX.128/256/512, aligned packed moves.
|
||||||
"VMOVAPS": {1, 0, 0x28, 0x29, 0, [3]int{16, 32, 64}, true, false, 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},
|
"VMOVAPD": {1, 1, 0x28, 0x29, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128/256/512.66.0F, aligned integer moves.
|
// EVEX.128/256/512.66.0F, aligned integer moves.
|
||||||
"VMOVDQA32": {1, 1, 0x6F, 0x7F, 0, [3]int{16, 32, 64}, true, false, 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},
|
"VMOVDQA64": {1, 1, 0x6F, 0x7F, 1, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
// EVEX.128.F3.0F.W0, scalar single move, memory operands (the
|
// EVEX.128.F3.0F.W0, scalar single move, memory operands (the
|
||||||
// three-operand register form is not supported).
|
// three-operand register form is not supported).
|
||||||
"VMOVSS": {1, 2, 0x10, 0x11, 0, [3]int{4, 4, 4}, false, true, 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
|
// EVEX.128.F2.0F.W1, scalar double move: memory operands and the
|
||||||
// three-operand register form (VMOVSD dst, src1, src2).
|
// three-operand register form (VMOVSD dst, src1, src2).
|
||||||
"VMOVSD": {1, 3, 0x10, 0x11, 1, [3]int{8, 8, 8}, false, true, true},
|
"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.
|
// EVEX.128/256/512.0F.W0, unaligned packed single move.
|
||||||
"VMOVUPS": {1, 0, 0x10, 0x11, 0, [3]int{16, 32, 64}, true, false, false},
|
"VMOVUPS": {1, 0, 0x10, 0x11, 0, [3]int{16, 32, 64}, true, false, false, false},
|
||||||
|
// EVEX.128.66.0F.W1, the 64-bit GPR/memory ↔ XMM move (VMOVQ RSP, X20
|
||||||
|
// and friends, the EVEX spelling the high registers demand).
|
||||||
|
"VMOVQ": {1, 1, 0x6E, 0x7E, 1, [3]int{8, 8, 8}, true, true, false, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// isEvex reports whether the mnemonic has an EVEX encoding we handle.
|
// isEvex reports whether the mnemonic has an EVEX encoding we handle.
|
||||||
@@ -900,6 +904,9 @@ func isEvex(mnemUpper string) bool {
|
|||||||
if _, ok := evexMoveTable[mnemUpper]; ok {
|
if _, ok := evexMoveTable[mnemUpper]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if _, ok := evexHptrTable[mnemUpper]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return isEvexQuad(mnemUpper)
|
return isEvexQuad(mnemUpper)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,7 +918,13 @@ func evexRequired(upper string, ops []Operand) bool {
|
|||||||
_, inVex := vexTable[upper]
|
_, inVex := vexTable[upper]
|
||||||
_, inVexMove := vexMoveTable[upper]
|
_, inVexMove := vexMoveTable[upper]
|
||||||
if !inVex && !inVexMove {
|
if !inVex && !inVexMove {
|
||||||
return true // EVEX-only mnemonic
|
// 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
|
// The byte-quad shifts have VEX register forms but EVEX-only memory
|
||||||
// forms: a memory count source forces the EVEX encoding.
|
// forms: a memory count source forces the EVEX encoding.
|
||||||
@@ -1018,6 +1031,8 @@ var evexRound = map[string]bool{
|
|||||||
"VCVTTSD2USIL": true, "VCVTTSD2USIQ": true, "VCVTTSS2USIL": true, "VCVTTSS2USIQ": true,
|
"VCVTTSD2USIL": true, "VCVTTSD2USIQ": true, "VCVTTSS2USIL": true, "VCVTTSS2USIQ": true,
|
||||||
"VCVTSI2SDQ": true, "VCVTSI2SSL": true, "VCVTSI2SSQ": true,
|
"VCVTSI2SDQ": true, "VCVTSI2SSL": true, "VCVTSI2SSQ": true,
|
||||||
"VCVTUSI2SDQ": true, "VCVTUSI2SSL": true, "VCVTUSI2SSQ": true,
|
"VCVTUSI2SDQ": true, "VCVTUSI2SSL": true, "VCVTUSI2SSQ": true,
|
||||||
|
// The scalar compares suppress exceptions on their LIG encoding.
|
||||||
|
"VCMPSD": true, "VCMPSS": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// evexBcstN maps an instruction accepting .BCST to the broadcast element
|
// evexBcstN maps an instruction accepting .BCST to the broadcast element
|
||||||
@@ -1082,6 +1097,14 @@ func (e *enc) encodeEvex(mnemUpper string, ops []Operand, sfx evexSuffix) error
|
|||||||
return e.encodeEvexRM(spec, ops, 0, sfx)
|
return e.encodeEvexRM(spec, ops, 0, sfx)
|
||||||
}
|
}
|
||||||
spec, inTable := evexTable[mnemUpper]
|
spec, inTable := evexTable[mnemUpper]
|
||||||
|
// A high/low half move that lives in the hptr table alone (the packed
|
||||||
|
// double twins) reaches the same inTable block below, which completes
|
||||||
|
// its spec from the hptr entry.
|
||||||
|
if !inTable {
|
||||||
|
if _, ok := evexHptrTable[mnemUpper]; ok {
|
||||||
|
inTable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
if q, ok := evexQuadTable[mnemUpper]; ok {
|
if q, ok := evexQuadTable[mnemUpper]; ok {
|
||||||
// The quad-register family carries no rounding, SAE or broadcast;
|
// The quad-register family carries no rounding, SAE or broadcast;
|
||||||
// only masking and zeroing apply.
|
// only masking and zeroing apply.
|
||||||
@@ -1446,6 +1469,19 @@ func (e *enc) encodeEvexExtractGPR(spec evexSpec, ops []Operand, mask int, sfx e
|
|||||||
// assembler. The scalar moves also carry a three-operand register form
|
// assembler. The scalar moves also carry a three-operand register form
|
||||||
// (VMOVSD dst, src1, src2: the load opcode with vvvv = src1), which ms.nds3
|
// (VMOVSD dst, src1, src2: the load opcode with vvvv = src1), which ms.nds3
|
||||||
// opens.
|
// opens.
|
||||||
|
// validEvexMoveOther reports whether the non-vector side of an EVEX move may
|
||||||
|
// take the operand: memory always, a general-purpose register when gprOK.
|
||||||
|
func validEvexMoveOther(ms evexMoveSpec, op Operand) bool {
|
||||||
|
if memOperand(op) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !ms.gprOK {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r, ok := op.(Reg)
|
||||||
|
return ok && !r.isVec() && !r.mask && r.ctl == 0 && !r.mmx && !r.fp
|
||||||
|
}
|
||||||
|
|
||||||
func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask int, sfx evexSuffix) error {
|
||||||
if len(ops) == 3 {
|
if len(ops) == 3 {
|
||||||
if !ms.nds3 {
|
if !ms.nds3 {
|
||||||
@@ -1453,7 +1489,7 @@ func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask i
|
|||||||
}
|
}
|
||||||
// The masked scalar register form keeps the Go assembler's own
|
// The masked scalar register form keeps the Go assembler's own
|
||||||
// layout: the store opcode with reg = op0, vvvv = op1 and the
|
// layout: the store opcode with reg = op0, vvvv = op1 and the
|
||||||
// destination in r/m (op2) — the bytes go tool asm emits, not
|
// destination in r/m (op2), the bytes go tool asm emits, not
|
||||||
// the manual's NDS reading.
|
// the manual's NDS reading.
|
||||||
src, src1, dst := ops[0], ops[1], ops[2]
|
src, src1, dst := ops[0], ops[1], ops[2]
|
||||||
reg, ok := src.(Reg)
|
reg, ok := src.(Reg)
|
||||||
@@ -1494,12 +1530,12 @@ func (e *enc) encodeEvexMove(mnem string, ms evexMoveSpec, ops []Operand, mask i
|
|||||||
}
|
}
|
||||||
reg, rm = srcReg, dst
|
reg, rm = srcReg, dst
|
||||||
case srcIsVec:
|
case srcIsVec:
|
||||||
if !memOperand(dst) {
|
if !validEvexMoveOther(ms, dst) {
|
||||||
return fmt.Errorf("%s: invalid destination operand", mnem)
|
return fmt.Errorf("%s: invalid destination operand", mnem)
|
||||||
}
|
}
|
||||||
reg, rm = srcReg, dst
|
reg, rm = srcReg, dst
|
||||||
case dstIsVec:
|
case dstIsVec:
|
||||||
if !memOperand(src) {
|
if !validEvexMoveOther(ms, src) {
|
||||||
return fmt.Errorf("%s: invalid source operand", mnem)
|
return fmt.Errorf("%s: invalid source operand", mnem)
|
||||||
}
|
}
|
||||||
op = ms.load
|
op = ms.load
|
||||||
@@ -1890,6 +1926,15 @@ var evexHptrTable = map[string]evexHptrSpec{
|
|||||||
"VMOVLHPS": {
|
"VMOVLHPS": {
|
||||||
insert: evexSpec{mapSel: 1, opcode: 0x16, w: 0, pp: 0, opdigit: -1, form: vexNDS3, n: [3]int{8, 0, 0}},
|
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.
|
// encodeEvexPrefGather encodes a gather/scatter prefetch hint: OP K, vsib.
|
||||||
@@ -1954,7 +1999,7 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
if !ok || !maskReg.isVec() {
|
if !ok || !maskReg.isVec() {
|
||||||
return fmt.Errorf("%s: mask must be a vector register", upper)
|
return fmt.Errorf("%s: mask must be a vector register", upper)
|
||||||
}
|
}
|
||||||
vsib, _, err := vsibLen(rest[1], upper)
|
vsib, idxLen, err := vsibLen(rest[1], upper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1962,12 +2007,16 @@ func (e *enc) encodeGather(upper string, gs gatherSpec, ops []Operand, sfx evexS
|
|||||||
if !ok || !dst.isVec() {
|
if !ok || !dst.isVec() {
|
||||||
return fmt.Errorf("%s: destination must be a vector register", upper)
|
return fmt.Errorf("%s: destination must be a vector register", upper)
|
||||||
}
|
}
|
||||||
|
// The L bit is the wider of the data register and the VSIB index
|
||||||
|
// lengths (a YMM index under an XMM destination selects 256-bit, the
|
||||||
|
// bytes go tool asm emits).
|
||||||
|
ll := max(idxLen, dst.vecLenBit())
|
||||||
spec := vexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1}
|
spec := vexSpec{mapSel: 2, opcode: gs.opcode, w: gs.w, pp: 1, opdigit: -1}
|
||||||
rBit := 0
|
rBit := 0
|
||||||
if dst.idx >= 8 {
|
if dst.idx >= 8 {
|
||||||
rBit = 1
|
rBit = 1
|
||||||
}
|
}
|
||||||
return e.emitVexFields(spec, dst.vecLenBit(), dst.idx&7, rBit, 15-maskReg.idx, vsib)
|
return e.emitVexFields(spec, ll, dst.idx&7, rBit, 15-maskReg.idx, vsib)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeScatter encodes a scatter (EVEX only): OP src, K, vsib, reg = src,
|
// encodeScatter encodes a scatter (EVEX only): OP src, K, vsib, reg = src,
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// The extended-instruction registry: the lookup over and above the generated
|
||||||
|
// architecture tables. The generated tables (arch/*_gen.go) list the
|
||||||
|
// mnemonics the Go toolchain knows; the extension layer carries the
|
||||||
|
// instructions it does not, and this file indexes them per architecture so
|
||||||
|
// the assembler and the linter can consult the layer without touching the
|
||||||
|
// generated lists or the main encoders. A later hook wires
|
||||||
|
// ExtensionEncodable into the Encodable mirror and EncodeExtension into the
|
||||||
|
// per-architecture assembly paths; nothing existing changes until then.
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// extensionIndex is the per-architecture index of the extension layer, keyed
|
||||||
|
// by upper-case mnemonic. One mnemonic registers several forms (the SVE ADD
|
||||||
|
// carries unpredicated, predicated and immediate shapes), so the value is the
|
||||||
|
// full candidate list in table order.
|
||||||
|
type extensionIndex struct {
|
||||||
|
byName map[string][]arch.ExtInstr
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionIndexes builds one index per known architecture. Architectures
|
||||||
|
// whose extension layer is not built yet get an empty index, which keeps the
|
||||||
|
// queries answering false rather than failing on a missing entry.
|
||||||
|
var extensionIndexes = buildExtensionIndexes()
|
||||||
|
|
||||||
|
func buildExtensionIndexes() map[arch.Arch]*extensionIndex {
|
||||||
|
m := make(map[arch.Arch]*extensionIndex)
|
||||||
|
for _, a := range []arch.Arch{arch.AMD64, arch.ARM64, arch.RISCV, arch.LOONG64} {
|
||||||
|
idx := &extensionIndex{byName: make(map[string][]arch.ExtInstr)}
|
||||||
|
for _, in := range arch.Extensions(a) {
|
||||||
|
key := strings.ToUpper(in.Name)
|
||||||
|
idx.byName[key] = append(idx.byName[key], in)
|
||||||
|
}
|
||||||
|
m[a] = idx
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupExtension returns the extended instructions registered for the
|
||||||
|
// mnemonic on a, outside the generated architecture table. It reports false
|
||||||
|
// when a carries no extended layer or the mnemonic is not in it; a mnemonic
|
||||||
|
// the base table knows is not thereby covered, the layers stay independent.
|
||||||
|
func LookupExtension(a arch.Arch, mnemonic string) ([]arch.ExtInstr, bool) {
|
||||||
|
idx, ok := extensionIndexes[a]
|
||||||
|
if !ok || idx == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
cands, ok := idx.byName[strings.ToUpper(mnemonic)]
|
||||||
|
return cands, ok && len(cands) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionNames returns the mnemonics the extension layer of a registers,
|
||||||
|
// in table order, without duplicates.
|
||||||
|
func ExtensionNames(a arch.Arch) []string {
|
||||||
|
var names []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, in := range arch.Extensions(a) {
|
||||||
|
key := strings.ToUpper(in.Name)
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
names = append(names, in.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtension encodes one extended instruction on a: it resolves the
|
||||||
|
// mnemonic through the extension registry, picks the registered form whose
|
||||||
|
// arity matches the operands and encodes against it. The first form that
|
||||||
|
// encodes wins. When every matching form rejects the operands, the error
|
||||||
|
// comes from the form whose operand kinds the list points at (the one with
|
||||||
|
// the most matching positions), so a mis-spelled predicate qualifier is
|
||||||
|
// diagnosed as one, not as the unpredicated form's register complaint.
|
||||||
|
func EncodeExtension(a arch.Arch, mnemonic string, ops ...arch.ExtOperand) ([]byte, error) {
|
||||||
|
cands, ok := LookupExtension(a, mnemonic)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s registers no extended instruction %q", a, mnemonic)
|
||||||
|
}
|
||||||
|
var bestErr error
|
||||||
|
var bestScore int
|
||||||
|
var tried int
|
||||||
|
for _, in := range cands {
|
||||||
|
if in.Form.Arity() != len(ops) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tried++
|
||||||
|
b, err := in.Encode(ops)
|
||||||
|
if err == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
if score := kindScore(in.Form, ops); bestErr == nil || score > bestScore {
|
||||||
|
bestErr, bestScore = err, score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tried == 0 {
|
||||||
|
return nil, fmt.Errorf("%s: extended %q takes %s, got %d operands",
|
||||||
|
a, mnemonic, extensionAritySummary(cands), len(ops))
|
||||||
|
}
|
||||||
|
return nil, bestErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// kindScore counts the positions whose operand kind matches what the form
|
||||||
|
// wants, the tie-break that picks the most specific rejection.
|
||||||
|
func kindScore(form arch.ExtForm, ops []arch.ExtOperand) int {
|
||||||
|
kinds := form.Kinds()
|
||||||
|
score := 0
|
||||||
|
for i, op := range ops {
|
||||||
|
if i < len(kinds) && op.Kind == kinds[i] {
|
||||||
|
score++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionEncodable reports whether the extension layer of a encodes the
|
||||||
|
// mnemonic with these operands. It mirrors asm.Encodable for the extension
|
||||||
|
// layer: the predicate the linter consults once the hook wires it in.
|
||||||
|
func ExtensionEncodable(a arch.Arch, mnemonic string, ops ...arch.ExtOperand) bool {
|
||||||
|
_, err := EncodeExtension(a, mnemonic, ops...)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionAritySummary describes the operand counts the candidate forms
|
||||||
|
// take, "2 or 3" style, for the arity error.
|
||||||
|
func extensionAritySummary(cands []arch.ExtInstr) string {
|
||||||
|
counts := make([]int, 0, len(cands))
|
||||||
|
seen := make(map[int]bool)
|
||||||
|
for _, in := range cands {
|
||||||
|
n := in.Form.Arity()
|
||||||
|
if !seen[n] {
|
||||||
|
seen[n] = true
|
||||||
|
counts = append(counts, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(counts)
|
||||||
|
var b strings.Builder
|
||||||
|
for i, n := range counts {
|
||||||
|
if i > 0 {
|
||||||
|
if i == len(counts)-1 {
|
||||||
|
b.WriteString(" or ")
|
||||||
|
} else {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%d", n)
|
||||||
|
}
|
||||||
|
b.WriteString(" operands")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAmd64ExtensionRegistry checks the mnemonic lookup for the amd64 layer:
|
||||||
|
// one mnemonic across several vector lengths or W bits resolves to every
|
||||||
|
// entry, the lookup is case-insensitive, and the counts match the registered
|
||||||
|
// families.
|
||||||
|
func TestAmd64ExtensionRegistry(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
mnem string
|
||||||
|
forms int
|
||||||
|
}{
|
||||||
|
{"VCVTNE2PS2BF16", 3},
|
||||||
|
{"VCVTNEPS2BF16", 3},
|
||||||
|
{"VDPBF16PS", 3},
|
||||||
|
{"VP2INTERSECTD", 3},
|
||||||
|
{"VP2INTERSECTQ", 3},
|
||||||
|
{"VMOVSH", 3},
|
||||||
|
{"VMOVW", 4},
|
||||||
|
{"VADDSH", 1},
|
||||||
|
{"VSQRTSH", 1},
|
||||||
|
{"VCOMISH", 1},
|
||||||
|
{"VCVTSH2SS", 1},
|
||||||
|
{"VCVTSI2SH", 2},
|
||||||
|
{"VCVTSH2SI", 2},
|
||||||
|
{"VADDPH", 3},
|
||||||
|
{"VSQRTPH", 3},
|
||||||
|
{"VSCALEFSH", 1},
|
||||||
|
{"VGETEXPSH", 1},
|
||||||
|
{"VCMPSH", 1},
|
||||||
|
{"VGETMANTSH", 1},
|
||||||
|
{"VREDUCESH", 1},
|
||||||
|
{"VRNDSCALESH", 1},
|
||||||
|
{"VCVTPH2W", 3},
|
||||||
|
{"VCVTPH2UW", 3},
|
||||||
|
{"VCVTW2PH", 3},
|
||||||
|
{"VCVTUW2PH", 3},
|
||||||
|
{"VCVTPH2DQ", 3},
|
||||||
|
{"VCVTPH2UDQ", 3},
|
||||||
|
{"VCVTDQ2PH", 3},
|
||||||
|
{"VCVTUDQ2PH", 3},
|
||||||
|
{"VCVTPH2QQ", 3},
|
||||||
|
{"VCVTPH2UQQ", 3},
|
||||||
|
{"VCVTQQ2PH", 3},
|
||||||
|
{"VCVTUQQ2PH", 3},
|
||||||
|
{"VCVTPH2PD", 3},
|
||||||
|
{"VCVTPD2PH", 3},
|
||||||
|
{"VRNDSCALEPH", 3},
|
||||||
|
{"VREDUCEPH", 3},
|
||||||
|
{"VGETMANTPH", 3},
|
||||||
|
{"VFMADD132PH", 3},
|
||||||
|
{"VFMADD213PH", 3},
|
||||||
|
{"VFMADD231PH", 3},
|
||||||
|
{"VFMSUB132PH", 3},
|
||||||
|
{"VFMSUB213PH", 3},
|
||||||
|
{"VFMSUB231PH", 3},
|
||||||
|
{"VFMADDSUB132PH", 3},
|
||||||
|
{"VFMADDSUB213PH", 3},
|
||||||
|
{"VFMADDSUB231PH", 3},
|
||||||
|
{"VFMSUBADD132PH", 3},
|
||||||
|
{"VFMSUBADD213PH", 3},
|
||||||
|
{"VFMSUBADD231PH", 3},
|
||||||
|
{"VFMADD132SH", 1},
|
||||||
|
{"VFMADD213SH", 1},
|
||||||
|
{"VFMADD231SH", 1},
|
||||||
|
{"VFMSUB132SH", 1},
|
||||||
|
{"VFMSUB213SH", 1},
|
||||||
|
{"VFMSUB231SH", 1},
|
||||||
|
{"VFMULCPH", 3},
|
||||||
|
{"VFCMULCPH", 3},
|
||||||
|
{"VFMULCSH", 1},
|
||||||
|
{"VFCMULCSH", 1},
|
||||||
|
{"VFMADDCPH", 3},
|
||||||
|
{"VFCMADDCPH", 3},
|
||||||
|
{"VFMADDCSH", 1},
|
||||||
|
{"VFCMADDCSH", 1},
|
||||||
|
{"VMINMAXPH", 3},
|
||||||
|
{"VMINMAXSH", 1},
|
||||||
|
{"VPDPWSUD", 2},
|
||||||
|
{"VPDPWSUDS", 2},
|
||||||
|
{"VPDPWUSD", 2},
|
||||||
|
{"VPDPWUSDS", 2},
|
||||||
|
} {
|
||||||
|
cands, ok := LookupExtension(arch.AMD64, tt.mnem)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("LookupExtension(AMD64, %s) found nothing", tt.mnem)
|
||||||
|
}
|
||||||
|
if len(cands) != tt.forms {
|
||||||
|
t.Errorf("%s registers %d forms, want %d", tt.mnem, len(cands), tt.forms)
|
||||||
|
}
|
||||||
|
lower, ok := LookupExtension(arch.AMD64, strings.ToLower(tt.mnem))
|
||||||
|
if !ok || len(lower) != tt.forms {
|
||||||
|
t.Errorf("the %s lookup is not case-insensitive", tt.mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(arch.AMD64); len(got) != 191 {
|
||||||
|
t.Errorf("the amd64 layer registers %d instructions, want 191", len(got))
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.AMD64, "NOSUCHINSTR"); ok {
|
||||||
|
t.Error("a non-extended mnemonic resolved")
|
||||||
|
}
|
||||||
|
// VPOPCNTD and VPOPCNTQ are toolchain instructions today: they stay in
|
||||||
|
// the generated table and out of the extension layer.
|
||||||
|
if _, ok := LookupExtension(arch.AMD64, "VPOPCNTD"); ok {
|
||||||
|
t.Error("VPOPCNTD is an extension, want it in the generated table alone")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAmd64ExtensionAboveGeneratedTable pins the layering twice over: no
|
||||||
|
// registered mnemonic sits in the generated amd64 table, and the encoder
|
||||||
|
// mirror asm.Encodable answers false for every one of them, so the layer
|
||||||
|
// stays out of the main encoders by test and not by promise.
|
||||||
|
func TestAmd64ExtensionAboveGeneratedTable(t *testing.T) {
|
||||||
|
for _, mnem := range ExtensionNames(arch.AMD64) {
|
||||||
|
if _, found := arch.ForArch(arch.AMD64).Lookup(mnem); found {
|
||||||
|
t.Errorf("%s leaked into the generated amd64 table", mnem)
|
||||||
|
}
|
||||||
|
if Encodable(mnem) {
|
||||||
|
t.Errorf("%s is encodable through the main encoder, the layer is not sealed", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionAmd64 encodes through the registry and pins the same
|
||||||
|
// golden words the arch table tests pin, proving the registry resolves to the
|
||||||
|
// right encoding.
|
||||||
|
func TestEncodeExtensionAmd64(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []arch.ExtOperand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"bf16 convert", "VCVTNE2PS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtZmm(6)},
|
||||||
|
"62f2574872f4"},
|
||||||
|
{"bf16 narrow convert", "VCVTNEPS2BF16",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtYmm(6)},
|
||||||
|
"62f27e4872f5"},
|
||||||
|
{"dot product", "VDPBF16PS",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtXmm(6)},
|
||||||
|
"62f2560852f4"},
|
||||||
|
{"intersect into a mask", "VP2INTERSECTD",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(2), arch.ExtYmm(1), arch.ExtMask(2)},
|
||||||
|
"62f26f2868d1"},
|
||||||
|
{"scalar fp16 add, high registers", "VADDSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
||||||
|
"6205160058f4"},
|
||||||
|
{"scalar compare", "VCOMISH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(30)},
|
||||||
|
"62057c082ff5"},
|
||||||
|
{"integer into a scalar fp16", "VCVTSI2SH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtGpr64(12), arch.ExtXmm(30)},
|
||||||
|
"624596002af4"},
|
||||||
|
{"scalar fp16 into an integer", "VCVTSH2SI",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(30), arch.ExtGpr32(2)},
|
||||||
|
"62957e082dd6"},
|
||||||
|
{"word move into an xmm", "VMOVW",
|
||||||
|
[]arch.ExtOperand{arch.ExtGpr64(12), arch.ExtXmm(30)},
|
||||||
|
"62457d086ef4"},
|
||||||
|
{"scalar compare into a mask", "VCMPSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtMask(5)},
|
||||||
|
"62931600c2ec7b"},
|
||||||
|
{"mantissa extract with a control byte", "VGETMANTSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x0b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
||||||
|
"6203140027f40b"},
|
||||||
|
{"packed fp16 add under embedded rounding", "VADDPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f5547858f4"},
|
||||||
|
{"scalar fp16 minimum with exceptions suppressed", "VMINSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundSAE)},
|
||||||
|
"62f556185df4"},
|
||||||
|
{"fp16 to signed words", "VCVTPH2W",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f57d487df5"},
|
||||||
|
{"dwords to fp16 over a broadcast source", "VCVTDQ2PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtBroadcast(9, 0), arch.ExtYmm(30)},
|
||||||
|
"62457c585b31"},
|
||||||
|
{"fp16 to signed qwords, rounded", "VCVTPH2QQ",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f57d787bf5"},
|
||||||
|
{"fp16 scalar out of memory into an integer", "VCVTSH2SI",
|
||||||
|
[]arch.ExtOperand{arch.ExtMemory(9, 0), arch.ExtGpr64(12)},
|
||||||
|
"6255fe082d21"},
|
||||||
|
{"fp16 to double-precision, widened", "VCVTPH2PD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f57c485af5"},
|
||||||
|
{"packed fp16 rounding to fraction bits", "VRNDSCALEPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtZmm(5), arch.ExtZmm(6)},
|
||||||
|
"62f37c4808f57b"},
|
||||||
|
{"packed fused multiply-add, high registers", "VFMADD132PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"6206154098f4"},
|
||||||
|
{"fused multiply-add out of a broadcast source", "VFMADD231PH",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(5), arch.ExtBroadcast(1, 0), arch.ExtYmm(6)},
|
||||||
|
"62f65538b831"},
|
||||||
|
{"scalar multiply-subtract under embedded rounding", "VFMSUB231SH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundTruncate)},
|
||||||
|
"62f65578bbf4"},
|
||||||
|
{"complex multiply, conjugating the first source", "VFCMULCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"62061740d6f4"},
|
||||||
|
{"complex multiply-add, conjugating the second source", "VFMADDCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
||||||
|
"6206164056f4"},
|
||||||
|
{"complex multiply-add, conjugated first source, rounded", "VFCMADDCPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundNearest)},
|
||||||
|
"62f6571856f4"},
|
||||||
|
{"scalar complex multiply-add out of memory", "VFMADDCSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtMemory(9, 0), arch.ExtXmm(30)},
|
||||||
|
"624616005731"},
|
||||||
|
{"minimum or maximum under a control byte", "VMINMAXPH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtZmm(29), arch.ExtMemory(9, 0), arch.ExtZmm(30)},
|
||||||
|
"62431440523188"},
|
||||||
|
{"scalar minimum or maximum out of memory", "VMINMAXSH",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtXmm(28), arch.ExtMemory(9, 0), arch.ExtXmm(29)},
|
||||||
|
"62431c00532988"},
|
||||||
|
{"vnni dot product through the VEX word", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtXmm(1), arch.ExtXmm(3)},
|
||||||
|
"c4e26ad2d9"},
|
||||||
|
{"saturating dot product, high registers", "VPDPWUSDS",
|
||||||
|
[]arch.ExtOperand{arch.ExtYmm(10), arch.ExtYmm(15), arch.ExtYmm(8)},
|
||||||
|
"c4422dd3c7"},
|
||||||
|
{"dot product out of memory", "VPDPWSUD",
|
||||||
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtMemory(1, 127), arch.ExtXmm(1)},
|
||||||
|
"c4e26ad2497f"},
|
||||||
|
} {
|
||||||
|
got, err := EncodeExtension(arch.AMD64, tt.mnem, tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: encode: %v", tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hex.EncodeToString(got) != tt.want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionAmd64Errors checks the registry's diagnostics on the
|
||||||
|
// amd64 side: a wrong arity names the form's count and a mis-classed operand
|
||||||
|
// surfaces the entry's own message.
|
||||||
|
func TestEncodeExtensionAmd64Errors(t *testing.T) {
|
||||||
|
if _, err := EncodeExtension(arch.AMD64, "VP2INTERSECTD", arch.ExtZmm(1)); err == nil {
|
||||||
|
t.Error("one operand encoded, want an arity error")
|
||||||
|
} else if !strings.Contains(err.Error(), "3 operands") {
|
||||||
|
t.Errorf("arity error %q does not name the count", err)
|
||||||
|
}
|
||||||
|
_, err := EncodeExtension(arch.AMD64, "VCVTNEPS2BF16", arch.ExtZmm(1), arch.ExtZmm(2))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("a ZMM destination encoded on the narrow convert, want an error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "YMM register") {
|
||||||
|
t.Errorf("error %q does not name the YMM destination", err)
|
||||||
|
}
|
||||||
|
if _, err := EncodeExtension(arch.AMD64, "VCVTNE2PS2BF16"); err == nil ||
|
||||||
|
!strings.Contains(err.Error(), "takes 3 operands, got 0") {
|
||||||
|
t.Errorf("zero-operand error = %v, want the operand-count diagnostic", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,534 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExtensionRegistryARM64 checks the mnemonic lookup over and above the
|
||||||
|
// generated arm64 table: one mnemonic, several forms, case-insensitive, and
|
||||||
|
// nothing offered for a spelling the layer does not carry.
|
||||||
|
func TestExtensionRegistryARM64(t *testing.T) {
|
||||||
|
add, ok := LookupExtension(arch.ARM64, "ADD")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("LookupExtension(ARM64, ADD) found nothing")
|
||||||
|
}
|
||||||
|
var forms []arch.ExtForm
|
||||||
|
for _, in := range add {
|
||||||
|
if in.Name != "ADD" {
|
||||||
|
t.Errorf("candidate %q leaked into the ADD lookup", in.Name)
|
||||||
|
}
|
||||||
|
forms = append(forms, in.Form)
|
||||||
|
}
|
||||||
|
if len(forms) != 3 ||
|
||||||
|
forms[0] != arch.ExtFormVectors ||
|
||||||
|
forms[1] != arch.ExtFormPredicated ||
|
||||||
|
forms[2] != arch.ExtFormImmediate {
|
||||||
|
t.Errorf("ADD registers forms %v, want unpredicated, predicated and immediate", forms)
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "add"); !ok {
|
||||||
|
t.Error("the lookup is case-sensitive")
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "NOSUCHINSTR"); ok {
|
||||||
|
t.Error("a non-extended mnemonic resolved")
|
||||||
|
}
|
||||||
|
sqadd, ok := LookupExtension(arch.ARM64, "SQADD")
|
||||||
|
if !ok || len(sqadd) != 2 {
|
||||||
|
t.Errorf("SQADD registers %d forms, want the unpredicated and immediate pair", len(sqadd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionAboveGeneratedTable pins the layering: SQADD is nowhere in the
|
||||||
|
// generated arm64 table (the toolchain knows only the NEON spelling VSQADD)
|
||||||
|
// yet the extension layer carries it, while ADD sits in both layers
|
||||||
|
// independently.
|
||||||
|
func TestExtensionAboveGeneratedTable(t *testing.T) {
|
||||||
|
if _, found := arch.ForArch(arch.ARM64).Lookup("SQADD"); found {
|
||||||
|
t.Error("SQADD is in the generated table, the layering assumption broke")
|
||||||
|
}
|
||||||
|
if _, ok := LookupExtension(arch.ARM64, "SQADD"); !ok {
|
||||||
|
t.Error("SQADD is missing from the extension layer")
|
||||||
|
}
|
||||||
|
if _, found := arch.ForArch(arch.ARM64).Lookup("ADD"); !found {
|
||||||
|
t.Error("ADD vanished from the generated table")
|
||||||
|
}
|
||||||
|
if add, ok := LookupExtension(arch.ARM64, "ADD"); !ok || len(add) != 3 {
|
||||||
|
t.Errorf("ADD carries %d extension forms, want 3", len(add))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionGolden encodes through the registry and pins the same
|
||||||
|
// golden words the arch table tests pin, proving the registry resolves to the
|
||||||
|
// right encoding.
|
||||||
|
func TestEncodeExtensionGolden(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []arch.ExtOperand
|
||||||
|
want uint32
|
||||||
|
}{
|
||||||
|
{"unpredicated add", "ADD",
|
||||||
|
[]arch.ExtOperand{
|
||||||
|
arch.ExtVector(2, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
},
|
||||||
|
0x04200040},
|
||||||
|
{"predicated mul", "MUL",
|
||||||
|
[]arch.ExtOperand{
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtPredicate(2, arch.ExtQualMerging), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
},
|
||||||
|
0x04100800},
|
||||||
|
{"immediate add with derived shift", "ADD",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(32512), arch.ExtVector(0, arch.ExtArrH)},
|
||||||
|
0x2560efe0},
|
||||||
|
{"signed immediate mul", "MUL",
|
||||||
|
[]arch.ExtOperand{arch.ExtImmediate(-1), arch.ExtVector(0, arch.ExtArrB)},
|
||||||
|
0x2530dfe0},
|
||||||
|
} {
|
||||||
|
got, err := EncodeExtension(arch.ARM64, tt.mnem, tt.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: encode: %v", tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if want := hex.EncodeToString([]byte{
|
||||||
|
byte(tt.want), byte(tt.want >> 8), byte(tt.want >> 16), byte(tt.want >> 24),
|
||||||
|
}); hex.EncodeToString(got) != want {
|
||||||
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEncodeExtensionErrors checks the registry's diagnostics: a wrong arity
|
||||||
|
// names every form's count, an operand the first candidate rejects surfaces
|
||||||
|
// its own message once a later form takes over.
|
||||||
|
func TestEncodeExtensionErrors(t *testing.T) {
|
||||||
|
if _, err := EncodeExtension(arch.ARM64, "ADD", arch.ExtVector(0, arch.ExtArrB)); err == nil {
|
||||||
|
t.Error("one operand encoded, want an arity error")
|
||||||
|
} else if !strings.Contains(err.Error(), "2 or 3 operands") {
|
||||||
|
t.Errorf("arity error %q does not name the counts", err)
|
||||||
|
}
|
||||||
|
// The predicated candidate must answer for its own operands: the /Z
|
||||||
|
// qualifier is rejected with the merging message, not the unpredicated
|
||||||
|
// form's register-kind complaint.
|
||||||
|
_, err := EncodeExtension(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtPredicate(0, arch.ExtQualZeroing), arch.ExtVector(0, arch.ExtArrB))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("/Z encoded, want an error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "/M") {
|
||||||
|
t.Errorf("error %q does not name the merging qualifier", err)
|
||||||
|
}
|
||||||
|
if _, err := EncodeExtension(arch.ARM64, "NOSUCHINSTR", arch.ExtVector(0, arch.ExtArrB)); err == nil ||
|
||||||
|
!strings.Contains(err.Error(), "registers no extended instruction") {
|
||||||
|
t.Errorf("unknown mnemonic error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionEncodable checks the predicate the later Encodable hook will
|
||||||
|
// call: true exactly when the registry encodes the operand list.
|
||||||
|
func TestExtensionEncodable(t *testing.T) {
|
||||||
|
if !ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrS), arch.ExtVector(1, arch.ExtArrS), arch.ExtVector(2, arch.ExtArrS)) {
|
||||||
|
t.Error("an encodable unpredicated add reported false")
|
||||||
|
}
|
||||||
|
if !ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(1, arch.ExtArrS), arch.ExtPredicate(0, arch.ExtQualMerging), arch.ExtVector(0, arch.ExtArrS)) {
|
||||||
|
t.Error("an encodable predicated add reported false")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "ADD",
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrS), arch.ExtVector(0, arch.ExtArrB)) {
|
||||||
|
t.Error("mismatched arrangements reported encodable")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "ADD", arch.ExtVector(0, arch.ExtArrB)) {
|
||||||
|
t.Error("a one-operand add reported encodable")
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.ARM64, "NOSUCHINSTR") {
|
||||||
|
t.Error("an unregistered mnemonic reported encodable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionArchIsolation is the architecture-binding negative case: no
|
||||||
|
// architecture answers the arm64 mnemonics but arm64, and the amd64 layer
|
||||||
|
// answers nothing of the arm64 family either (its own mnemonics live in
|
||||||
|
// extension_amd64_test.go).
|
||||||
|
func TestExtensionArchIsolation(t *testing.T) {
|
||||||
|
ops := []arch.ExtOperand{
|
||||||
|
arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(0, arch.ExtArrB),
|
||||||
|
}
|
||||||
|
for _, a := range []arch.Arch{arch.RISCV, arch.LOONG64, arch.Unknown} {
|
||||||
|
if cands, ok := LookupExtension(a, "ADD"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(%s, ADD) offered %d candidates", a, len(cands))
|
||||||
|
}
|
||||||
|
if cands, ok := LookupExtension(a, "MUL"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(%s, MUL) offered %d candidates", a, len(cands))
|
||||||
|
}
|
||||||
|
if got, err := EncodeExtension(a, "ADD", ops...); err == nil {
|
||||||
|
t.Errorf("EncodeExtension(%s, ADD) encoded %x, want a refusal", a, got)
|
||||||
|
} else if !strings.Contains(err.Error(), string(a)) {
|
||||||
|
t.Errorf("EncodeExtension(%s) error %q does not name the architecture", a, err)
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(a, "ADD", ops...) {
|
||||||
|
t.Errorf("ExtensionEncodable(%s, ADD) reported true", a)
|
||||||
|
}
|
||||||
|
if names := ExtensionNames(a); len(names) != 0 {
|
||||||
|
t.Errorf("ExtensionNames(%s) = %v, want none", a, names)
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(a); len(got) != 0 {
|
||||||
|
t.Errorf("arch.Extensions(%s) carries %d instructions", a, len(got))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The amd64 layer exists but stays silent about the arm64 family.
|
||||||
|
if cands, ok := LookupExtension(arch.AMD64, "ADD"); ok || cands != nil {
|
||||||
|
t.Errorf("LookupExtension(AMD64, ADD) offered %d candidates", len(cands))
|
||||||
|
}
|
||||||
|
if got, err := EncodeExtension(arch.AMD64, "ADD", ops...); err == nil {
|
||||||
|
t.Errorf("EncodeExtension(AMD64, ADD) encoded %x, want a refusal", got)
|
||||||
|
} else if !strings.Contains(err.Error(), string(arch.AMD64)) {
|
||||||
|
t.Errorf("EncodeExtension(AMD64) error %q does not name the architecture", err)
|
||||||
|
}
|
||||||
|
if ExtensionEncodable(arch.AMD64, "ADD", ops...) {
|
||||||
|
t.Error("ExtensionEncodable(AMD64, ADD) reported true")
|
||||||
|
}
|
||||||
|
if names := ExtensionNames(arch.AMD64); len(names) == 0 {
|
||||||
|
t.Error("the amd64 layer registers no names")
|
||||||
|
}
|
||||||
|
if got := arch.Extensions(arch.AMD64); len(got) == 0 {
|
||||||
|
t.Error("arch.Extensions(AMD64) is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtensionNamesARM64 checks the completion-facing name list: every
|
||||||
|
// distinct mnemonic of the family, first-occurrence order, no duplicates.
|
||||||
|
func TestExtensionNamesARM64(t *testing.T) {
|
||||||
|
want := []string{
|
||||||
|
// The SVE integer add/subtract/multiply family.
|
||||||
|
"ADD", "SUB", "SQADD", "UQADD", "SQSUB", "UQSUB", "MUL", "SMULH", "UMULH", "SUBR",
|
||||||
|
// The SVE and SVE2.1 predicate family: the logical operations, the
|
||||||
|
// breaks, the permutations, the singles, the first-fault group and
|
||||||
|
// the while compares.
|
||||||
|
"PAND", "PANDS", "PBIC", "PBICS", "PEOR", "PEORS",
|
||||||
|
"PNAND", "PNANDS", "PNOR", "PNORS", "PORN", "PORNS", "PORR", "PORRS",
|
||||||
|
"PSEL",
|
||||||
|
"PBRKA", "PBRKAS", "PBRKB", "PBRKBS", "PBRKN", "PBRKNS",
|
||||||
|
"PBRKPA", "PBRKPAS", "PBRKPB", "PBRKPBS",
|
||||||
|
"PTRN1", "PTRN2", "PUZP1", "PUZP2", "PZIP1", "PZIP2",
|
||||||
|
"PPFALSE", "PPFIRST", "PPNEXT", "PPTEST", "PPTRUE", "PPUNPKHI", "PPUNPKLO",
|
||||||
|
"PRDFFR", "PRDFFRS", "PWRFFR", "PREV", "SETFFR",
|
||||||
|
"PWHILEGE", "PWHILEGT", "PWHILEHI", "PWHILEHS",
|
||||||
|
"PWHILELE", "PWHILELO", "PWHILELS", "PWHILELT", "PWHILERW", "PWHILEWR",
|
||||||
|
// The SVE2.1 Z-alias families, in first-occurrence order.
|
||||||
|
"ZABS",
|
||||||
|
"ZREVB",
|
||||||
|
"ZREVH",
|
||||||
|
"ZSXTB",
|
||||||
|
"ZSXTH",
|
||||||
|
"ZUXTB",
|
||||||
|
"ZUXTH",
|
||||||
|
"ZADD",
|
||||||
|
"ZAND",
|
||||||
|
"ZBIC",
|
||||||
|
"ZCLS",
|
||||||
|
"ZCLZ",
|
||||||
|
"ZCNOT",
|
||||||
|
"ZCNT",
|
||||||
|
"ZCOMPACT",
|
||||||
|
"ZDECP",
|
||||||
|
"ZDUP",
|
||||||
|
"ZEOR",
|
||||||
|
"ZEXPAND",
|
||||||
|
"ZINSR",
|
||||||
|
"ZLASTA",
|
||||||
|
"ZLASTB",
|
||||||
|
"ZMOVPRFX",
|
||||||
|
"ZREVD",
|
||||||
|
"ZREVW",
|
||||||
|
"ZSXTW",
|
||||||
|
"ZUXTW",
|
||||||
|
"ZNEG",
|
||||||
|
"ZNOT",
|
||||||
|
"ZORR",
|
||||||
|
"ZRBIT",
|
||||||
|
"ZREV",
|
||||||
|
"ZSEL",
|
||||||
|
"ZSQABS",
|
||||||
|
"ZSQNEG",
|
||||||
|
"ZSUB",
|
||||||
|
"ZSUBR",
|
||||||
|
"ZSUNPKHI",
|
||||||
|
"ZSUNPKLO",
|
||||||
|
"ZTBX",
|
||||||
|
"ZTBXQ",
|
||||||
|
"ZTRN1",
|
||||||
|
"ZTRN2",
|
||||||
|
"ZUUNPKHI",
|
||||||
|
"ZUUNPKLO",
|
||||||
|
"ZUZP1",
|
||||||
|
"ZUZP2",
|
||||||
|
"ZUZPQ1",
|
||||||
|
"ZUZPQ2",
|
||||||
|
"ZZIP1",
|
||||||
|
"ZZIP2",
|
||||||
|
"ZZIPQ1",
|
||||||
|
"ZZIPQ2",
|
||||||
|
// The stage-four families: the SVE2.1 narrowing two-to-one set, the
|
||||||
|
// pairwise forms and the quadword reductions.
|
||||||
|
"ZADDHNB",
|
||||||
|
"ZADDHNT",
|
||||||
|
"ZRADDHNB",
|
||||||
|
"ZRADDHNT",
|
||||||
|
"ZRSUBHNB",
|
||||||
|
"ZRSUBHNT",
|
||||||
|
"ZSUBHNB",
|
||||||
|
"ZSUBHNT",
|
||||||
|
"ZADDP",
|
||||||
|
"ZADDPT",
|
||||||
|
"ZADDQV",
|
||||||
|
"ZANDQV",
|
||||||
|
"ZEORQV",
|
||||||
|
"ZFADDQV",
|
||||||
|
"ZFMAXNMQV",
|
||||||
|
"ZFMAXQV",
|
||||||
|
"ZFMINNMQV",
|
||||||
|
"ZFMINQV",
|
||||||
|
"ZORQV",
|
||||||
|
"ZSMAXQV",
|
||||||
|
"ZSMINQV",
|
||||||
|
"ZUMAXQV",
|
||||||
|
"ZUMINQV",
|
||||||
|
"ZASR",
|
||||||
|
"ZASRR",
|
||||||
|
"ZLSL",
|
||||||
|
"ZLSLR",
|
||||||
|
"ZLSR",
|
||||||
|
"ZLSRR",
|
||||||
|
"ZBCAX",
|
||||||
|
"ZBDEP",
|
||||||
|
"ZBEXT",
|
||||||
|
"ZBGRP",
|
||||||
|
"ZBSL",
|
||||||
|
"ZBSL1N",
|
||||||
|
"ZBSL2N",
|
||||||
|
"ZEOR3",
|
||||||
|
"ZEORBT",
|
||||||
|
"ZEORTB",
|
||||||
|
"ZNBSL",
|
||||||
|
"ZBF1CVT",
|
||||||
|
"ZBF1CVTLT",
|
||||||
|
"ZBF2CVT",
|
||||||
|
"ZBF2CVTLT",
|
||||||
|
"ZF1CVT",
|
||||||
|
"ZF1CVTLT",
|
||||||
|
"ZF2CVT",
|
||||||
|
"ZF2CVTLT",
|
||||||
|
"ZBFADD",
|
||||||
|
"ZBFCLAMP",
|
||||||
|
"ZBFMAX",
|
||||||
|
"ZBFMAXNM",
|
||||||
|
"ZBFMIN",
|
||||||
|
"ZBFMINNM",
|
||||||
|
"ZBFMUL",
|
||||||
|
"ZBFSUB",
|
||||||
|
"ZBFCVT",
|
||||||
|
"ZBFCVTNT",
|
||||||
|
"ZBFDOT",
|
||||||
|
"ZBFMLA",
|
||||||
|
"ZBFMLALB",
|
||||||
|
"ZBFMLALT",
|
||||||
|
"ZBFMLS",
|
||||||
|
"ZBFMLSLB",
|
||||||
|
"ZBFMLSLT",
|
||||||
|
"ZBFMMLA",
|
||||||
|
"ZBFSCALE",
|
||||||
|
"ZCLASTA",
|
||||||
|
"ZCLASTB",
|
||||||
|
"ZCMPEQ",
|
||||||
|
"ZCMPGE",
|
||||||
|
"ZCMPGT",
|
||||||
|
"ZCMPHI",
|
||||||
|
"ZCMPHS",
|
||||||
|
"ZCMPNE",
|
||||||
|
"ADDPL",
|
||||||
|
"ADDVL",
|
||||||
|
"RDVL",
|
||||||
|
"ZLD2B",
|
||||||
|
"ZLD2D",
|
||||||
|
"ZLD2H",
|
||||||
|
"ZLD2Q",
|
||||||
|
"ZLD2W",
|
||||||
|
"ZLD3B",
|
||||||
|
"ZLD3D",
|
||||||
|
"ZLD3H",
|
||||||
|
"ZLD3Q",
|
||||||
|
"ZLD3W",
|
||||||
|
"ZLD4B",
|
||||||
|
"ZLD4D",
|
||||||
|
"ZLD4H",
|
||||||
|
"ZLD4Q",
|
||||||
|
"ZLD4W",
|
||||||
|
"ZST2B",
|
||||||
|
"ZST2D",
|
||||||
|
"ZST2H",
|
||||||
|
"ZST2Q",
|
||||||
|
"ZST2W",
|
||||||
|
"ZST3B",
|
||||||
|
"ZST3D",
|
||||||
|
"ZST3H",
|
||||||
|
"ZST3Q",
|
||||||
|
"ZST3W",
|
||||||
|
"ZST4B",
|
||||||
|
"ZST4D",
|
||||||
|
"ZST4H",
|
||||||
|
"ZST4Q",
|
||||||
|
"ZST4W",
|
||||||
|
// The stage-three families: the SVE2 crypto group, the predicate
|
||||||
|
// counters and loop terminators with the 32-bit while compares, and
|
||||||
|
// the reductions into a SIMD register.
|
||||||
|
"ZADCLB",
|
||||||
|
"ZADCLT",
|
||||||
|
"ZSBCLB",
|
||||||
|
"ZSBCLT",
|
||||||
|
"ZRAX1",
|
||||||
|
"ZSM4EKEY",
|
||||||
|
"ZSM4E",
|
||||||
|
"ZAESD",
|
||||||
|
"ZAESE",
|
||||||
|
"ZAESIMC",
|
||||||
|
"ZAESMC",
|
||||||
|
"CTERMEQ",
|
||||||
|
"CTERMEQW",
|
||||||
|
"CTERMNE",
|
||||||
|
"CTERMNEW",
|
||||||
|
"PCNTP",
|
||||||
|
"PFIRSTP",
|
||||||
|
"PLASTP",
|
||||||
|
"PDECP",
|
||||||
|
"PINCP",
|
||||||
|
"PSQDECP",
|
||||||
|
"PSQINCP",
|
||||||
|
"PUQDECP",
|
||||||
|
"PUQINCP",
|
||||||
|
"PUQDECPW",
|
||||||
|
"PUQINCPW",
|
||||||
|
"PSQDECPW",
|
||||||
|
"PSQINCPW",
|
||||||
|
"PWHILEGEW",
|
||||||
|
"PWHILEGTW",
|
||||||
|
"PWHILEHIW",
|
||||||
|
"PWHILEHSW",
|
||||||
|
"PWHILELEW",
|
||||||
|
"PWHILELOW",
|
||||||
|
"PWHILELSW",
|
||||||
|
"PWHILELTW",
|
||||||
|
"ZSADDVD",
|
||||||
|
"ZUADDVD",
|
||||||
|
"ZANDVB",
|
||||||
|
"ZANDVH",
|
||||||
|
"ZANDVS",
|
||||||
|
"ZANDVD",
|
||||||
|
"ZEORVB",
|
||||||
|
"ZEORVH",
|
||||||
|
"ZEORVS",
|
||||||
|
"ZEORVD",
|
||||||
|
"ZORVB",
|
||||||
|
"ZORVH",
|
||||||
|
"ZORVS",
|
||||||
|
"ZORVD",
|
||||||
|
"ZSMAXVB",
|
||||||
|
"ZSMAXVH",
|
||||||
|
"ZSMAXVS",
|
||||||
|
"ZSMAXVD",
|
||||||
|
"ZSMINVB",
|
||||||
|
"ZSMINVH",
|
||||||
|
"ZSMINVS",
|
||||||
|
"ZSMINVD",
|
||||||
|
"ZUMAXVB",
|
||||||
|
"ZUMAXVH",
|
||||||
|
"ZUMAXVS",
|
||||||
|
"ZUMAXVD",
|
||||||
|
"ZUMINVB",
|
||||||
|
"ZUMINVH",
|
||||||
|
"ZUMINVS",
|
||||||
|
"ZUMINVD",
|
||||||
|
"ZFADDVH",
|
||||||
|
"ZFADDVS",
|
||||||
|
"ZFADDVD",
|
||||||
|
"ZFMAXNMVH",
|
||||||
|
"ZFMAXNMVS",
|
||||||
|
"ZFMAXNMVD",
|
||||||
|
"ZFMAXVH",
|
||||||
|
"ZFMAXVS",
|
||||||
|
"ZFMAXVD",
|
||||||
|
"ZFMINNMVH",
|
||||||
|
"ZFMINNMVS",
|
||||||
|
"ZFMINNMVD",
|
||||||
|
"ZFMINVH",
|
||||||
|
"ZFMINVS",
|
||||||
|
"ZFMINVD",
|
||||||
|
"ZFADDAH",
|
||||||
|
"ZFADDAS",
|
||||||
|
"ZFADDAD",
|
||||||
|
// The gather loads and scatter stores: plain, sign-extended and
|
||||||
|
// first-fault loads, and the stores, in first-occurrence order.
|
||||||
|
"ZLD1B",
|
||||||
|
"ZLD1D",
|
||||||
|
"ZLD1H",
|
||||||
|
"ZLD1SB",
|
||||||
|
"ZLD1SH",
|
||||||
|
"ZLD1SW",
|
||||||
|
"ZLD1W",
|
||||||
|
"ZLDFF1B",
|
||||||
|
"ZLDFF1D",
|
||||||
|
"ZLDFF1H",
|
||||||
|
"ZLDFF1SB",
|
||||||
|
"ZLDFF1SH",
|
||||||
|
"ZLDFF1SW",
|
||||||
|
"ZLDFF1W",
|
||||||
|
"ZST1B",
|
||||||
|
"ZST1D",
|
||||||
|
"ZST1H",
|
||||||
|
"ZST1W",
|
||||||
|
// The shift-immediate classes: the narrowing and widening
|
||||||
|
// three-vector shifts and the predicated saturating left shifts,
|
||||||
|
// in first-occurrence order.
|
||||||
|
"ZSQSHRUNB",
|
||||||
|
"ZSQSHRUNT",
|
||||||
|
"ZSHRNB",
|
||||||
|
"ZSHRNT",
|
||||||
|
"ZRSHRNB",
|
||||||
|
"ZRSHRNT",
|
||||||
|
"ZSQSHRNB",
|
||||||
|
"ZSQSHRNT",
|
||||||
|
"ZSQRSHRNB",
|
||||||
|
"ZSQRSHRNT",
|
||||||
|
"ZUQSHRNB",
|
||||||
|
"ZUQSHRNT",
|
||||||
|
"ZUQRSHRNB",
|
||||||
|
"ZUQRSHRNT",
|
||||||
|
"ZSSHLLB",
|
||||||
|
"ZSSHLLT",
|
||||||
|
"ZUSHLLB",
|
||||||
|
"ZUSHLLT",
|
||||||
|
"ZSQSHL",
|
||||||
|
"ZSQSHLU",
|
||||||
|
"ZUQSHL",
|
||||||
|
"ZSRI",
|
||||||
|
"ZSSRA",
|
||||||
|
"ZUSRA",
|
||||||
|
"ZSRSRA",
|
||||||
|
"ZURSRA",
|
||||||
|
"ZASRD",
|
||||||
|
"ZXAR",
|
||||||
|
}
|
||||||
|
got := ExtensionNames(arch.ARM64)
|
||||||
|
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||||
|
t.Errorf("ExtensionNames(ARM64) = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if n := len(arch.Extensions(arch.ARM64)); n != 537 {
|
||||||
|
t.Errorf("the family registers %d instructions, want 537", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flagListShapes are the TEXT shapes the flags operand decides: joined
|
||||||
|
// names, the legacy numeric spellings, the arithmetic and immediate forms,
|
||||||
|
// each with a frame size beside it. A flag the operand carries suppresses
|
||||||
|
// the stack-split guard; the toolchain reads one integer from the operand,
|
||||||
|
// and after the front end folds it the bytes must not tell the difference.
|
||||||
|
// The bodies are leaf-shaped on purpose: the frame engine's open divergences
|
||||||
|
// (the NOFRAME prologue for a non-leaf, the big-frame guard's shape) sit
|
||||||
|
// outside the flags operand and are somebody else's gap list.
|
||||||
|
var flagListShapes = []struct {
|
||||||
|
name string // the subtest's name
|
||||||
|
flags string // the flags operand as written
|
||||||
|
frame string // the frame operand as written
|
||||||
|
body string // the function body
|
||||||
|
}{
|
||||||
|
{"name", "NOSPLIT", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"numeric", "4", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"numericOR", "2|4", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"joinedFrame", "DUPOK|NOSPLIT", "$4096-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
{"joinedFrameless", "NOSPLIT|TOPFRAME", "$0-0", "\tRET\n"},
|
||||||
|
{"parenthesised", "(NOSPLIT|NOFRAME)", "$0-0", "\tMOVD R0, R1\n\tRET\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFlagListAssemblyPathRejectsUnknown holds the front end's rejections
|
||||||
|
// that mirror the toolchain's: an identifier outside the flag table
|
||||||
|
// ("unexpected TYPO evaluating expression") and the immediate spelling of
|
||||||
|
// the operand ("TEXT: expected integer constant"), each refused on the
|
||||||
|
// assembly path before the encoder ever sees a tree.
|
||||||
|
func TestFlagListAssemblyPathRejectsUnknown(t *testing.T) {
|
||||||
|
for _, c := range []struct{ src, want string }{
|
||||||
|
{"#include \"textflag.h\"\n\nTEXT f(SB), NOSPLIT|TYPO, $0-0\n\tRET\n",
|
||||||
|
"unexpected TYPO evaluating expression"},
|
||||||
|
{"#include \"textflag.h\"\n\nTEXT f(SB), $NOSPLIT, $0-0\n\tRET\n",
|
||||||
|
"TEXT: expected integer constant; found $NOSPLIT"},
|
||||||
|
{"#include \"textflag.h\"\n\nGLOBL g<>(SB), $8, $8\n",
|
||||||
|
"GLOBL: expected integer constant; found $8"},
|
||||||
|
} {
|
||||||
|
_, errs := parser.ParseWithOptions("f.s", c.src, parser.Options{Expand: true})
|
||||||
|
if len(errs) == 0 || !strings.Contains(errs[0].Error(), c.want) {
|
||||||
|
t.Errorf("errors = %v, want %q", errs, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFlagListByteParity assembles every flag-list shape through the fixed
|
||||||
|
// front end and holds the function's bytes equal to the installed
|
||||||
|
// toolchain's, no guard words where the flag suppresses them and none
|
||||||
|
// missing where it demands one.
|
||||||
|
func TestFlagListByteParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
out, err := exec.Command(goBin, "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("go env GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
include := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include")
|
||||||
|
|
||||||
|
for _, c := range flagListShapes {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT f(SB), " + c.flags + ", " + c.frame + "\n" + c.body
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "f.s")
|
||||||
|
if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
af, errs := parser.ParseWithOptions(file, src, parser.Options{Expand: true})
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs[0])
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(af)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("%d functions, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
|
||||||
|
objPath := filepath.Join(t.TempDir(), "oracle.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm", "-std", "-I", include,
|
||||||
|
"-p", "flaglisttest", "-o", objPath, file)
|
||||||
|
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64")
|
||||||
|
if oout, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("go tool asm: %v\n%s", err, oout)
|
||||||
|
}
|
||||||
|
blocks, ok := oracleFuncText(t, mustRead(t, objPath))["f"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("the oracle output carries no f")
|
||||||
|
}
|
||||||
|
if len(blocks) != 1 {
|
||||||
|
t.Fatalf("%d text blocks for f, want 1", len(blocks))
|
||||||
|
}
|
||||||
|
goCode := blocks[0]
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
|
||||||
|
cmpLen := min(len(goCode), len(gasmCode))
|
||||||
|
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
|
||||||
|
t.Errorf("bytes differ:\ngasm: % x\ngo: % x", gasmCode[:cmpLen], goCode[:cmpLen])
|
||||||
|
}
|
||||||
|
if len(goCode) > len(gasmCode) {
|
||||||
|
for _, b := range goCode[len(gasmCode):] {
|
||||||
|
if b != 0 {
|
||||||
|
t.Errorf("non-zero trailing bytes in the oracle output")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
kindSTEXT = 1
|
kindSTEXT = 1
|
||||||
kindSRODATA = 3
|
kindSRODATA = 3
|
||||||
|
kindSNOPTRDATA = 5
|
||||||
kindSDATA = 7
|
kindSDATA = 7
|
||||||
kindSDWARFFCN = 14
|
kindSDWARFFCN = 14
|
||||||
kindSDWARFLINES = 20
|
kindSDWARFLINES = 20
|
||||||
@@ -305,9 +306,16 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
if !d.Static {
|
if !d.Static {
|
||||||
name = pkgPath + "." + name
|
name = pkgPath + "." + name
|
||||||
}
|
}
|
||||||
|
// RODATA implies no pointers, so it wins over NOPTR: the kind is
|
||||||
|
// SRODATA either way, exactly as the toolchain chooses it. Plain
|
||||||
|
// NOPTR data is SNOPTRDATA, which the linker keeps out of the GC's
|
||||||
|
// type scan; a plain SDATA symbol would demand Go type information
|
||||||
|
// no assembly file can supply, and the link would fail.
|
||||||
typ := uint8(kindSDATA)
|
typ := uint8(kindSDATA)
|
||||||
if d.Rodata {
|
if d.Rodata {
|
||||||
typ = kindSRODATA
|
typ = kindSRODATA
|
||||||
|
} else if d.Noptr {
|
||||||
|
typ = kindSNOPTRDATA
|
||||||
}
|
}
|
||||||
flag := uint8(0)
|
flag := uint8(0)
|
||||||
if d.Dupok {
|
if d.Dupok {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ func TestReadRuntimeSymbols(t *testing.T) {
|
|||||||
// TestResolveExternalSymbols verifies end-to-end resolution of external
|
// TestResolveExternalSymbols verifies end-to-end resolution of external
|
||||||
// symbol references.
|
// symbol references.
|
||||||
func TestResolveExternalSymbols(t *testing.T) {
|
func TestResolveExternalSymbols(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("resolves through a live go list -export: skipped in -short mode")
|
||||||
|
}
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
if _, err := exec.LookPath("go"); err != nil {
|
||||||
t.Skip("go toolchain not available")
|
t.Skip("go toolchain not available")
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-1
@@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// goobjView is a minimal parsed view of a GOOBJ payload, enough to check
|
// goobjView is a minimal parsed view of a GOOBJ payload, enough to check
|
||||||
@@ -514,6 +514,145 @@ func fieldAfter(line, flag string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildLogSteps is what a substitution test needs from a `go build -x -work`
|
||||||
|
// log: the work directory, the assembler's object, the package archive and
|
||||||
|
// the link command line.
|
||||||
|
type buildLogSteps struct {
|
||||||
|
work string
|
||||||
|
asmObj string // $WORK expanded
|
||||||
|
pkgArch string // $WORK expanded
|
||||||
|
linkLine string // still carries $WORK placeholders
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBuildLog extracts the build steps from a `go build -x -work` log.
|
||||||
|
// asmFile names the assembly file whose object the test substitutes. A
|
||||||
|
// missing step is a failure, not a skip: the toolchain changed shape and the
|
||||||
|
// substitution would silently test nothing.
|
||||||
|
func parseBuildLog(t *testing.T, log []byte, asmFile string) buildLogSteps {
|
||||||
|
t.Helper()
|
||||||
|
var st buildLogSteps
|
||||||
|
for line := range strings.SplitSeq(string(log), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "WORK="):
|
||||||
|
st.work = strings.TrimPrefix(line, "WORK=")
|
||||||
|
case strings.Contains(line, "/asm ") && strings.Contains(line, asmFile) && !strings.Contains(line, "-gensymabis"):
|
||||||
|
st.asmObj = fieldAfter(line, "-o")
|
||||||
|
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
|
||||||
|
rest := strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
|
||||||
|
st.pkgArch = strings.Fields(strings.SplitN(rest, "#", 2)[0])[0]
|
||||||
|
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||||
|
st.linkLine = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if st.work == "" || st.asmObj == "" || st.pkgArch == "" || st.linkLine == "" {
|
||||||
|
t.Fatalf("could not locate the build steps (work=%q asmObj=%q pkgArch=%q link=%q):\n%s",
|
||||||
|
st.work, st.asmObj, st.pkgArch, st.linkLine, log)
|
||||||
|
}
|
||||||
|
st.asmObj = strings.ReplaceAll(st.asmObj, "$WORK", st.work)
|
||||||
|
st.pkgArch = strings.ReplaceAll(st.pkgArch, "$WORK", st.work)
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
// substituteAndRelink swaps the gasm object into the package archive the
|
||||||
|
// baseline build produced and re-runs the captured link line against the
|
||||||
|
// rebuilt archive, writing the binary to outBin (the -x log's link step
|
||||||
|
// always targets the action graph's internal a.out, which the helper
|
||||||
|
// redirects; the copy to the -o target is a separate build action the helper
|
||||||
|
// does not need). The archive handed to the linker is proven to carry the
|
||||||
|
// gasm object byte for byte, so a build-layout change that skipped the
|
||||||
|
// substitution fails here instead of passing vacuously.
|
||||||
|
func substituteAndRelink(t *testing.T, goBin, dir string, st buildLogSteps, outBin string, gasmObj []byte, extraEnv ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// The deliberate-run boundary: this path drives a real `go build` and
|
||||||
|
// cmd/link per invocation, minutes-scale work on the small single-core
|
||||||
|
// CI runner. Under -short (the push pipeline's mode) it skips; the
|
||||||
|
// local test gate and the dispatched workflows run it in full.
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("end-to-end go build and link: skipped in -short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the archive, overwrite the assembler's member with the gasm
|
||||||
|
// object and repack (go tool pack has no replace-in-place).
|
||||||
|
membersDir := filepath.Join(dir, "members")
|
||||||
|
if err := os.MkdirAll(membersDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extract := exec.Command(goBin, "tool", "pack", "x", st.pkgArch)
|
||||||
|
extract.Dir = membersDir
|
||||||
|
if out, err := extract.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
member := filepath.Join(membersDir, filepath.Base(st.asmObj))
|
||||||
|
if _, err := os.Stat(member); err != nil {
|
||||||
|
t.Fatalf("the assembler's archive member was not extracted: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(member, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(member, gasmObj, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
listCmd := exec.Command(goBin, "tool", "pack", "t", st.pkgArch)
|
||||||
|
listOut, err := listCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pack t: %v\n%s", err, listOut)
|
||||||
|
}
|
||||||
|
newArch := filepath.Join(dir, "pkg.a")
|
||||||
|
args := []string{"tool", "pack", "c", newArch}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for m := range strings.FieldsSeq(string(listOut)) {
|
||||||
|
if seen[m] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[m] = true
|
||||||
|
if err := os.Chmod(filepath.Join(membersDir, m), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args = append(args, m)
|
||||||
|
}
|
||||||
|
pack := exec.Command(goBin, args...)
|
||||||
|
pack.Dir = membersDir
|
||||||
|
if out, err := pack.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack c: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prove the substitution: the archive the linker is about to consume
|
||||||
|
// holds the gasm object, byte for byte.
|
||||||
|
checkDir := filepath.Join(dir, "check")
|
||||||
|
if err := os.MkdirAll(checkDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
check := exec.Command(goBin, "tool", "pack", "x", newArch)
|
||||||
|
check.Dir = checkDir
|
||||||
|
if out, err := check.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x (verification): %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
got, err := os.ReadFile(filepath.Join(checkDir, filepath.Base(st.asmObj)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read the substituted member back: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, gasmObj) {
|
||||||
|
t.Fatal("the repacked archive does not carry the gasm object")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-link. The line carries a GOROOT assignment and $WORK placeholders;
|
||||||
|
// GOEXPERIMENT must match the toolchain's own, because the linker
|
||||||
|
// compares the object header against its configuration.
|
||||||
|
goExp, _ := exec.Command(goBin, "env", "GOEXPERIMENT").Output()
|
||||||
|
linkLine := strings.ReplaceAll(st.linkLine, "$WORK", st.work)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "_pkg_.a"), newArch)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "exe", "a.out"), outBin)
|
||||||
|
env := append(os.Environ(), "GOEXPERIMENT="+strings.TrimSpace(string(goExp)))
|
||||||
|
env = append(env, extraEnv...)
|
||||||
|
link := exec.Command("sh", "-c", linkLine)
|
||||||
|
link.Dir = dir
|
||||||
|
link.Env = env
|
||||||
|
if out, err := link.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("link with the gasm object: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestGOObjectExternalPackageLink is the cross-package end-to-end check: a
|
// TestGOObjectExternalPackageLink is the cross-package end-to-end check: a
|
||||||
// GOOBJ whose code references a real external package symbol (runtime's
|
// GOOBJ whose code references a real external package symbol (runtime's
|
||||||
// morestack, a plain reference rather than the builtin noctxt form) must
|
// morestack, a plain reference rather than the builtin noctxt form) must
|
||||||
@@ -524,6 +663,9 @@ func fieldAfter(line, flag string) string {
|
|||||||
// failed. The binary is not run: morestack returns to the call site's
|
// failed. The binary is not run: morestack returns to the call site's
|
||||||
// stack check, which a hand-written caller has none of.
|
// stack check, which a hand-written caller has none of.
|
||||||
func TestGOObjectExternalPackageLink(t *testing.T) {
|
func TestGOObjectExternalPackageLink(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("end-to-end go build and link: skipped in -short mode")
|
||||||
|
}
|
||||||
goBin, err := exec.LookPath("go")
|
goBin, err := exec.LookPath("go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("no Go toolchain available")
|
t.Skip("no Go toolchain available")
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
return relocArm64Branch, 4
|
return relocArm64Branch, 4
|
||||||
case RelArm64LDST64:
|
case RelArm64LDST64:
|
||||||
return relocArm64LDST64, 8
|
return relocArm64LDST64, 8
|
||||||
|
case RelArm64TLSLE:
|
||||||
|
return relocArm64TLSLE, 4
|
||||||
default:
|
default:
|
||||||
return relocArm64Addr, 8
|
return relocArm64Addr, 8
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,7 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
const (
|
const (
|
||||||
relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair
|
relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair
|
||||||
relocArm64Branch = 9 // R_CALLARM64, BL instruction
|
relocArm64Branch = 9 // R_CALLARM64, BL instruction
|
||||||
|
relocArm64TLSLE = 32 // R_ARM64_TLS_LE, MOVZ local-exec TLS load
|
||||||
relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair
|
relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -30,6 +30,8 @@ func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
return relocRISCVPcrelStype, 8
|
return relocRISCVPcrelStype, 8
|
||||||
case RelRISCVJal:
|
case RelRISCVJal:
|
||||||
return relocRISCVJal, 4
|
return relocRISCVJal, 4
|
||||||
|
case RelRISCVTLSLE:
|
||||||
|
return relocRISCVTLSLE, 8
|
||||||
default:
|
default:
|
||||||
return relocRISCVPcrelItype, 8
|
return relocRISCVPcrelItype, 8
|
||||||
}
|
}
|
||||||
@@ -38,11 +40,13 @@ func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
|
|
||||||
// RISC-V relocation types (cmd/internal/objabi). The Go linker applies
|
// RISC-V relocation types (cmd/internal/objabi). The Go linker applies
|
||||||
// R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a
|
// R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a
|
||||||
// single 8-byte field; R_RISCV_JAL covers a single 4-byte J-type instruction.
|
// single 8-byte field; R_RISCV_JAL covers a single 4-byte J-type instruction;
|
||||||
|
// R_RISCV_TLS_LE covers the LUI + I-type pair of a local-exec TLS reference.
|
||||||
const (
|
const (
|
||||||
relocRISCVJal = 59 // R_RISCV_JAL
|
relocRISCVJal = 59 // R_RISCV_JAL
|
||||||
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
||||||
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
||||||
|
relocRISCVTLSLE = 65 // R_RISCV_TLS_LE
|
||||||
)
|
)
|
||||||
|
|
||||||
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The expected bytes are pinned from `go tool asm` output (Go 1.27, amd64,
|
// The expected bytes are pinned from `go tool asm` output (Go 1.27, amd64,
|
||||||
|
|||||||
+303
-39
@@ -90,6 +90,40 @@ var noOperandTable = map[string][]byte{
|
|||||||
"LOCK": {0xF0},
|
"LOCK": {0xF0},
|
||||||
"REP": {0xF3},
|
"REP": {0xF3},
|
||||||
"REPN": {0xF2},
|
"REPN": {0xF2},
|
||||||
|
"ENDBR64": {0xF3, 0x0F, 0x1E, 0xFA},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysUnaryTable maps the one-operand system instructions to their bytes:
|
||||||
|
// the prefix, the opcode and the /digit the reg field carries. The operand
|
||||||
|
// is a register or memory in r/m.
|
||||||
|
var sysUnaryTable = map[string]struct {
|
||||||
|
prefix byte
|
||||||
|
opcode []byte
|
||||||
|
digit int
|
||||||
|
}{
|
||||||
|
"CLWB": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"TPAUSE": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"UMONITOR": {0xF3, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"UMWAIT": {0xF2, []byte{0x0F, 0xAE}, 6},
|
||||||
|
"RDPID": {0xF3, []byte{0x0F, 0xC7}, 7},
|
||||||
|
"CLDEMOTE": {0x00, []byte{0x0F, 0x1C}, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSysUnary emits a one-operand system instruction: the operand in r/m
|
||||||
|
// under the fixed /digit, no REX.W.
|
||||||
|
func (e *enc) encodeSysUnary(mnem string, m struct {
|
||||||
|
prefix byte
|
||||||
|
opcode []byte
|
||||||
|
digit int
|
||||||
|
}, ops []Operand) error {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: m.opcode, modrm: -1, sib: -1}
|
||||||
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MOV --------------------------------------------------------------------
|
// --- MOV --------------------------------------------------------------------
|
||||||
@@ -111,6 +145,102 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
|||||||
// silently emit REX.W 8B with the wrong operand meaning.
|
// silently emit REX.W 8B with the wrong operand meaning.
|
||||||
_, srcVec := vecReg(src)
|
_, srcVec := vecReg(src)
|
||||||
dstReg, dstVec := vecReg(dst)
|
dstReg, dstVec := vecReg(dst)
|
||||||
|
|
||||||
|
// Control and debug register moves: 0F 20 (CRn→r64), 0F 22 (r64→CRn),
|
||||||
|
// 0F 21 (DRn→r64) and 0F 23 (r64→DRn). The CR/DR number rides the reg
|
||||||
|
// field, the general register r/m; CR8+/DR8+ take REX.R.
|
||||||
|
if c, ok := src.(Reg); ok && c.ctl != 0 {
|
||||||
|
g, ok := dst.(Reg)
|
||||||
|
if !ok || g.isVec() || g.ctl != 0 {
|
||||||
|
return fmt.Errorf("MOV: control/debug register load needs a general register destination")
|
||||||
|
}
|
||||||
|
opc := byte(0x20)
|
||||||
|
if c.ctl == 2 {
|
||||||
|
opc = 0x21
|
||||||
|
}
|
||||||
|
return e.emit(&instr{
|
||||||
|
opcode: []byte{0x0F, opc},
|
||||||
|
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||||
|
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if c, ok := dst.(Reg); ok && c.ctl != 0 {
|
||||||
|
g, ok := src.(Reg)
|
||||||
|
if !ok || g.isVec() || g.ctl != 0 {
|
||||||
|
return fmt.Errorf("MOV: control/debug register store needs a general register source")
|
||||||
|
}
|
||||||
|
opc := byte(0x22)
|
||||||
|
if c.ctl == 2 {
|
||||||
|
opc = 0x23
|
||||||
|
}
|
||||||
|
return e.emit(&instr{
|
||||||
|
opcode: []byte{0x0F, opc},
|
||||||
|
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||||
|
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MMX register moves: MOVQ M0, mem and MOVQ mem, M0 are the MMX
|
||||||
|
// load/store pair 0F 6F/0F 7F (no prefix); a register pair takes the
|
||||||
|
// load opcode. The GPR crossings ride the MOVD opcodes with REX.W
|
||||||
|
// (0F 6E into the bank, 0F 7E out), and an XMM source crosses into the
|
||||||
|
// bank through the F2 0F D6 move. The XMM MOVQ forms follow below.
|
||||||
|
if m, ok := src.(Reg); ok && m.mmx {
|
||||||
|
switch d := dst.(type) {
|
||||||
|
case Reg:
|
||||||
|
if !d.mmx && (d.isVec() || d.fp) {
|
||||||
|
return fmt.Errorf("MOV: MMX register moves stay inside the M bank")
|
||||||
|
}
|
||||||
|
if !d.mmx {
|
||||||
|
// M → GPR: 0F 7E with REX.W, the bank in reg and the GPR in
|
||||||
|
// r/m, the store layout the toolchain picks.
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x7E}, modrm: -1, sib: -1, rexW: true}
|
||||||
|
if err := setRM(i, m, d, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, d, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
case Mem:
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x7F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, d, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("MOV: invalid MMX destination")
|
||||||
|
}
|
||||||
|
if m, ok := dst.(Reg); ok && m.mmx {
|
||||||
|
switch src.(type) {
|
||||||
|
case Mem, sbMem:
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
case Reg:
|
||||||
|
if g := src.(Reg); g.isVec() {
|
||||||
|
// X → M: F2 0F D6, the bank in reg, the XMM source in r/m.
|
||||||
|
i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
// GPR → M: 0F 6E with REX.W, the bank in reg, the GPR in r/m.
|
||||||
|
i := &instr{opcode: []byte{0x0F, 0x6E}, modrm: -1, sib: -1, rexW: true}
|
||||||
|
if err := setRM(i, m, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("MOV: MMX load takes a register or memory source")
|
||||||
|
}
|
||||||
|
|
||||||
if srcVec || dstVec {
|
if srcVec || dstVec {
|
||||||
if dstVec {
|
if dstVec {
|
||||||
if g, ok := src.(Reg); ok && !g.isVec() {
|
if g, ok := src.(Reg); ok && !g.isVec() {
|
||||||
@@ -219,14 +349,14 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
|||||||
case Imm:
|
case Imm:
|
||||||
if dstIsReg {
|
if dstIsReg {
|
||||||
v := int64(src)
|
v := int64(src)
|
||||||
// The Go assembler compresses 64-bit moves whose immediate fits
|
// The Go assembler compresses 64-bit moves whose immediate
|
||||||
// a signed int32, choosing per sign:
|
// fits the zero-extending 32-bit span, choosing per sign:
|
||||||
// v >= 0: B8+rd imm32 without REX.W (zero-extended by the
|
// v >= 0: B8+rd imm32 without REX.W (zero-extended by the
|
||||||
// hardware, REX.B still emitted for R8-R15);
|
// hardware, REX.B still emitted for R8-R15);
|
||||||
// v < 0: REX.W C7 /0 imm32 (sign-extended, the plain B8+rd
|
// v < 0: REX.W C7 /0 imm32 (sign-extended, the plain B8+rd
|
||||||
// form would zero-extend and corrupt the value).
|
// form would zero-extend and corrupt the value).
|
||||||
// Out-of-range immediates keep the B8+rd imm64 form.
|
// Out-of-range immediates keep the B8+rd imm64 form.
|
||||||
if size == 8 && v >= 0 && v <= (1<<31)-1 {
|
if size == 8 && v >= 0 && v <= (1<<32)-1 {
|
||||||
i := newInstr(4, []byte{0xB8 + byte(dstReg.idx&7)})
|
i := newInstr(4, []byte{0xB8 + byte(dstReg.idx&7)})
|
||||||
i.rexB = dstReg.idx >= 8
|
i.rexB = dstReg.idx >= 8
|
||||||
i.imm = le32(v)
|
i.imm = le32(v)
|
||||||
@@ -399,8 +529,10 @@ func (e *enc) encodeALUImm(digit int, dst Operand, imm int64, size int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The byte accumulator short form (0x04+digit*8, no ModR/M) when
|
// The byte accumulator short form (0x04+digit*8, no ModR/M) when
|
||||||
// the destination is AL, the form the Go assembler prefers here.
|
// the destination is spelled AL itself; the size-agnostic AX takes
|
||||||
if r, ok := dst.(Reg); ok && r.idx == 0 {
|
// the generic 0x80 /digit form, the division go tool asm makes
|
||||||
|
// (ADDB $7, AL is 04 07 while ADDB $3, AX is 80 c0 03).
|
||||||
|
if r, ok := dst.(Reg); ok && r.idx == 0 && byteReg(r) {
|
||||||
i := &instr{opcode: []byte{byte(0x04 + digit*8)}, modrm: -1, sib: -1}
|
i := &instr{opcode: []byte{byte(0x04 + digit*8)}, modrm: -1, sib: -1}
|
||||||
i.imm = immBytes
|
i.imm = immBytes
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
@@ -457,8 +589,11 @@ func (e *enc) encodeTest(ops []Operand, size int) error {
|
|||||||
if imm, ok := src.(Imm); ok {
|
if imm, ok := src.(Imm); ok {
|
||||||
// TEST r/m, imm: 0xF6 (8-bit) / 0xF7 /0, but the Go assembler
|
// TEST r/m, imm: 0xF6 (8-bit) / 0xF7 /0, but the Go assembler
|
||||||
// always uses the accumulator forms (A8/A9, no ModR/M) when the
|
// always uses the accumulator forms (A8/A9, no ModR/M) when the
|
||||||
// register operand is AL/AX, whatever the immediate's width.
|
// register operand is AL/AX, whatever the immediate's width. At
|
||||||
if r, ok := dst.(Reg); ok && r.idx == 0 {
|
// byte width the short form belongs to the AL spelling alone; the
|
||||||
|
// size-agnostic AX takes the generic F6 /0 (TESTB $7, AX is
|
||||||
|
// f6 c0 07), the same division the ALU accumulator makes.
|
||||||
|
if r, ok := dst.(Reg); ok && r.idx == 0 && (size != 1 || byteReg(r)) {
|
||||||
op := byte(0xA9)
|
op := byte(0xA9)
|
||||||
if size == 1 {
|
if size == 1 {
|
||||||
op = 0xA8
|
op = 0xA8
|
||||||
@@ -518,6 +653,14 @@ func (e *enc) encodeLea(ops []Operand, size int) error {
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("LEA: source must be a memory operand")
|
return fmt.Errorf("LEA: source must be a memory operand")
|
||||||
}
|
}
|
||||||
|
// LEA accepts the full unsigned 32-bit displacement span where the
|
||||||
|
// loads and stores reject it beyond the signed one; the wide values
|
||||||
|
// ride the same disp32 bytes as their two's-complement bit pattern.
|
||||||
|
if m, ok := src.(Mem); ok && m.Disp >= 1<<31 && m.Disp <= (1<<32)-1 {
|
||||||
|
c := m
|
||||||
|
c.Disp = int64(int32(uint32(m.Disp)))
|
||||||
|
src = c
|
||||||
|
}
|
||||||
i := newInstr(size, []byte{0x8D})
|
i := newInstr(size, []byte{0x8D})
|
||||||
if err := setRM(i, dstReg, src, size); err != nil {
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -653,7 +796,7 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
|
|||||||
}
|
}
|
||||||
i.imm = []byte{byte(imm)}
|
i.imm = []byte{byte(imm)}
|
||||||
}
|
}
|
||||||
if err := setRMReg(i, srcReg.idx, srcReg.idx >= 8, false, dst, size); err != nil {
|
if err := setRMReg(i, srcReg.idx&7, srcReg.idx >= 8, false, dst, size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
@@ -663,6 +806,18 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
|
|||||||
|
|
||||||
func (e *enc) encodeImul(ops []Operand, size int) error {
|
func (e *enc) encodeImul(ops []Operand, size int) error {
|
||||||
switch len(ops) {
|
switch len(ops) {
|
||||||
|
case 1:
|
||||||
|
// The one-operand form, IMUL r/m: F6/F7 /5 with AL/AX/EAX/RAX as the
|
||||||
|
// implied destination (the toolchain's one-register shape).
|
||||||
|
opc := byte(0xF7)
|
||||||
|
if size == 1 {
|
||||||
|
opc = 0xF6
|
||||||
|
}
|
||||||
|
i := newInstr(size, []byte{opc})
|
||||||
|
if err := setRMDigit(i, 5, ops[0], size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
case 2:
|
case 2:
|
||||||
// Two shapes. The leading-immediate spelling IMUL $imm, r multiplies
|
// Two shapes. The leading-immediate spelling IMUL $imm, r multiplies
|
||||||
// r in place (dst = rm = r): the shape GOROOT's clock code writes.
|
// r in place (dst = rm = r): the shape GOROOT's clock code writes.
|
||||||
@@ -697,7 +852,7 @@ func (e *enc) encodeImul(ops []Operand, size int) error {
|
|||||||
// r/m operand (setRM takes registers and memory alike).
|
// r/m operand (setRM takes registers and memory alike).
|
||||||
return e.encodeImulImm(imm, ops[1], dstReg, size)
|
return e.encodeImulImm(imm, ops[1], dstReg, size)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("IMUL expects 2 or 3 operands, got %d", len(ops))
|
return fmt.Errorf("IMUL expects 1, 2 or 3 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeImulImm emits the immediate multiply: 0x6B with a sign-extended imm8
|
// encodeImulImm emits the immediate multiply: 0x6B with a sign-extended imm8
|
||||||
@@ -742,6 +897,27 @@ func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {
|
|||||||
w16 := size == 2
|
w16 := size == 2
|
||||||
switch op := ops[0].(type) {
|
switch op := ops[0].(type) {
|
||||||
case Reg:
|
case Reg:
|
||||||
|
// Segment registers: FS and GS carry their own one-byte opcodes
|
||||||
|
// under 0F (A0/A8 push, A1/A9 pop); the other four spellings are
|
||||||
|
// not pushable in 64-bit mode.
|
||||||
|
if n, isSeg := op.segNumber(); isSeg {
|
||||||
|
switch n {
|
||||||
|
case 4: // FS
|
||||||
|
if push {
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA0}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA1}, modrm: -1, sib: -1})
|
||||||
|
case 5: // GS
|
||||||
|
if push {
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA8}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return e.emit(&instr{opcode: []byte{0x0F, 0xA9}, modrm: -1, sib: -1})
|
||||||
|
}
|
||||||
|
return fmt.Errorf("PUSH/POP: only FS and GS are encodable in 64-bit mode")
|
||||||
|
}
|
||||||
|
if op.mmx || op.isVec() || op.fp || op.ctl != 0 {
|
||||||
|
return fmt.Errorf("PUSH/POP: invalid register operand")
|
||||||
|
}
|
||||||
base := byte(0x50) // PUSH r; POP is 0x58
|
base := byte(0x50) // PUSH r; POP is 0x58
|
||||||
if !push {
|
if !push {
|
||||||
base = 0x58
|
base = 0x58
|
||||||
@@ -772,8 +948,12 @@ func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {
|
|||||||
}
|
}
|
||||||
// PUSH imm32, sign-extended to 64 bits; go tool asm bounds the
|
// PUSH imm32, sign-extended to 64 bits; go tool asm bounds the
|
||||||
// immediate by the same signed/unsigned 32-bit span as every other
|
// immediate by the same signed/unsigned 32-bit span as every other
|
||||||
// scalar immediate.
|
// scalar immediate. The W spelling takes imm16 alone.
|
||||||
immBytes, err := immediate(int64(op), 8, false)
|
immWidth := 8
|
||||||
|
if w16 {
|
||||||
|
immWidth = 2
|
||||||
|
}
|
||||||
|
immBytes, err := immediate(int64(op), immWidth, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -893,29 +1073,46 @@ func immediate(v int64, size int, full64 bool) ([]byte, error) {
|
|||||||
|
|
||||||
// --- CMOVcc / SETcc ---------------------------------------------------------
|
// --- CMOVcc / SETcc ---------------------------------------------------------
|
||||||
|
|
||||||
|
// cmovCondition splits a CMOVcc suffix into an optional size letter and the
|
||||||
|
// condition code. Two vocabularies meet here: the Plan 9 spellings prefix
|
||||||
|
// the condition with a size letter (CMOVLGT, CMOVQEQ), while the toolchain's
|
||||||
|
// renderer prints the condition alone (CMOVLE, CMOVG) and leaves the width
|
||||||
|
// to the operand registers. A suffix that is itself a condition name reads
|
||||||
|
// as that condition, so the renderer's text re-encodes; CMOVBGT, CMOVWXX and
|
||||||
|
// the bare CMOV still find no condition and stay rejected.
|
||||||
|
func cmovCondition(rest string) (size int, cc int, ok bool) {
|
||||||
|
if cc, ok := jccMap[rest]; ok {
|
||||||
|
return 0, cc, true
|
||||||
|
}
|
||||||
|
if len(rest) >= 2 {
|
||||||
|
switch rest[0] {
|
||||||
|
case 'W':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 2, cc, true
|
||||||
|
}
|
||||||
|
case 'L':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 4, cc, true
|
||||||
|
}
|
||||||
|
case 'Q':
|
||||||
|
if cc, ok := jccMap[rest[1:]]; ok {
|
||||||
|
return 8, cc, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition
|
// encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition
|
||||||
// (CMOVLGT, CMOVQEQ, …). The condition reads exactly like the Jcc spellings;
|
// (CMOVLGT, CMOVQEQ, …), or the renderer's condition alone (CMOVLE) with the
|
||||||
// the instruction is 0F 40+cc with reg = dst, rm = src.
|
// width taken from the destination register. The instruction is 0F 40+cc
|
||||||
|
// with reg = dst, rm = src.
|
||||||
func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
rest := upper[len("CMOV"):]
|
rest := upper[len("CMOV"):]
|
||||||
if len(rest) < 2 {
|
size, cc, ok := cmovCondition(rest)
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
|
||||||
}
|
|
||||||
var size int
|
|
||||||
switch rest[0] {
|
|
||||||
case 'W':
|
|
||||||
size = 2
|
|
||||||
case 'L':
|
|
||||||
size = 4
|
|
||||||
case 'Q':
|
|
||||||
size = 8
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
|
||||||
}
|
|
||||||
cc, ok := jccMap[rest[1:]]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
return fmt.Errorf("unsupported instruction %q", upper)
|
||||||
}
|
}
|
||||||
@@ -924,6 +1121,14 @@ func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("CMOVcc destination must be a register")
|
return fmt.Errorf("CMOVcc destination must be a register")
|
||||||
}
|
}
|
||||||
|
if size == 0 {
|
||||||
|
// The renderer's spelling carries no size letter: the width rides
|
||||||
|
// the destination register's own size class.
|
||||||
|
size = dstReg.size
|
||||||
|
if size != 1 && size != 2 && size != 4 && size != 8 {
|
||||||
|
return fmt.Errorf("CMOVcc destination must be a general register")
|
||||||
|
}
|
||||||
|
}
|
||||||
i := newInstr(size, []byte{0x0F, byte(0x40 + cc)})
|
i := newInstr(size, []byte{0x0F, byte(0x40 + cc)})
|
||||||
if err := setRM(i, dstReg, src, size); err != nil {
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1093,6 +1298,38 @@ var sseMoveTable = map[string]sseMove{
|
|||||||
"MOVSS": {0xF3, 0x10, 0x11}, // scalar single
|
"MOVSS": {0xF3, 0x10, 0x11}, // scalar single
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sseStoreOnly holds the store-only SSE forms, OP xmm, mem: the XMM register
|
||||||
|
// rides the reg field and memory r/m (the non-temporal store).
|
||||||
|
var sseStoreOnly = map[string]struct {
|
||||||
|
prefix byte
|
||||||
|
op byte
|
||||||
|
}{
|
||||||
|
"MOVNTDQ": {0x66, 0xE7},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEStoreOnly encodes OP xmm, mem (reg = the XMM source, r/m = the
|
||||||
|
// destination memory).
|
||||||
|
func (e *enc) encodeSSEStoreOnly(mnem string, m struct {
|
||||||
|
prefix byte
|
||||||
|
op byte
|
||||||
|
}, ops []Operand) error {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
srcReg, ok := ops[0].(Reg)
|
||||||
|
if !ok || !srcReg.isVec() {
|
||||||
|
return fmt.Errorf("%s source must be a vector register", mnem)
|
||||||
|
}
|
||||||
|
if !isX86Mem(ops[1]) {
|
||||||
|
return fmt.Errorf("%s destination must be a memory operand", mnem)
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, srcReg, ops[1], 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
// encodeSSEMove encodes a legacy SSE move: a vector-to-vector move uses the
|
// encodeSSEMove encodes a legacy SSE move: a vector-to-vector move uses the
|
||||||
// load form (reg = destination), matching the Go assembler.
|
// load form (reg = destination), matching the Go assembler.
|
||||||
func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
||||||
@@ -1229,13 +1466,15 @@ type sseExtract struct {
|
|||||||
op []byte
|
op []byte
|
||||||
opMem []byte // used when the destination is memory; nil shares op
|
opMem []byte // used when the destination is memory; nil shares op
|
||||||
rexW bool // PEXTRQ's REX.W
|
rexW bool // PEXTRQ's REX.W
|
||||||
|
rev bool // PEXTRW's GPR form swaps the fields: the register
|
||||||
|
// destination rides reg and the XMM source r/m
|
||||||
}
|
}
|
||||||
|
|
||||||
var sseExtractTable = map[string]sseExtract{
|
var sseExtractTable = map[string]sseExtract{
|
||||||
"PEXTRB": {[]byte{0x0F, 0x3A, 0x14}, nil, false},
|
"PEXTRB": {[]byte{0x0F, 0x3A, 0x14}, nil, false, false},
|
||||||
"PEXTRD": {[]byte{0x0F, 0x3A, 0x16}, nil, false},
|
"PEXTRD": {[]byte{0x0F, 0x3A, 0x16}, nil, false, false},
|
||||||
"PEXTRQ": {[]byte{0x0F, 0x3A, 0x16}, nil, true},
|
"PEXTRQ": {[]byte{0x0F, 0x3A, 0x16}, nil, true, false},
|
||||||
"PEXTRW": {[]byte{0x0F, 0xC5}, []byte{0x0F, 0x3A, 0x15}, false},
|
"PEXTRW": {[]byte{0x0F, 0xC5}, []byte{0x0F, 0x3A, 0x15}, false, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// sseInsert describes a lane insert: OP $imm, src, xdst with reg = the XMM
|
// sseInsert describes a lane insert: OP $imm, src, xdst with reg = the XMM
|
||||||
@@ -1308,14 +1547,23 @@ func (e *enc) encodeSSEBin(m sseBin, ops []Operand) error {
|
|||||||
}
|
}
|
||||||
src, dst := ops[0], ops[1]
|
src, dst := ops[0], ops[1]
|
||||||
dstReg, ok := dst.(Reg)
|
dstReg, ok := dst.(Reg)
|
||||||
if !ok || !dstReg.isVec() {
|
if !ok || (!dstReg.isVec() && !dstReg.mmx) {
|
||||||
return fmt.Errorf("SSE binary destination must be a vector register")
|
return fmt.Errorf("SSE binary destination must be a vector register")
|
||||||
}
|
}
|
||||||
|
// The MMX twins of the packed-integer SSE2 ops drop the 0x66 prefix:
|
||||||
|
// PADDD M2, M1 is 0F FE where the XMM form is 66 0F FE.
|
||||||
|
prefix := m.prefix
|
||||||
|
if dstReg.mmx {
|
||||||
|
if prefix != 0x66 {
|
||||||
|
return fmt.Errorf("SSE binary: this form takes no MMX register operand")
|
||||||
|
}
|
||||||
|
prefix = 0
|
||||||
|
}
|
||||||
opcode := []byte{0x0F, m.op}
|
opcode := []byte{0x0F, m.op}
|
||||||
if m.map38 {
|
if m.map38 {
|
||||||
opcode = []byte{0x0F, 0x38, m.op}
|
opcode = []byte{0x0F, 0x38, m.op}
|
||||||
}
|
}
|
||||||
i := &instr{prefix: m.prefix, opcode: opcode, modrm: -1, sib: -1}
|
i := &instr{prefix: prefix, opcode: opcode, modrm: -1, sib: -1}
|
||||||
if err := setRM(i, dstReg, src, 8); err != nil {
|
if err := setRM(i, dstReg, src, 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1713,8 +1961,17 @@ func (e *enc) encodeSSEExtract(m sseExtract, ops []Operand) error {
|
|||||||
if m.opMem != nil && memOperand(ops[2]) {
|
if m.opMem != nil && memOperand(ops[2]) {
|
||||||
opcode = m.opMem
|
opcode = m.opMem
|
||||||
}
|
}
|
||||||
|
reg, rm := srcReg, ops[2]
|
||||||
|
if m.rev && opcode[1] == 0xC5 {
|
||||||
|
// the 0F C5 layout: the GPR destination rides reg, the XMM source r/m
|
||||||
|
if d, ok := ops[2].(Reg); !ok {
|
||||||
|
return fmt.Errorf("PEXTRW destination must be a register or memory")
|
||||||
|
} else {
|
||||||
|
reg, rm = d, ops[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
i := &instr{prefix: 0x66, opcode: opcode, modrm: -1, sib: -1, rexW: m.rexW}
|
i := &instr{prefix: 0x66, opcode: opcode, modrm: -1, sib: -1, rexW: m.rexW}
|
||||||
if err := setRM(i, srcReg, ops[2], 8); err != nil {
|
if err := setRM(i, reg, rm, 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
i.imm = []byte{immByte}
|
i.imm = []byte{immByte}
|
||||||
@@ -1791,12 +2048,19 @@ func (e *enc) encodeSSEShift(name string, ops []Operand) error {
|
|||||||
// immediate LAST in Plan 9 order (src, dst, $imm), unlike the shuffle family:
|
// immediate LAST in Plan 9 order (src, dst, $imm), unlike the shuffle family:
|
||||||
// F2 0F C2 with reg = dst, rm = src.
|
// F2 0F C2 with reg = dst, rm = src.
|
||||||
func (e *enc) encodeCmpsd(ops []Operand) error {
|
func (e *enc) encodeCmpsd(ops []Operand) error {
|
||||||
|
return e.encodeSSECmp("CMPSD", 0xF2, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSECmp encodes the SSE compare family (CMPSD/CMPSS/CMPPS/CMPPD):
|
||||||
|
// 0F C2 /r ib with the predicate immediate last in Plan 9 order
|
||||||
|
// (src, dst, $imm) and the packed forms' prefixes.
|
||||||
|
func (e *enc) encodeSSECmp(mnem string, prefix byte, ops []Operand) error {
|
||||||
if len(ops) != 3 {
|
if len(ops) != 3 {
|
||||||
return fmt.Errorf("CMPSD expects 3 operands (src, dst, $imm), got %d", len(ops))
|
return fmt.Errorf("%s expects 3 operands (src, dst, $imm), got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
imm, ok := ops[2].(Imm)
|
imm, ok := ops[2].(Imm)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("CMPSD predicate must be an immediate")
|
return fmt.Errorf("%s predicate must be an immediate", mnem)
|
||||||
}
|
}
|
||||||
immByte, err := imm8(int64(imm))
|
immByte, err := imm8(int64(imm))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1804,9 +2068,9 @@ func (e *enc) encodeCmpsd(ops []Operand) error {
|
|||||||
}
|
}
|
||||||
dstReg, ok2 := ops[1].(Reg)
|
dstReg, ok2 := ops[1].(Reg)
|
||||||
if !ok2 || !dstReg.isVec() {
|
if !ok2 || !dstReg.isVec() {
|
||||||
return fmt.Errorf("CMPSD destination must be a vector register")
|
return fmt.Errorf("%s destination must be a vector register", mnem)
|
||||||
}
|
}
|
||||||
i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
||||||
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAssembleGoFlacAVX2Kernel assembles the whole production AVX2 kernel;
|
// TestAssembleGoFlacAVX2Kernel assembles the whole production AVX2 kernel;
|
||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
||||||
path := "../../go-libraries/go-flac/avx2_amd64.s"
|
path := "../../go-libraries/go-flac/avx2_amd64.s"
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
t.Skip("go-libraries repository not present next to gasm-devkit")
|
t.Skip("go-libraries repository not present next to gasm-sdk")
|
||||||
}
|
}
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,7 +86,7 @@ func TestAssembleGoFlacAVX2Kernel(t *testing.T) {
|
|||||||
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
||||||
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
t.Skip("go-libraries repository not present next to gasm-devkit")
|
t.Skip("go-libraries repository not present next to gasm-sdk")
|
||||||
}
|
}
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The differential kernels for the DATA-path and front-end gaps are kept in
|
// The differential kernels for the DATA-path and front-end gaps are kept in
|
||||||
@@ -23,9 +23,18 @@ import (
|
|||||||
// bytes must agree with the relocation sites masked on both sides.
|
// bytes must agree with the relocation sites masked on both sides.
|
||||||
|
|
||||||
// toolAsmObject assembles path with the installed toolchain's assembler for
|
// toolAsmObject assembles path with the installed toolchain's assembler for
|
||||||
// goarch ("" = the host) and returns the object bytes.
|
// 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 {
|
func toolAsmObject(t *testing.T, path, goarch string) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live go tool asm oracle: skipped in -short mode")
|
||||||
|
}
|
||||||
goBin, err := exec.LookPath("go")
|
goBin, err := exec.LookPath("go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("no Go toolchain available")
|
t.Skip("no Go toolchain available")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGOObjectLOONG64Structure checks the emitted loong64 object's blocks:
|
// TestGOObjectLOONG64Structure checks the emitted loong64 object's blocks:
|
||||||
|
|||||||
+132
-9
@@ -9,7 +9,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Image is an assembled file: the function bodies laid out in source order,
|
// Image is an assembled file: the function bodies laid out in source order,
|
||||||
@@ -98,6 +98,7 @@ const (
|
|||||||
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
||||||
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
||||||
RelRISCVJal // R_RISCV_JAL (J-type call)
|
RelRISCVJal // R_RISCV_JAL (J-type call)
|
||||||
|
RelRISCVTLSLE // R_RISCV_TLS_LE (LUI + I-type local-exec pair)
|
||||||
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
||||||
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
||||||
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
||||||
@@ -105,6 +106,7 @@ const (
|
|||||||
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
||||||
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
||||||
RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field
|
RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field
|
||||||
|
RelArm64TLSLE // R_ARM64_TLS_LE (MOVZ local-exec TLS load)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reloc struct {
|
type Reloc struct {
|
||||||
@@ -134,7 +136,8 @@ type DataSymbol struct {
|
|||||||
Offset int // byte offset within Data
|
Offset int // byte offset within Data
|
||||||
Size int
|
Size int
|
||||||
Static bool // the <> marker: file-local, not exported
|
Static bool // the <> marker: file-local, not exported
|
||||||
Rodata bool // the RODATA flag: read-only data
|
Rodata bool // the RODATA flag: read-only data (implies no pointers)
|
||||||
|
Noptr bool // the NOPTR flag: data with no pointers, kept out of GC scanning
|
||||||
Dupok bool // the DUPOK flag: duplicate-OK
|
Dupok bool // the DUPOK flag: duplicate-OK
|
||||||
// Relocs carries the symbol-valued DATA initialisers ("DATA s+0(SB)/8,
|
// 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
|
// $other(SB)"): fields of this symbol's data that hold another symbol's
|
||||||
@@ -269,6 +272,7 @@ func AssembleFile(f *ast.File, opts ...AssembleOption) (*Image, error) {
|
|||||||
Size: len(d.buf),
|
Size: len(d.buf),
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
img.Data = append(img.Data, d.buf...)
|
img.Data = append(img.Data, d.buf...)
|
||||||
@@ -333,6 +337,35 @@ func AssembleFile(f *ast.File, opts ...AssembleOption) (*Image, error) {
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// riscvTLSSymbols names the symbols the file declares with the TLSBSS flag
|
||||||
|
// (or its legacy numeric constant 256 from textflag.h): the assembler gives
|
||||||
|
// their SB references the local-exec TLS sequence.
|
||||||
|
func riscvTLSSymbols(f *ast.File) map[string]bool {
|
||||||
|
var tls map[string]bool
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
gd, ok := d.(*ast.Globl)
|
||||||
|
if !ok || gd.Name == nil || gd.Name.Pseudo != "SB" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, fl := range gd.Flags {
|
||||||
|
isTLS := fl == "TLSBSS"
|
||||||
|
if !isTLS {
|
||||||
|
if n, err := strconv.Atoi(fl); err == nil && n&256 != 0 {
|
||||||
|
isTLS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isTLS {
|
||||||
|
if tls == nil {
|
||||||
|
tls = map[string]bool{}
|
||||||
|
}
|
||||||
|
tls[gd.Name.Name] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tls
|
||||||
|
}
|
||||||
|
|
||||||
// AssembleFileRISCV assembles every TEXT function of a parsed RISC-V file
|
// AssembleFileRISCV assembles every TEXT function of a parsed RISC-V file
|
||||||
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
||||||
// code. SB references in the code are encoded as AUIPC pairs with zero
|
// code. SB references in the code are encoded as AUIPC pairs with zero
|
||||||
@@ -342,6 +375,10 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// The symbols the file declares TLSBSS resolve through the local-exec
|
||||||
|
// sequence (LUI + ADDIW + ADD of TP), exactly as the toolchain routes
|
||||||
|
// every SB reference whose symbol carries the STLSBSS type.
|
||||||
|
tlsSyms := riscvTLSSymbols(f)
|
||||||
// The pooled $i64 constants the wide MOV immediate loads refer to join
|
// The pooled $i64 constants the wide MOV immediate loads refer to join
|
||||||
// the declared data as read-only symbols, deduplicated across the file
|
// the declared data as read-only symbols, deduplicated across the file
|
||||||
// (the toolchain synthesises the same symbols into its rodata).
|
// (the toolchain synthesises the same symbols into its rodata).
|
||||||
@@ -353,7 +390,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t)
|
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t, tlsSyms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
}
|
}
|
||||||
@@ -413,6 +450,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
Size: d.size,
|
Size: d.size,
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -485,6 +523,7 @@ func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
|||||||
Size: d.size,
|
Size: d.size,
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
Rodata: d.rodata,
|
Rodata: d.rodata,
|
||||||
|
Noptr: d.noptr,
|
||||||
Dupok: d.dupok,
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -546,19 +585,91 @@ type dataSym struct {
|
|||||||
size int
|
size int
|
||||||
static bool
|
static bool
|
||||||
rodata bool
|
rodata bool
|
||||||
|
noptr bool
|
||||||
dupok bool
|
dupok bool
|
||||||
// relocs are the symbol-valued DATA fields, in declaration order; Off
|
// relocs are the symbol-valued DATA fields, in declaration order; Off
|
||||||
// is relative to the symbol's data start.
|
// is relative to the symbol's data start.
|
||||||
relocs []Reloc
|
relocs []Reloc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkDuplicateDecls rejects a symbol the file declares twice, the
|
||||||
|
// toolchain's OnList rule (cmd/internal/obj, InitTextSym and GloblPos,
|
||||||
|
// measured with go tool asm): the second declaration of a symbol is
|
||||||
|
// diagnosed as a redeclaration whether the pair is two TEXTs, two GLOBLs
|
||||||
|
// or one of each, and a second TEXT carries the other declaration's line
|
||||||
|
// the way the toolchain's note does. The DUPOK flag plays no part at
|
||||||
|
// assembly time: it legalises duplicate definitions across files
|
||||||
|
// (AttrDuplicateOK, which the linker resolves), never two declarations
|
||||||
|
// inside one file, so a DUPOK pair here is rejected exactly like a plain
|
||||||
|
// one. Symbol identity follows the object model: the package prefix and
|
||||||
|
// the <> static marker are part of the name (foo(SB), foo<>(SB) and
|
||||||
|
// other·foo(SB) are three symbols); the ABI selector is not, because
|
||||||
|
// outside the runtime package, where alone it is legal, foo<ABIInternal>(SB)
|
||||||
|
// resolves to foo(SB) and is a redeclaration.
|
||||||
|
func checkDuplicateDecls(f *ast.File) error {
|
||||||
|
type decl struct {
|
||||||
|
text bool
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
seen := make(map[string]decl)
|
||||||
|
symKey := func(s *ast.Symbol) string {
|
||||||
|
key := s.Pkg + "\x00" + s.Name
|
||||||
|
if s.Static {
|
||||||
|
key += "\x00<>"
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
switch t := d.(type) {
|
||||||
|
case *ast.Text:
|
||||||
|
if first, dup := seen[symKey(t.Name)]; dup {
|
||||||
|
if first.text {
|
||||||
|
return fmt.Errorf("symbol %q redeclared (other declaration on line %d)", t.Name.Name, first.line)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("symbol %q redeclared", t.Name.Name)
|
||||||
|
}
|
||||||
|
seen[symKey(t.Name)] = decl{text: true, line: t.Pos().Line}
|
||||||
|
case *ast.Globl:
|
||||||
|
if t.Name == nil || t.Name.Pseudo != "SB" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, dup := seen[symKey(t.Name)]; dup {
|
||||||
|
return fmt.Errorf("symbol %q redeclared", t.Name.Name)
|
||||||
|
}
|
||||||
|
seen[symKey(t.Name)] = decl{line: t.Pos().Line}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data section's size ceilings. The image materialises every GLOBL's
|
||||||
|
// bytes at assembly time, where the toolchain defers the cost to its linker,
|
||||||
|
// so gasm needs its own bound and a diagnostic in place of an allocation
|
||||||
|
// failure. The toolchain's own limit (obj's "symbol too large", 2,000,000,000
|
||||||
|
// bytes) would let a twenty-five byte input demand four gigabytes of resident
|
||||||
|
// memory (measured: `GLOBL d(SB), $2000000000` peaks at 3.92 GiB RSS), which
|
||||||
|
// starves the memory fence the test recipes run under when the fuzz workers
|
||||||
|
// share it. 64 MiB per symbol and 128 MiB per file sit two orders above any
|
||||||
|
// real assembly symbol (the runtime's largest GLOBL is KiB-scale) and keep a
|
||||||
|
// worker's worst-case peak near its fence share.
|
||||||
|
const (
|
||||||
|
maxSymbolSize = 64 << 20
|
||||||
|
maxDataSize = 128 << 20
|
||||||
|
)
|
||||||
|
|
||||||
// collectData gathers the file's static symbols (GLOBL) and their initial
|
// collectData gathers the file's static symbols (GLOBL) and their initial
|
||||||
// contents (DATA) into byte buffers. Two passes: the Plan 9 convention puts
|
// contents (DATA) into byte buffers. Two passes: the Plan 9 convention puts
|
||||||
// every DATA line before its symbol's GLOBL, so the symbols are registered
|
// every DATA line before its symbol's GLOBL, so the symbols are registered
|
||||||
// before the initialisers are applied.
|
// before the initialisers are applied. Every per-architecture entry point
|
||||||
|
// collects data before assembling text, so the duplicate-declaration check
|
||||||
|
// rides here and covers them all.
|
||||||
func collectData(f *ast.File) ([]dataSym, error) {
|
func collectData(f *ast.File) ([]dataSym, error) {
|
||||||
|
if err := checkDuplicateDecls(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
index := map[string]int{}
|
index := map[string]int{}
|
||||||
var syms []dataSym
|
var syms []dataSym
|
||||||
|
total := 0
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
gd, ok := d.(*ast.Globl)
|
gd, ok := d.(*ast.Globl)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -568,12 +679,19 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := gd.Name.Name
|
name := gd.Name.Name
|
||||||
if _, dup := index[name]; dup {
|
|
||||||
return nil, fmt.Errorf("duplicate GLOBL %q", name)
|
|
||||||
}
|
|
||||||
size := 0
|
size := 0
|
||||||
if gd.Size != nil && gd.Size.Imm.HasVal {
|
if gd.Size != nil && gd.Size.Imm.HasVal {
|
||||||
size = int(gd.Size.Imm.Val)
|
size = int(gd.Size.Imm.Val)
|
||||||
|
if gd.Size.Imm.Neg {
|
||||||
|
size = -size
|
||||||
|
}
|
||||||
|
if size < 0 || size > maxSymbolSize {
|
||||||
|
return nil, fmt.Errorf("GLOBL %q: symbol too large (%d bytes > %d bytes)", name, size, maxSymbolSize)
|
||||||
|
}
|
||||||
|
if total+size > maxDataSize {
|
||||||
|
return nil, fmt.Errorf("GLOBL %q: data section too large (%d bytes > %d bytes)", name, total+size, maxDataSize)
|
||||||
|
}
|
||||||
|
total += size
|
||||||
}
|
}
|
||||||
index[name] = len(syms)
|
index[name] = len(syms)
|
||||||
ds := dataSym{
|
ds := dataSym{
|
||||||
@@ -587,12 +705,14 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
switch f {
|
switch f {
|
||||||
case "RODATA":
|
case "RODATA":
|
||||||
ds.rodata = true
|
ds.rodata = true
|
||||||
|
case "NOPTR":
|
||||||
|
ds.noptr = true
|
||||||
case "DUPOK":
|
case "DUPOK":
|
||||||
ds.dupok = true
|
ds.dupok = true
|
||||||
default:
|
default:
|
||||||
// Legacy numeric flag constants (runtime/textflag.h):
|
// Legacy numeric flag constants (runtime/textflag.h):
|
||||||
// DUPOK is 2, RODATA is 8; combinations arrive as one
|
// DUPOK is 2, RODATA is 8, NOPTR is 16; combinations arrive
|
||||||
// number (e.g. 10 = RODATA|DUPOK).
|
// as one number (e.g. 10 = RODATA|DUPOK).
|
||||||
if n, err := strconv.Atoi(f); err == nil {
|
if n, err := strconv.Atoi(f); err == nil {
|
||||||
if n&2 != 0 {
|
if n&2 != 0 {
|
||||||
ds.dupok = true
|
ds.dupok = true
|
||||||
@@ -600,6 +720,9 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
if n&8 != 0 {
|
if n&8 != 0 {
|
||||||
ds.rodata = true
|
ds.rodata = true
|
||||||
}
|
}
|
||||||
|
if n&16 != 0 {
|
||||||
|
ds.noptr = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-38
@@ -12,9 +12,107 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestDuplicateDecls pins the duplicate-declaration rule against the
|
||||||
|
// toolchain's, measured with go tool asm (Go 1.27.1): a symbol declared
|
||||||
|
// twice in one file is rejected at the second declaration whether the pair
|
||||||
|
// is two TEXTs, two GLOBLs or one of each, and DUPOK never legalises it
|
||||||
|
// within a file (the toolchain's duperror.s testdata asserts the plain
|
||||||
|
// pair). DUPOK legalises duplicates across files, so each file of a pair
|
||||||
|
// assembles cleanly on its own, and symbol identity keeps the package
|
||||||
|
// prefix and the <> static marker: foo(SB), foo<>(SB) and other·foo(SB)
|
||||||
|
// are three symbols, not one. Every case runs through all four
|
||||||
|
// per-architecture entry points.
|
||||||
|
func TestDuplicateDecls(t *testing.T) {
|
||||||
|
targets := []struct {
|
||||||
|
goarch string
|
||||||
|
file string
|
||||||
|
asm func(*ast.File) (*Image, error)
|
||||||
|
}{
|
||||||
|
{"amd64", "dup_amd64.s", func(f *ast.File) (*Image, error) { return AssembleFile(f) }},
|
||||||
|
{"arm64", "dup_arm64.s", AssembleFileARM64},
|
||||||
|
{"riscv64", "dup_riscv64.s", AssembleFileRISCV},
|
||||||
|
{"loong64", "dup_loong64.s", AssembleFileLOONG64},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string // substring of the error, "" for no error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"plain TEXT duplicate rejected",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nTEXT foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared (other declaration on line 1)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DUPOK pair in one file rejected",
|
||||||
|
"TEXT foo(SB), DUPOK, $0\n\tRET\nTEXT foo(SB), DUPOK, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared (other declaration on line 1)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TEXT then GLOBL rejected",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nGLOBL foo(SB), NOPTR, $8\n",
|
||||||
|
`symbol "foo" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GLOBL then TEXT rejected",
|
||||||
|
"GLOBL foo(SB), NOPTR, $8\nTEXT foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
`symbol "foo" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"plain GLOBL duplicate rejected",
|
||||||
|
"GLOBL bar(SB), NOPTR, $8\nGLOBL bar(SB), NOPTR, $8\n",
|
||||||
|
`symbol "bar" redeclared`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"static and package markers separate symbols",
|
||||||
|
"TEXT foo(SB), NOSPLIT, $0\n\tRET\nTEXT foo<>(SB), NOSPLIT, $0\n\tRET\nTEXT other\u00b7foo(SB), NOSPLIT, $0\n\tRET\n",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single DUPOK TEXT assembles",
|
||||||
|
"TEXT foo(SB), DUPOK, $0\n\tRET\n",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tg := range targets {
|
||||||
|
for _, c := range cases {
|
||||||
|
f, errs := parser.Parse(tg.file, c.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("%s/%s: parse: %v", tg.goarch, c.name, errs)
|
||||||
|
}
|
||||||
|
_, err := tg.asm(f)
|
||||||
|
if c.want == "" {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/%s: error %v, want none", tg.goarch, c.name, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil || !strings.Contains(err.Error(), c.want) {
|
||||||
|
t.Errorf("%s/%s: error %v, want substring %q", tg.goarch, c.name, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DUPOK pair across files is the case DUPOK exists for: the linker
|
||||||
|
// resolves it, the assembler never sees both sides, so each file of the
|
||||||
|
// pair assembles cleanly on its own.
|
||||||
|
for _, tg := range targets {
|
||||||
|
for _, name := range []string{"first", "second"} {
|
||||||
|
f, errs := parser.Parse(tg.file, "TEXT foo(SB), DUPOK, $0\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("%s/dupok cross-file %s: parse: %v", tg.goarch, name, errs)
|
||||||
|
}
|
||||||
|
if _, err := tg.asm(f); err != nil {
|
||||||
|
t.Errorf("%s/dupok cross-file %s: error %v, want none", tg.goarch, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAssembleFileStaticData checks the whole-image layout; code, padding
|
// TestAssembleFileStaticData checks the whole-image layout; code, padding
|
||||||
// and the data section; and that the RIP-relative displacements of static
|
// and the data section; and that the RIP-relative displacements of static
|
||||||
// symbol loads resolve to the right bytes.
|
// symbol loads resolve to the right bytes.
|
||||||
@@ -60,23 +158,15 @@ DATA small<>+0(SB)/4, $0x1234
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAssembleFileErrors checks the static-symbol error paths.
|
// TestAssembleFileErrors checks the static-symbol error paths. A reference
|
||||||
|
// to a static symbol no GLOBL defines defers to the linker exactly as the
|
||||||
|
// toolchain does (an external relocation), so it is not an error here.
|
||||||
func TestAssembleFileErrors(t *testing.T) {
|
func TestAssembleFileErrors(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
src string
|
src string
|
||||||
want string // substring of the error
|
want string // substring of the error
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
"undefined symbol",
|
|
||||||
`
|
|
||||||
#include "textflag.h"
|
|
||||||
TEXT ·f(SB), NOSPLIT, $0
|
|
||||||
VMOVDQU nope<>(SB), X0
|
|
||||||
RET
|
|
||||||
`,
|
|
||||||
"undefined symbol",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"DATA without GLOBL",
|
"DATA without GLOBL",
|
||||||
`
|
`
|
||||||
@@ -369,23 +459,8 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
}
|
}
|
||||||
var work, linkLine, asmObj string
|
st := parseBuildLog(t, buildLog, "main_amd64.s")
|
||||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
defer os.RemoveAll(st.work)
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(line, "WORK="):
|
|
||||||
work = strings.TrimPrefix(line, "WORK=")
|
|
||||||
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_amd64.s") && !strings.Contains(line, "-gensymabis"):
|
|
||||||
asmObj = fieldAfter(line, "-o")
|
|
||||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
|
||||||
linkLine = line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if work == "" || asmObj == "" || linkLine == "" {
|
|
||||||
t.Skipf("could not parse build log (work=%q asmObj=%q link=%q)", work, asmObj, linkLine)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(work)
|
|
||||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
|
||||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
|
||||||
|
|
||||||
// Assemble the same source with gasm and substitute the object.
|
// Assemble the same source with gasm and substitute the object.
|
||||||
src, err := os.ReadFile(filepath.Join(dir, "main_amd64.s"))
|
src, err := os.ReadFile(filepath.Join(dir, "main_amd64.s"))
|
||||||
@@ -400,21 +475,18 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("AssembleFile: %v", err)
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
}
|
}
|
||||||
gasmObj, err := img.GOObject("dlink", "main_amd64.s")
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("GOObject: %v", err)
|
t.Fatalf("GOObject: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"), gasmObj)
|
||||||
t.Fatalf("write gasm object: %v", err)
|
|
||||||
}
|
|
||||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
|
||||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
|
||||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The linked program must run and find the right function behind the
|
// The linked program must run and find the right function behind the
|
||||||
// data word.
|
// data word.
|
||||||
out, err := exec.Command(filepath.Join(dir, "prog")).CombinedOutput()
|
out, err := exec.Command(filepath.Join(dir, "prog2")).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("linked program failed: %v\n%s", err, out)
|
t.Fatalf("linked program failed: %v\n%s", err, out)
|
||||||
}
|
}
|
||||||
|
|||||||
+445
-30
@@ -9,7 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// assembleLOONG64 assembles a LoongArch (loong64) TEXT function body into
|
// assembleLOONG64 assembles a LoongArch (loong64) TEXT function body into
|
||||||
@@ -360,6 +360,59 @@ func l64SubToAdd(mnem string, ops []*ast.Operand) (string, bool) {
|
|||||||
return mnem, false
|
return mnem, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64PairRegister reports whether name names a register the pair sugar may
|
||||||
|
// carry: the general, FP, FCC and FCSR spellings loong64RegNum resolves, or
|
||||||
|
// a bare LSX/LASX vector register (V0-V31, X0-X31), which lives outside
|
||||||
|
// that table and parses only through the vector reader.
|
||||||
|
func l64PairRegister(name string) bool {
|
||||||
|
if loong64RegNum(name) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v, ok := l64ParseVecOperand(&ast.Operand{Kind: ast.OpAddr, Raw: name})
|
||||||
|
return ok && !v.hasSuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// l64ExpandPairs rewrites the toolchain's register-pair spellings into the
|
||||||
|
// operand list its own parser produces: a top-level colon between two
|
||||||
|
// registers splits the operand in two with the halves swapped (the old x86
|
||||||
|
// "register pair" syntax the shared grammar keeps on every GOARCH), so
|
||||||
|
// INSTR R4:R5, R6 encodes exactly as INSTR R5, R4, R6, bytes included.
|
||||||
|
// The sugar is pure syntax: every acceptance question the reordered list
|
||||||
|
// raises is answered by the ordinary operand matching. changed reports
|
||||||
|
// whether any operand carried it; the operand objects are copied, never
|
||||||
|
// edited in the shared syntax tree.
|
||||||
|
func l64ExpandPairs(ops []*ast.Operand) ([]*ast.Operand, bool) {
|
||||||
|
changed := false
|
||||||
|
var out []*ast.Operand
|
||||||
|
for i, op := range ops {
|
||||||
|
sfx := strings.Join(strings.Fields(op.Addr.Shift), "")
|
||||||
|
if op.Addr.Base != "" || !strings.HasPrefix(sfx, ":") || !l64PairRegister(sfx[1:]) {
|
||||||
|
if changed {
|
||||||
|
out = append(out, op)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
out = make([]*ast.Operand, 0, len(ops)+1)
|
||||||
|
out = append(out, ops[:i]...)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
name := sfx[1:]
|
||||||
|
// The second register lands first. Both halves keep the operand
|
||||||
|
// shape a plain register spelling parses to (a bare symbol
|
||||||
|
// reference), which is what every operand consumer reads.
|
||||||
|
second := *op
|
||||||
|
second.Addr.Shift = ""
|
||||||
|
second.Addr.Sym = &ast.Symbol{Raw: name, Name: name}
|
||||||
|
second.Raw = name
|
||||||
|
first := *op
|
||||||
|
first.Addr.Shift = ""
|
||||||
|
first.Raw = operandRegName(op)
|
||||||
|
out = append(out, &second, &first)
|
||||||
|
}
|
||||||
|
return out, changed
|
||||||
|
}
|
||||||
|
|
||||||
// loong64InstrSize returns the encoded size of an instruction: 4 bytes for
|
// loong64InstrSize returns the encoded size of an instruction: 4 bytes for
|
||||||
// most, more for the multi-instruction expansions.
|
// most, more for the multi-instruction expansions.
|
||||||
func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
||||||
@@ -367,10 +420,22 @@ func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
|||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
var neg bool
|
var neg bool
|
||||||
mnem, neg = l64SubToAdd(mnem, ops)
|
mnem, neg = l64SubToAdd(mnem, ops)
|
||||||
|
// The register-pair sugar widens the operand list exactly as the encode
|
||||||
|
// pass sees it, so both passes count the same instruction.
|
||||||
|
if pairOps, changed := l64ExpandPairs(ops); changed {
|
||||||
|
ops = pairOps
|
||||||
|
}
|
||||||
|
|
||||||
if mnem == "RET" {
|
if mnem == "RET" {
|
||||||
return len(loong64Return(fi))
|
return len(loong64Return(fi))
|
||||||
}
|
}
|
||||||
|
// BYTE lays down one raw byte per operand, a front-end pseudo-op the
|
||||||
|
// toolchain spells only on x86 but accepts here the same way the arm64
|
||||||
|
// and riscv64 encoders do (a superset spelling, shippable via the goobj
|
||||||
|
// path).
|
||||||
|
if mnem == "BYTE" {
|
||||||
|
return len(ops)
|
||||||
|
}
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "END", "FUNCDATA", "PCDATA":
|
case "END", "FUNCDATA", "PCDATA":
|
||||||
return 0 // bookkeeping statements contribute no bytes
|
return 0 // bookkeeping statements contribute no bytes
|
||||||
@@ -380,6 +445,16 @@ func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
|||||||
return 8 // bne/beq over the BREAK, then BREAK
|
return 8 // bne/beq over the BREAK, then BREAK
|
||||||
case "PRELDX":
|
case "PRELDX":
|
||||||
return 20 // the four-instruction constant materialisation + preldx
|
return 20 // the four-instruction constant materialisation + preldx
|
||||||
|
case "LL", "LLW", "LLV", "SC", "SCW", "SCV", "MOVWP", "MOVVP":
|
||||||
|
// The 2RI14 families: one word inside the signed 16-bit offset
|
||||||
|
// span, otherwise the toolchain's materialisation sequences (see
|
||||||
|
// l64firr14Words, which decides the width). A memory operand that
|
||||||
|
// will not parse leaves the single-word size: the encode pass
|
||||||
|
// reports the error.
|
||||||
|
if _, _, off, _, err := l64MemOperands(ops, fi); err == nil {
|
||||||
|
return len(l64firr14Words(0, int(off), 0, 0))
|
||||||
|
}
|
||||||
|
return 4
|
||||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||||
return loong64MovSize(mnem, ops, fi)
|
return loong64MovSize(mnem, ops, fi)
|
||||||
case "ADD", "ADDW", "ADDV", "ADDVU", "AND", "OR", "XOR", "SGT", "SGTU":
|
case "ADD", "ADDW", "ADDV", "ADDVU", "AND", "OR", "XOR", "SGT", "SGTU":
|
||||||
@@ -468,8 +543,45 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
copy(ops2[1:], ops[1:])
|
copy(ops2[1:], ops[1:])
|
||||||
ops = ops2
|
ops = ops2
|
||||||
}
|
}
|
||||||
|
// The toolchain's register-pair sugar: INSTR R4:R5, R6 encodes exactly
|
||||||
|
// as INSTR R5, R4, R6. The expansion is a fresh operand list; the
|
||||||
|
// instruction pointer stays the original one, because the PC-relative
|
||||||
|
// layout tables are keyed on it.
|
||||||
|
if pairOps, changed := l64ExpandPairs(ops); changed {
|
||||||
|
ops = pairOps
|
||||||
|
}
|
||||||
|
|
||||||
// Pseudo-instructions and the branches first.
|
// Pseudo-instructions and the branches first.
|
||||||
|
// Before any of them: the toolchain's loong64 operand grammar has no
|
||||||
|
// shifted-register composition (R0<<2, R0>>R1, R0->3, R0@>3 are parse
|
||||||
|
// errors under GOARCH=loong64 go tool asm), and the shift suffix the
|
||||||
|
// shared parser records is arm64's. Encoding on would silently drop
|
||||||
|
// the composition and emit the bare register, so every operand whose
|
||||||
|
// verbatim suffix is not the element-selector index (V1.B[3] records
|
||||||
|
// the name as V1.B and the suffix "[3]", which is a real loong64
|
||||||
|
// form) or the register-pair sugar expanded above is rejected
|
||||||
|
// outright, with the toolchain's own wording where the shape is one
|
||||||
|
// it diagnoses.
|
||||||
|
for _, op := range ops {
|
||||||
|
sfx := strings.Join(strings.Fields(op.Addr.Shift), "")
|
||||||
|
if sfx == "" || strings.HasPrefix(sfx, "[") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(sfx, ":") {
|
||||||
|
if op.Addr.Base != "" {
|
||||||
|
// (Rj:Rk) inside an address: the pair never splits there.
|
||||||
|
return nil, fmt.Errorf("%s: indirect through register pair", mnem)
|
||||||
|
}
|
||||||
|
// A register-pair spelling with a register right half was
|
||||||
|
// expanded above, so whatever survives carries a right half
|
||||||
|
// outside the register table: the toolchain's parse-stage
|
||||||
|
// objection (R4:label).
|
||||||
|
return nil, fmt.Errorf("%s: illegal or missing addressing mode for symbol %s",
|
||||||
|
mnem, strings.TrimPrefix(sfx, ":"))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s: shifted register operand %s%s is not a loong64 form",
|
||||||
|
mnem, operandRegName(op), sfx)
|
||||||
|
}
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "RET":
|
case "RET":
|
||||||
return loong64Return(fi), nil
|
return loong64Return(fi), nil
|
||||||
@@ -484,6 +596,18 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
|
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
|
||||||
}
|
}
|
||||||
return l64wordLE(uint32(immFromOperand(ops[0]))), nil
|
return l64wordLE(uint32(immFromOperand(ops[0]))), nil
|
||||||
|
case "BYTE":
|
||||||
|
// BYTE $b lays down one raw byte per operand, the same front-end
|
||||||
|
// pseudo-op the arm64 and riscv64 encoders accept.
|
||||||
|
var out []byte
|
||||||
|
for _, op := range ops {
|
||||||
|
b := l64Imm64(op)
|
||||||
|
if b < 0 || b > 0xFF {
|
||||||
|
return nil, fmt.Errorf("BYTE: immediate %d does not fit a byte", b)
|
||||||
|
}
|
||||||
|
out = append(out, byte(b))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
case "END", "FUNCDATA", "PCDATA", "GETCALLERPC":
|
case "END", "FUNCDATA", "PCDATA", "GETCALLERPC":
|
||||||
// The assembler's bookkeeping statements. END, FUNCDATA and PCDATA
|
// The assembler's bookkeeping statements. END, FUNCDATA and PCDATA
|
||||||
// contribute no bytes, the same shapes GOARCH=loong64 go tool asm
|
// contribute no bytes, the same shapes GOARCH=loong64 go tool asm
|
||||||
@@ -585,11 +709,11 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
l64rrr(preldx, 30, rj, hint),
|
l64rrr(preldx, 30, rj, hint),
|
||||||
), nil
|
), nil
|
||||||
case "JMP", "B":
|
case "JMP", "B":
|
||||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve, relocs, pcRelPcs)
|
return encodeLOONG64Branch(instr, ops, mnem, pc, offsets, false, resolve, relocs, pcRelPcs)
|
||||||
case "JAL", "CALL", "BL":
|
case "JAL", "CALL", "BL":
|
||||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve, relocs, pcRelPcs)
|
return encodeLOONG64Branch(instr, ops, mnem, pc, offsets, true, resolve, relocs, pcRelPcs)
|
||||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||||
return encodeLOONG64Mov(instr, mnem, fi, relocs)
|
return encodeLOONG64Mov(ops, mnem, fi, relocs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16-bit branches (BEQ/BNE/BLT/BGE/BLTU/BGEU) and JIRL.
|
// 16-bit branches (BEQ/BNE/BLT/BGE/BLTU/BGEU) and JIRL.
|
||||||
@@ -659,7 +783,7 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
// The LSX/LASX vector slice and the VMOVQ/XVMOVQ move family, before
|
// The LSX/LASX vector slice and the VMOVQ/XVMOVQ move family, before
|
||||||
// the integer/FP table (their mnemonics overlap the table's 2R format
|
// the integer/FP table (their mnemonics overlap the table's 2R format
|
||||||
// but resolve vector-bank registers).
|
// but resolve vector-bank registers).
|
||||||
if code, handled, err := encodeLOONG64Vector(instr, mnem, fi); handled {
|
if code, handled, err := encodeLOONG64Vector(mnem, ops, fi); handled {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -701,6 +825,35 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
}
|
}
|
||||||
return l64wordLE(l64rr(enc.op, rj, rd)), nil
|
return l64wordLE(l64rr(enc.op, rj, rd)), nil
|
||||||
|
|
||||||
|
case l64Fllsc:
|
||||||
|
// LLACQ{W,V} (Rj), Rd loads and SCREL{W,V} Rd, (Rj) stores, both
|
||||||
|
// 2R encodings op | rj<<5 | rd against a zero-offset memory operand
|
||||||
|
// (the toolchain's C_ZOREG, which rejects any displacement).
|
||||||
|
rd, rj, off, _, err := l64MemOperands(ops, fi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", mnem, err)
|
||||||
|
}
|
||||||
|
if off != 0 {
|
||||||
|
return nil, fmt.Errorf("%s: only a zero-offset memory operand is allowed", mnem)
|
||||||
|
}
|
||||||
|
return l64wordLE(l64rr(enc.op, rj, rd)), nil
|
||||||
|
|
||||||
|
case l64Fscq:
|
||||||
|
// SCQ first, middle, (base): op | middle<<10 | base<<5 | first,
|
||||||
|
// against a zero-offset memory operand as with the LL/SC pair.
|
||||||
|
if len(ops) != 3 || !isMemOperand(ops[2]) || isMemOperand(ops[0]) || isMemOperand(ops[1]) {
|
||||||
|
return nil, fmt.Errorf("%s expects reg, reg, (reg)", mnem)
|
||||||
|
}
|
||||||
|
first, middle := l64Reg(ops[0]), l64Reg(ops[1])
|
||||||
|
rj, off := l64MemWithFrame(ops[2], fi)
|
||||||
|
if first < 0 || middle < 0 || rj < 0 {
|
||||||
|
return nil, fmt.Errorf("%s: invalid register operand", mnem)
|
||||||
|
}
|
||||||
|
if off != 0 {
|
||||||
|
return nil, fmt.Errorf("%s: only a zero-offset memory operand is allowed", mnem)
|
||||||
|
}
|
||||||
|
return l64wordLE(l64rrr(enc.op, middle, rj, first)), nil
|
||||||
|
|
||||||
case l64Firr:
|
case l64Firr:
|
||||||
// LU52ID: INSTR $imm, rd or INSTR $imm, rj, rd.
|
// LU52ID: INSTR $imm, rd or INSTR $imm, rj, rd.
|
||||||
if len(ops) < 2 || !isImmOperand(ops[0]) {
|
if len(ops) < 2 || !isImmOperand(ops[0]) {
|
||||||
@@ -719,7 +872,10 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
|
|
||||||
case l64Firr16:
|
case l64Firr16:
|
||||||
// ADDV16: INSTR $imm, rd or INSTR $imm, rj, rd; the immediate must be
|
// ADDV16: INSTR $imm, rd or INSTR $imm, rj, rd; the immediate must be
|
||||||
// a multiple of 65536 and is shifted right by 16.
|
// a multiple of 65536 and is shifted right by 16. Both registers are
|
||||||
|
// general: the toolchain's class match refuses an F, V or X bank
|
||||||
|
// register here ("illegal combination ADDV16 ... FREG ..."), and the
|
||||||
|
// 5-bit numbering would otherwise encode it silently.
|
||||||
if len(ops) < 2 || !isImmOperand(ops[0]) {
|
if len(ops) < 2 || !isImmOperand(ops[0]) {
|
||||||
return nil, fmt.Errorf("%s expects an immediate operand", mnem)
|
return nil, fmt.Errorf("%s expects an immediate operand", mnem)
|
||||||
}
|
}
|
||||||
@@ -727,6 +883,11 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
if v&0xFFFF != 0 {
|
if v&0xFFFF != 0 {
|
||||||
return nil, fmt.Errorf("%s: the constant must be a multiple of 65536", mnem)
|
return nil, fmt.Errorf("%s: the constant must be a multiple of 65536", mnem)
|
||||||
}
|
}
|
||||||
|
for _, op := range ops[1:] {
|
||||||
|
if c := loong64RegClass(operandRegName(op)); c != l64ClsGR {
|
||||||
|
return nil, fmt.Errorf("%s: illegal combination: the registers must be general", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
rd := l64Reg(ops[len(ops)-1])
|
rd := l64Reg(ops[len(ops)-1])
|
||||||
rj := rd
|
rj := rd
|
||||||
if len(ops) == 3 {
|
if len(ops) == 3 {
|
||||||
@@ -749,7 +910,26 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
// ldptr.{w,d} = stptr.{w,d} minus the LSB of the opcode field.
|
// ldptr.{w,d} = stptr.{w,d} minus the LSB of the opcode field.
|
||||||
op -= 1 << 24
|
op -= 1 << 24
|
||||||
}
|
}
|
||||||
return l64wordLE(l64irr14(op, int(off)>>2, rj, rd)), nil
|
if off&3 != 0 {
|
||||||
|
return nil, fmt.Errorf("%s: offset must be a multiple of 4", mnem)
|
||||||
|
}
|
||||||
|
// The 64-bit span reproduces the toolchain's case 73/74 size-24
|
||||||
|
// expansion word for word, its missing load negation included: a
|
||||||
|
// MOVWP/MOVVP load emits the store opcode, because case 74 spells
|
||||||
|
// opirr(p.As) where the narrower spans spell opirr(-p.As), and the
|
||||||
|
// LL family has no unnegated opirr entry at all, so the toolchain
|
||||||
|
// refuses those loads with "bad irr opcode".
|
||||||
|
if off < -2147483650 || off >= 2147483646 {
|
||||||
|
if load {
|
||||||
|
switch mnem {
|
||||||
|
case "MOVWP", "MOVVP":
|
||||||
|
op += 1 << 24
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("bad irr opcode %s", mnem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l64firr14Words(op, int(off), rj, rd), nil
|
||||||
|
|
||||||
case l64Fir20:
|
case l64Fir20:
|
||||||
// LU12IW/LU32ID/PCALAU12I/PCADDU12I: INSTR rd, $imm.
|
// LU12IW/LU32ID/PCALAU12I/PCADDU12I: INSTR rd, $imm.
|
||||||
@@ -868,11 +1048,14 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
//
|
//
|
||||||
// JMP/B label → b label JMP/B (rj) → jirl r0, rj, 0
|
// JMP/B label → b label JMP/B (rj) → jirl r0, rj, 0
|
||||||
// JAL/CALL/BL label → bl label JAL/CALL/BL (rj) → jirl r1, rj, 0
|
// JAL/CALL/BL label → bl label JAL/CALL/BL (rj) → jirl r1, rj, 0
|
||||||
func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
//
|
||||||
if len(instr.Operands) != 1 {
|
// instr is the original instruction, the key the PC-relative layout table
|
||||||
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(instr.Operands))
|
// is keyed on; ops are the operands to encode, the pair expansion included.
|
||||||
|
func encodeLOONG64Branch(instr *ast.Instr, ops []*ast.Operand, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||||
|
if len(ops) != 1 {
|
||||||
|
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
op := instr.Operands[0]
|
op := ops[0]
|
||||||
// PC-relative displacement: N(PC) resolves to the instruction N slots
|
// PC-relative displacement: N(PC) resolves to the instruction N slots
|
||||||
// away in source order (the toolchain's parse-time count), and the field
|
// away in source order (the toolchain's parse-time count), and the field
|
||||||
// carries the final pc distance in instruction units.
|
// carries the final pc distance in instruction units.
|
||||||
@@ -1107,8 +1290,8 @@ func encodeLOONG64Branch21(instr *ast.Instr, mnem string, op uint32, ops []*ast.
|
|||||||
//
|
//
|
||||||
// ADD/SGT family: −2048..0x7ff → addi/slti directly (4 bytes)
|
// ADD/SGT family: −2048..0x7ff → addi/slti directly (4 bytes)
|
||||||
// 0x800..0xfff → ori r30, r0, v; op rd, rj, r30 (8)
|
// 0x800..0xfff → ori r30, r0, v; op rd, rj, r30 (8)
|
||||||
// AND/OR/XOR: 0..0x7ff → andi/ori/xori directly (4)
|
// AND/OR/XOR: 0..0xfff → andi/ori/xori directly (4)
|
||||||
// −2048..−1 → addi.d r30, r0, v; op rd, rj, r30 (8)
|
// −2048..−1 → addi.w r30, r0, v; op rd, rj, r30 (8)
|
||||||
// 32-bit: lu12i.w r30, v>>12 [; ori r30, r30, v]; op (8/12)
|
// 32-bit: lu12i.w r30, v>>12 [; ori r30, r30, v]; op (8/12)
|
||||||
// 64-bit: lu12i.w + ori + lu32i.d + lu52i.d + op (20)
|
// 64-bit: lu12i.w + ori + lu32i.d + lu52i.d + op (20)
|
||||||
func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]byte, error) {
|
func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]byte, error) {
|
||||||
@@ -1146,12 +1329,18 @@ func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]by
|
|||||||
if v == 0 {
|
if v == 0 {
|
||||||
return l64wordLE(l64rrr(de.rrr, 0, rj, rd)), nil
|
return l64wordLE(l64rrr(de.rrr, 0, rj, rd)), nil
|
||||||
}
|
}
|
||||||
if v >= 0 && v <= 0x7ff {
|
if v >= 0 && v <= 0xfff {
|
||||||
|
// andi/ori/xori take the full unsigned 12-bit range, the
|
||||||
|
// toolchain's C_UU12CON class.
|
||||||
return l64wordLE(l64irr(de.imm, int(v), rj, rd)), nil
|
return l64wordLE(l64irr(de.imm, int(v), rj, rd)), nil
|
||||||
}
|
}
|
||||||
if v >= -2048 && v < 0 {
|
if v >= -2048 && v < 0 {
|
||||||
|
// The toolchain's case 10 loads the negative constant with
|
||||||
|
// addi.w (opirr(AADD)), whose 32-bit result zero-extends into
|
||||||
|
// the register; addi.d here would sign-extend instead and the
|
||||||
|
// 64-bit AND/OR/XOR would keep bits the toolchain clears.
|
||||||
return l64WordsLE(
|
return l64WordsLE(
|
||||||
l64irr(0x00b<<22, int(v), 0, 30), // addi.d r30, r0, v
|
l64irr(0x00a<<22, int(v), 0, 30), // addi.w r30, r0, v
|
||||||
l64rrr(de.rrr, 30, rj, rd),
|
l64rrr(de.rrr, 30, rj, rd),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
@@ -1209,26 +1398,61 @@ func l64FmaOperands(ops []*ast.Operand) (fa, fk, fj, fd int, err error) {
|
|||||||
|
|
||||||
// l64MemOperands extracts (rd, rj, off, load) from a load/store instruction:
|
// l64MemOperands extracts (rd, rj, off, load) from a load/store instruction:
|
||||||
// INSTR mem, rd is a load, INSTR rd, mem a store.
|
// INSTR mem, rd is a load, INSTR rd, mem a store.
|
||||||
func l64MemOperands(ops []*ast.Operand, fi loong64FrameInfo) (rd, rj int, off int32, load bool, err error) {
|
// l64MemOperands splits the two-operand load/store forms: the register and
|
||||||
|
// the memory side, the memory's base and full-width byte offset (64-bit
|
||||||
|
// offsets reach the 2RI14 families' six-word span), and the direction.
|
||||||
|
func l64MemOperands(ops []*ast.Operand, fi loong64FrameInfo) (rd, rj int, off int64, load bool, err error) {
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return 0, 0, 0, false, fmt.Errorf("expected 2 operands, got %d", len(ops))
|
return 0, 0, 0, false, fmt.Errorf("expected 2 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
|
var mem *ast.Operand
|
||||||
if isMemOperand(ops[0]) {
|
if isMemOperand(ops[0]) {
|
||||||
rd = l64Reg(ops[1])
|
rd = l64Reg(ops[1])
|
||||||
rj, off = l64MemWithFrame(ops[0], fi)
|
mem = ops[0]
|
||||||
load = true
|
load = true
|
||||||
} else if isMemOperand(ops[1]) {
|
} else if isMemOperand(ops[1]) {
|
||||||
rd = l64Reg(ops[0])
|
rd = l64Reg(ops[0])
|
||||||
rj, off = l64MemWithFrame(ops[1], fi)
|
mem = ops[1]
|
||||||
} else {
|
} else {
|
||||||
return 0, 0, 0, false, fmt.Errorf("expected a memory operand")
|
return 0, 0, 0, false, fmt.Errorf("expected a memory operand")
|
||||||
}
|
}
|
||||||
|
if mem.Addr.Sym != nil && mem.Addr.Sym.Pseudo != "" {
|
||||||
|
pj, poff := loong64ResolvePseudo(mem.Addr.Sym, fi)
|
||||||
|
rj, off = pj, int64(poff)
|
||||||
|
} else {
|
||||||
|
rj, off = loong64RegNum(mem.Addr.Base), mem.Addr.Offset
|
||||||
|
}
|
||||||
if rd < 0 || rj < 0 {
|
if rd < 0 || rj < 0 {
|
||||||
return 0, 0, 0, false, fmt.Errorf("invalid operand")
|
return 0, 0, 0, false, fmt.Errorf("invalid operand")
|
||||||
}
|
}
|
||||||
return rd, rj, off, load, nil
|
return rd, rj, off, load, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64ImmMem reads the `$off(rj)` immediate form off an operand's raw text:
|
||||||
|
// the shared immediate parse reduces it to the bare number and keeps only
|
||||||
|
// the text as a witness of the base register. ok reports the form was
|
||||||
|
// found, with the base's register number (or -1 when the name is not a
|
||||||
|
// general register).
|
||||||
|
func l64ImmMem(op *ast.Operand) (off int32, base int, ok bool) {
|
||||||
|
if op.Kind != ast.OpImmediate || !op.Imm.HasVal {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
raw := strings.ReplaceAll(op.Raw, " ", "")
|
||||||
|
if !strings.HasPrefix(raw, "$") || !strings.HasSuffix(raw, ")") {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
open := strings.LastIndexByte(raw, '(')
|
||||||
|
if open < 2 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
base = loong64RegNum(raw[open+1 : len(raw)-1])
|
||||||
|
v := op.Imm.Val
|
||||||
|
if op.Imm.Neg {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
return int32(v), base, base >= 0
|
||||||
|
}
|
||||||
|
|
||||||
// ---- the MOV pseudo-instruction ----
|
// ---- the MOV pseudo-instruction ----
|
||||||
|
|
||||||
// encodeLOONG64Mov encodes the MOV family, the load/store/immediate
|
// encodeLOONG64Mov encodes the MOV family, the load/store/immediate
|
||||||
@@ -1243,8 +1467,7 @@ func l64MemOperands(ops []*ast.Operand, fi loong64FrameInfo) (rd, rj int, off in
|
|||||||
// MOVx $sym(SB), rd address of a static symbol (pcalau12i+addi.d)
|
// MOVx $sym(SB), rd address of a static symbol (pcalau12i+addi.d)
|
||||||
// MOVx sym(SB), rd load from a static symbol (pcalau12i+ld)
|
// MOVx sym(SB), rd load from a static symbol (pcalau12i+ld)
|
||||||
// MOVx rd, sym(SB) store to a static symbol (pcalau12i+st)
|
// MOVx rd, sym(SB) store to a static symbol (pcalau12i+st)
|
||||||
func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
|
func encodeLOONG64Mov(ops []*ast.Operand, mnem string, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
|
||||||
ops := instr.Operands
|
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1485,29 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs
|
|||||||
}
|
}
|
||||||
return encodeLOONG64SBAddr(src.Imm.Sym, rd, relocs), nil
|
return encodeLOONG64SBAddr(src.Imm.Sym, rd, relocs), nil
|
||||||
}
|
}
|
||||||
|
// MOVx $off(rj), rd computes an address: the toolchain's `mov
|
||||||
|
// $soreg, r` case, a plain addi.d whatever the move's width (both
|
||||||
|
// MOVW and MOVV $4(R4), R5 encode the same addi.d in its testdata).
|
||||||
|
// A wider offset materialises in R30 first (lu12i.w + ori + add.d,
|
||||||
|
// its case 10). The immediate's Raw carries the base register,
|
||||||
|
// which the shared immediate parse reduces to the bare number.
|
||||||
|
if off, base, ok := l64ImmMem(src); ok {
|
||||||
|
rd := l64Reg(dst)
|
||||||
|
if rd < 0 {
|
||||||
|
return nil, fmt.Errorf("%s $imm(rj): invalid destination register", mnem)
|
||||||
|
}
|
||||||
|
if loong64RegClass(operandRegName(dst)) == l64ClsFP {
|
||||||
|
return nil, fmt.Errorf("%s $imm(rj): illegal combination with an F register destination", mnem)
|
||||||
|
}
|
||||||
|
if off >= -2048 && off <= 2047 {
|
||||||
|
return l64wordLE(l64irr(l64DualTable["ADDV"].imm, int(off), base, rd)), nil
|
||||||
|
}
|
||||||
|
return l64WordsLE(
|
||||||
|
l64ir(l64InstrTable["LU12IW"].op, int(off)>>12, 30),
|
||||||
|
l64irr(l64DualTable["OR"].imm, int(off)&0xFFF, 30, 30),
|
||||||
|
l64rrr(l64DualTable["ADDV"].rrr, 30, base, rd),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
rd := l64Reg(dst)
|
rd := l64Reg(dst)
|
||||||
if rd < 0 {
|
if rd < 0 {
|
||||||
return nil, fmt.Errorf("%s $imm: invalid destination register", mnem)
|
return nil, fmt.Errorf("%s $imm: invalid destination register", mnem)
|
||||||
@@ -1298,6 +1544,11 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs
|
|||||||
|
|
||||||
// Register-offset addressing: MOVx (rj)(rk), rd / MOVx rd, (rj)(rk).
|
// Register-offset addressing: MOVx (rj)(rk), rd / MOVx rd, (rj)(rk).
|
||||||
if src.Addr.Index != "" && !isMemOperand(dst) {
|
if src.Addr.Index != "" && !isMemOperand(dst) {
|
||||||
|
if src.Addr.HasOff {
|
||||||
|
// The toolchain rejects off(rj)(rk) with an illegal
|
||||||
|
// combination; the offset would be silently dropped here.
|
||||||
|
return nil, fmt.Errorf("%s: the register-indexed form takes no offset", mnem)
|
||||||
|
}
|
||||||
rd := l64Reg(dst)
|
rd := l64Reg(dst)
|
||||||
rj, rk := loong64RegNum(src.Addr.Base), loong64RegNum(src.Addr.Index)
|
rj, rk := loong64RegNum(src.Addr.Base), loong64RegNum(src.Addr.Index)
|
||||||
if rd < 0 || rj < 0 || rk < 0 {
|
if rd < 0 || rj < 0 || rk < 0 {
|
||||||
@@ -1310,6 +1561,10 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs
|
|||||||
return l64wordLE(l64rrr(op.ld, rk, rj, rd)), nil
|
return l64wordLE(l64rrr(op.ld, rk, rj, rd)), nil
|
||||||
}
|
}
|
||||||
if dst.Addr.Index != "" && !isMemOperand(src) {
|
if dst.Addr.Index != "" && !isMemOperand(src) {
|
||||||
|
if dst.Addr.HasOff {
|
||||||
|
// The store twin of the load rejection above.
|
||||||
|
return nil, fmt.Errorf("%s: the register-indexed form takes no offset", mnem)
|
||||||
|
}
|
||||||
rs := l64Reg(src)
|
rs := l64Reg(src)
|
||||||
rj, rk := loong64RegNum(dst.Addr.Base), loong64RegNum(dst.Addr.Index)
|
rj, rk := loong64RegNum(dst.Addr.Base), loong64RegNum(dst.Addr.Index)
|
||||||
if rs < 0 || rj < 0 || rk < 0 {
|
if rs < 0 || rj < 0 || rk < 0 {
|
||||||
@@ -1356,6 +1611,14 @@ func loong64MovSize(mnem string, ops []*ast.Operand, fi loong64FrameInfo) int {
|
|||||||
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
||||||
return 8 // pcalau12i + addi.d
|
return 8 // pcalau12i + addi.d
|
||||||
}
|
}
|
||||||
|
// The $off(rj) address immediate: addi.d in the 12-bit window,
|
||||||
|
// lu12i.w + ori + add.d beyond it (the toolchain's case 10).
|
||||||
|
if off, _, ok := l64ImmMem(src); ok {
|
||||||
|
if off >= -2048 && off <= 2047 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 12
|
||||||
|
}
|
||||||
if loong64RegClass(operandRegName(dst)) == l64ClsFP {
|
if loong64RegClass(operandRegName(dst)) == l64ClsFP {
|
||||||
return 8 // ori/addi.w r30 + movgr2fr.w (an encode-time diagnostic when invalid)
|
return 8 // ori/addi.w r30 + movgr2fr.w (an encode-time diagnostic when invalid)
|
||||||
}
|
}
|
||||||
@@ -1835,8 +2098,14 @@ func operandRegName(op *ast.Operand) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// l64Reg returns the register number of an operand, or -1.
|
// l64Reg returns the register number of a register operand, or -1. A
|
||||||
|
// memory reference is not a register, however register-shaped its base:
|
||||||
|
// the toolchain's class match refuses one wherever a C_REG is required,
|
||||||
|
// and reading the base's number here would encode it silently.
|
||||||
func l64Reg(op *ast.Operand) int {
|
func l64Reg(op *ast.Operand) int {
|
||||||
|
if isMemOperand(op) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
return loong64RegNum(operandRegName(op))
|
return loong64RegNum(operandRegName(op))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1868,6 +2137,19 @@ func l64MemWithFrame(op *ast.Operand, fi loong64FrameInfo) (rj int, off int32) {
|
|||||||
return l64Mem(op)
|
return l64Mem(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64VmovqMem resolves a VMOVQ/XVMOVQ memory operand. The toolchain's
|
||||||
|
// vector table falls back to the zero register as the FP-relative base
|
||||||
|
// (`VMOVQ V2, y+16(FP)` stores through R0 while MOVW reads the same operand
|
||||||
|
// through R3), so the vector moves keep the resolved offset but the zero
|
||||||
|
// base, exactly as `go tool asm` emits them.
|
||||||
|
func l64VmovqMem(op *ast.Operand, fi loong64FrameInfo) (rj int, off int32) {
|
||||||
|
rj, off = l64MemWithFrame(op, fi)
|
||||||
|
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "FP" {
|
||||||
|
rj = 0
|
||||||
|
}
|
||||||
|
return rj, off
|
||||||
|
}
|
||||||
|
|
||||||
// l64MemOffset returns the resolved byte offset of a memory operand.
|
// l64MemOffset returns the resolved byte offset of a memory operand.
|
||||||
func l64MemOffset(op *ast.Operand, fi loong64FrameInfo) int32 {
|
func l64MemOffset(op *ast.Operand, fi loong64FrameInfo) int32 {
|
||||||
_, off := l64MemWithFrame(op, fi)
|
_, off := l64MemWithFrame(op, fi)
|
||||||
@@ -1892,7 +2174,7 @@ func l64Label(op *ast.Operand) string {
|
|||||||
type l64VecOperand struct {
|
type l64VecOperand struct {
|
||||||
num int // 5-bit register number
|
num int // 5-bit register number
|
||||||
lasx bool // X bank (LASX) rather than V (LSX)
|
lasx bool // X bank (LASX) rather than V (LSX)
|
||||||
width byte // suffix width letter (B/H/W/V), 0 on a bare register
|
width byte // suffix width letter (B/H/W/V/Q), 0 on a bare register
|
||||||
lanes int // lane count of a width suffix (B16 → 16)
|
lanes int // lane count of a width suffix (B16 → 16)
|
||||||
elem int // element index of a .T[i] suffix
|
elem int // element index of a .T[i] suffix
|
||||||
hasEl bool // the suffix names an element (.T[i])
|
hasEl bool // the suffix names an element (.T[i])
|
||||||
@@ -1932,7 +2214,7 @@ func l64ParseVecOperand(op *ast.Operand) (v l64VecOperand, ok bool) {
|
|||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
w := name[i]
|
w := name[i]
|
||||||
if w != 'B' && w != 'H' && w != 'W' && w != 'V' {
|
if w != 'B' && w != 'H' && w != 'W' && w != 'V' && w != 'Q' {
|
||||||
return v, false
|
return v, false
|
||||||
}
|
}
|
||||||
v.width, v.hasSuf = w, true
|
v.width, v.hasSuf = w, true
|
||||||
@@ -2042,16 +2324,15 @@ func l64VecElementBase(lasx bool, v l64VecOperand) (int, bool) {
|
|||||||
// vector plus the VMOVQ/XVMOVQ move family. handled reports whether the
|
// vector plus the VMOVQ/XVMOVQ move family. handled reports whether the
|
||||||
// mnemonic belongs to the vector slice; the operand shapes and opcode
|
// mnemonic belongs to the vector slice; the operand shapes and opcode
|
||||||
// constants reproduce GOARCH=loong64 `go tool asm` exactly.
|
// constants reproduce GOARCH=loong64 `go tool asm` exactly.
|
||||||
func encodeLOONG64Vector(instr *ast.Instr, mnem string, fi loong64FrameInfo) ([]byte, bool, error) {
|
func encodeLOONG64Vector(mnem string, ops []*ast.Operand, fi loong64FrameInfo) ([]byte, bool, error) {
|
||||||
if mnem == "VMOVQ" || mnem == "XVMOVQ" {
|
if mnem == "VMOVQ" || mnem == "XVMOVQ" {
|
||||||
code, err := encodeLOONG64Vmovq(mnem == "XVMOVQ", instr.Operands, fi)
|
code, err := encodeLOONG64Vmovq(mnem == "XVMOVQ", ops, fi)
|
||||||
return code, true, err
|
return code, true, err
|
||||||
}
|
}
|
||||||
lasx, ok := l64VecBank[mnem]
|
lasx, ok := l64VecBank[mnem]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
ops := instr.Operands
|
|
||||||
bank := "V"
|
bank := "V"
|
||||||
if lasx {
|
if lasx {
|
||||||
bank = "X"
|
bank = "X"
|
||||||
@@ -2174,6 +2455,10 @@ func encodeLOONG64Vector(instr *ast.Instr, mnem string, fi loong64FrameInfo) ([]
|
|||||||
// VMOVQ rj, vd.T vreplgr2vr (duplicate a general register)
|
// VMOVQ rj, vd.T vreplgr2vr (duplicate a general register)
|
||||||
// VMOVQ vj.T[i], rd vpickve2gr (extract one element)
|
// VMOVQ vj.T[i], rd vpickve2gr (extract one element)
|
||||||
// VMOVQ rj, vd.T[i] vinsgr2vr (insert one element)
|
// VMOVQ rj, vd.T[i] vinsgr2vr (insert one element)
|
||||||
|
// VMOVQ vj.T[i], vd.T vreplvei (broadcast one element, LSX)
|
||||||
|
// XVMOVQ xj, xd.T xvreplve0 (broadcast element zero, LASX)
|
||||||
|
// XVMOVQ xj, xd.T[i] xvinsve0 (insert element zero, LASX)
|
||||||
|
// XVMOVQ xj.T[i], xd xvpickve (extract one element, LASX)
|
||||||
func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]byte, error) {
|
func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]byte, error) {
|
||||||
enc := l64VmovqTable[lasx]
|
enc := l64VmovqTable[lasx]
|
||||||
bank := "V"
|
bank := "V"
|
||||||
@@ -2200,6 +2485,118 @@ func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]b
|
|||||||
return loong64RegNum(name), nil
|
return loong64RegNum(name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Element broadcast: VMOVQ vj.T[i], vd.T (vreplvei.{b,h,w,d}), the
|
||||||
|
// source element width matching the destination arrangement. An LSX-only
|
||||||
|
// form: the toolchain's table gives vreplvei no LASX counterpart.
|
||||||
|
if srcVec && dstVec && src.hasEl && dst.hasSuf && !dst.hasEl {
|
||||||
|
if lasx || src.lasx || dst.lasx {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: vreplvei has no %s-bank form", bank)
|
||||||
|
}
|
||||||
|
if src.unsig {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: vreplvei takes no unsigned element suffix")
|
||||||
|
}
|
||||||
|
if src.width != dst.width {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: element width does not match arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
if _, ok := l64VecSuffixWidth(false, dst); !ok {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
var op uint32
|
||||||
|
limit := 0
|
||||||
|
switch src.width {
|
||||||
|
case 'B':
|
||||||
|
op, limit = enc.rveiB, 15
|
||||||
|
case 'H':
|
||||||
|
op, limit = enc.rveiH, 7
|
||||||
|
case 'W':
|
||||||
|
op, limit = enc.rveiW, 3
|
||||||
|
default:
|
||||||
|
op, limit = enc.rveiD, 1
|
||||||
|
}
|
||||||
|
if src.elem > limit {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: element index %d out of range [0, %d]", src.elem, limit)
|
||||||
|
}
|
||||||
|
return l64wordLE(op | uint32(src.elem)<<10 | uint32(src.num)<<5 | uint32(dst.num)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast of element zero: XVMOVQ xj, xd.T (xvreplve0.{b,h,w,d,q}),
|
||||||
|
// a bare X source into an arranged X destination. LASX only.
|
||||||
|
if srcVec && dstVec && !src.hasSuf && dst.hasSuf && !dst.hasEl {
|
||||||
|
if !lasx || src.lasx != lasx || dst.lasx != lasx {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: xvreplve0 is the %s-bank form alone", bank)
|
||||||
|
}
|
||||||
|
var op uint32
|
||||||
|
switch dst.width {
|
||||||
|
case 'B':
|
||||||
|
if dst.lanes != 32 {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
op = enc.rve0B
|
||||||
|
case 'H':
|
||||||
|
if dst.lanes != 16 {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
op = enc.rve0H
|
||||||
|
case 'W':
|
||||||
|
if dst.lanes != 8 {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
op = enc.rve0W
|
||||||
|
case 'V':
|
||||||
|
if dst.lanes != 4 {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
op = enc.rve0D
|
||||||
|
case 'Q':
|
||||||
|
if dst.lanes != 2 {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
op = enc.rve0Q
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: invalid arrangement %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
return l64wordLE(op | uint32(src.num)<<5 | uint32(dst.num)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert of element zero: XVMOVQ xj, xd.T[i] (xvinsve0.{w,d}), a bare X
|
||||||
|
// source into one word or double-word lane. LASX only.
|
||||||
|
if srcVec && dstVec && !src.hasSuf && dst.hasEl {
|
||||||
|
if !lasx || src.lasx != lasx || dst.lasx != lasx {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: xvinsve0 is the %s-bank form alone", bank)
|
||||||
|
}
|
||||||
|
op, limit := enc.xinsW, 7
|
||||||
|
if dst.width != 'W' {
|
||||||
|
op, limit = enc.xinsD, 3
|
||||||
|
if dst.width != 'V' {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: xvinsve0 takes word or double-word lanes, got %q", ops[1].Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst.elem > limit {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: element index %d out of range [0, %d]", dst.elem, limit)
|
||||||
|
}
|
||||||
|
return l64wordLE(op | uint32(dst.elem)<<10 | uint32(src.num)<<5 | uint32(dst.num)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element extract into a vector register: XVMOVQ xj.T[i], xd
|
||||||
|
// (xvpickve.{w,d}), one word or double-word lane out to a bare X
|
||||||
|
// register. LASX only.
|
||||||
|
if srcVec && src.hasEl && dstVec && !dst.hasSuf {
|
||||||
|
if !lasx || src.lasx != lasx || dst.lasx != lasx {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: xvpickve is the %s-bank form alone", bank)
|
||||||
|
}
|
||||||
|
op, limit := enc.xpickW, 7
|
||||||
|
if src.width != 'W' {
|
||||||
|
op, limit = enc.xpickD, 3
|
||||||
|
if src.width != 'V' {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: xvpickve takes word or double-word lanes, got %q", ops[0].Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.elem > limit {
|
||||||
|
return nil, fmt.Errorf("XVMOVQ: element index %d out of range [0, %d]", src.elem, limit)
|
||||||
|
}
|
||||||
|
return l64wordLE(op | uint32(src.elem)<<10 | uint32(src.num)<<5 | uint32(dst.num)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Register move: VMOVQ vj, vd (vori.b/xvori.b with the zero constant),
|
// Register move: VMOVQ vj, vd (vori.b/xvori.b with the zero constant),
|
||||||
// both operands bare registers of the same bank.
|
// both operands bare registers of the same bank.
|
||||||
if srcVec && dstVec {
|
if srcVec && dstVec {
|
||||||
@@ -2224,7 +2621,7 @@ func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]b
|
|||||||
}
|
}
|
||||||
return l64wordLE(l64rrr(enc.stx, rk, rj, src.num)), nil
|
return l64wordLE(l64rrr(enc.stx, rk, rj, src.num)), nil
|
||||||
}
|
}
|
||||||
rj, off := l64MemWithFrame(ops[1], fi)
|
rj, off := l64VmovqMem(ops[1], fi)
|
||||||
if rj < 0 || off < -2048 || off > 2047 {
|
if rj < 0 || off < -2048 || off > 2047 {
|
||||||
return nil, fmt.Errorf("VMOVQ: store offset out of range [-2048, 2047]")
|
return nil, fmt.Errorf("VMOVQ: store offset out of range [-2048, 2047]")
|
||||||
}
|
}
|
||||||
@@ -2247,9 +2644,9 @@ func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]b
|
|||||||
}
|
}
|
||||||
return l64wordLE(l64rrr(enc.ldx, rk, rj, dst.num)), nil
|
return l64wordLE(l64rrr(enc.ldx, rk, rj, dst.num)), nil
|
||||||
}
|
}
|
||||||
rj, off := l64MemWithFrame(ops[0], fi)
|
rj, off := l64VmovqMem(ops[0], fi)
|
||||||
if rj < 0 || off < -2048 || off > 2047 {
|
if rj < 0 {
|
||||||
return nil, fmt.Errorf("VMOVQ: load offset out of range [-2048, 2047]")
|
return nil, fmt.Errorf("VMOVQ: invalid load operand")
|
||||||
}
|
}
|
||||||
op := enc.ld
|
op := enc.ld
|
||||||
if dst.hasSuf {
|
if dst.hasSuf {
|
||||||
@@ -2257,16 +2654,34 @@ func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]b
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("VMOVQ: invalid replicate width suffix %q", ops[1].Raw)
|
return nil, fmt.Errorf("VMOVQ: invalid replicate width suffix %q", ops[1].Raw)
|
||||||
}
|
}
|
||||||
|
// vldrepl keeps the byte offset raw for bytes and scales it by
|
||||||
|
// the element width for the wider forms, the immediate field
|
||||||
|
// shrinking a bit per scale exactly as the toolchain encodes it
|
||||||
|
// (the field mask keeps the two's complement inside its width).
|
||||||
|
scale, mask, lo, hi := 1, int32(0xFFF), -2048, 2047
|
||||||
switch w {
|
switch w {
|
||||||
case 0:
|
case 0:
|
||||||
op = enc.replB
|
op = enc.replB
|
||||||
case 1:
|
case 1:
|
||||||
op = enc.replH
|
op = enc.replH
|
||||||
|
scale, mask, lo, hi = 2, 0x7FF, -1024, 1023
|
||||||
case 2:
|
case 2:
|
||||||
op = enc.replW
|
op = enc.replW
|
||||||
|
scale, mask, lo, hi = 4, 0x3FF, -512, 511
|
||||||
default:
|
default:
|
||||||
op = enc.replD
|
op = enc.replD
|
||||||
|
scale, mask, lo, hi = 8, 0x1FF, -256, 255
|
||||||
}
|
}
|
||||||
|
if off%int32(scale) != 0 {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: offset %d must be a multiple of %d", off, scale)
|
||||||
|
}
|
||||||
|
off /= int32(scale)
|
||||||
|
if off < int32(lo) || off > int32(hi) {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: offset out of range [%d, %d]", lo*scale, hi*scale)
|
||||||
|
}
|
||||||
|
off &= mask
|
||||||
|
} else if off < -2048 || off > 2047 {
|
||||||
|
return nil, fmt.Errorf("VMOVQ: load offset out of range [-2048, 2047]")
|
||||||
}
|
}
|
||||||
return l64wordLE(l64irr(op, int(off), rj, dst.num)), nil
|
return l64wordLE(l64irr(op, int(off), rj, dst.num)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-5
@@ -257,6 +257,37 @@ func l64WordsLE(ws ...uint32) []byte {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64firr14Words returns the words the 2RI14 families (LL/LLW/LLV, SC/SCW/
|
||||||
|
// SCV, MOVWP/MOVVP) encode to for a 4-aligned byte offset. The toolchain
|
||||||
|
// classifies the memory operand by span, with the class constants' -2 slop
|
||||||
|
// included verbatim: a signed 16-bit offset (C_SOREG_16) rides in si14 alone;
|
||||||
|
// a 32-bit one (C_LOREG_32) splits between addu16i.d (bits 31:16, materialised
|
||||||
|
// in R30, the assembler temp) and si14 (bits 15:2); anything wider
|
||||||
|
// (C_LOREG_64) builds the whole constant in R30 through lu12i.w + ori +
|
||||||
|
// lu32i.d + lu52i.d and folds the base into it. The caller adjusts the
|
||||||
|
// opcode for the 64-bit span's load-direction quirks before arriving here.
|
||||||
|
func l64firr14Words(op uint32, off, rj, rd int) []byte {
|
||||||
|
switch {
|
||||||
|
case off >= -32766 && off < 32766:
|
||||||
|
return l64wordLE(l64irr14(op, off>>2, rj, rd))
|
||||||
|
case off >= -2147483650 && off < 2147483646:
|
||||||
|
return l64WordsLE(
|
||||||
|
l64irr16(l64InstrTable["ADDV16"].op, off>>16, 0, 30),
|
||||||
|
l64rrr(l64DualTable["ADDV"].rrr, rj, 30, 30),
|
||||||
|
l64irr14(op, off>>2, 30, rd),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return l64WordsLE(
|
||||||
|
l64ir(l64InstrTable["LU12IW"].op, off>>12, 30),
|
||||||
|
l64irr(l64DualTable["OR"].imm, off&0xFFF, 30, 30),
|
||||||
|
l64ir(l64InstrTable["LU32ID"].op, off>>32, 30),
|
||||||
|
l64irr(l64InstrTable["LU52ID"].op, off>>52, 30, 30),
|
||||||
|
l64rrr(l64DualTable["ADDV"].rrr, 30, rj, rj),
|
||||||
|
l64irr14(op, 0, rj, rd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- instruction formats ----
|
// ---- instruction formats ----
|
||||||
|
|
||||||
type l64Format uint8
|
type l64Format uint8
|
||||||
@@ -279,6 +310,8 @@ const (
|
|||||||
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
|
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
|
||||||
l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc
|
l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc
|
||||||
l64Fvvvv // 4R vector shuffle: op | va<<15 | vk<<10 | vj<<5 | vd
|
l64Fvvvv // 4R vector shuffle: op | va<<15 | vk<<10 | vj<<5 | vd
|
||||||
|
l64Fllsc // acquire/release LL/SC (2R against a zero-offset memory operand)
|
||||||
|
l64Fscq // sc.q: op | middle<<10 | base<<5 | first against a zero-offset memory operand
|
||||||
)
|
)
|
||||||
|
|
||||||
// l64Enc is one instruction's encoding: its bit layout (format) and the
|
// l64Enc is one instruction's encoding: its bit layout (format) and the
|
||||||
@@ -341,8 +374,9 @@ var l64Vec2R = map[string]bool{}
|
|||||||
// such as vshuf.b).
|
// such as vshuf.b).
|
||||||
var l64Vec4R = map[string]bool{}
|
var l64Vec4R = map[string]bool{}
|
||||||
|
|
||||||
// l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants (pre-shifted to bit
|
// l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants, each pre-shifted to
|
||||||
// 15), read off `go tool objdump` of GOARCH=loong64 `go tool asm` kernels.
|
// its exact bit range, read off `go tool objdump` of GOARCH=loong64
|
||||||
|
// `go tool asm` kernels and the toolchain's specialLsxMovInst table.
|
||||||
type l64VmovqEnc struct {
|
type l64VmovqEnc struct {
|
||||||
ld, st, ldx, stx uint32 // plain and indexed load/store
|
ld, st, ldx, stx uint32 // plain and indexed load/store
|
||||||
replB, replH, replW, replD uint32 // vldrepl: load and replicate element
|
replB, replH, replW, replD uint32 // vldrepl: load and replicate element
|
||||||
@@ -350,6 +384,11 @@ type l64VmovqEnc struct {
|
|||||||
ins uint32 // vinsgr2vr element insert
|
ins uint32 // vinsgr2vr element insert
|
||||||
dup uint32 // vreplgr2vr duplicate (width in [11:10])
|
dup uint32 // vreplgr2vr duplicate (width in [11:10])
|
||||||
move uint32 // vori.b/xvori.b $0 register move
|
move uint32 // vori.b/xvori.b $0 register move
|
||||||
|
rveiB, rveiH, rveiW, rveiD uint32 // vreplvei: broadcast one element (LSX)
|
||||||
|
rve0B, rve0H, rve0W uint32 // xvreplve0 broadcast of element zero (LASX)
|
||||||
|
rve0D, rve0Q uint32 // xvreplve0.{d,q}, ditto
|
||||||
|
xinsW, xinsD uint32 // xvinsve0: insert element zero (LASX)
|
||||||
|
xpickW, xpickD uint32 // xvpickve: extract element (LASX)
|
||||||
}
|
}
|
||||||
|
|
||||||
var l64VmovqTable = map[bool]l64VmovqEnc{
|
var l64VmovqTable = map[bool]l64VmovqEnc{
|
||||||
@@ -358,12 +397,17 @@ var l64VmovqTable = map[bool]l64VmovqEnc{
|
|||||||
replB: 0x6100 << 15, replH: 0x6080 << 15, replW: 0x6040 << 15, replD: 0x6020 << 15,
|
replB: 0x6100 << 15, replH: 0x6080 << 15, replW: 0x6040 << 15, replD: 0x6020 << 15,
|
||||||
pickS: 0xE5DF << 15, pickU: 0xE5E7 << 15,
|
pickS: 0xE5DF << 15, pickU: 0xE5E7 << 15,
|
||||||
ins: 0xE5D7 << 15, dup: 0xE53E << 15, move: 0xE65A << 15,
|
ins: 0xE5D7 << 15, dup: 0xE53E << 15, move: 0xE65A << 15,
|
||||||
|
rveiB: 0x01CBDE << 14, rveiH: 0x0397BE << 13, rveiW: 0x072F7E << 12, rveiD: 0x0E5EFE << 11,
|
||||||
},
|
},
|
||||||
true: { // XVMOVQ, the LASX (X) bank
|
true: { // XVMOVQ, the LASX (X) bank
|
||||||
ld: 0x5900 << 15, st: 0x5980 << 15, ldx: 0x7090 << 15, stx: 0x7098 << 15,
|
ld: 0x5900 << 15, st: 0x5980 << 15, ldx: 0x7090 << 15, stx: 0x7098 << 15,
|
||||||
replB: 0x6500 << 15, replH: 0x6480 << 15, replW: 0x6440 << 15, replD: 0x6420 << 15,
|
replB: 0x6500 << 15, replH: 0x6480 << 15, replW: 0x6440 << 15, replD: 0x6420 << 15,
|
||||||
pickS: 0xEDDF << 15, pickU: 0xEDE7 << 15,
|
pickS: 0xEDDF << 15, pickU: 0xEDE7 << 15,
|
||||||
ins: 0xEDD7 << 15, dup: 0xED3E << 15, move: 0xEE5A << 15,
|
ins: 0xEDD7 << 15, dup: 0xED3E << 15, move: 0xEE5A << 15,
|
||||||
|
rve0B: 0x1DC1C0 << 10, rve0H: 0x1DC1E0 << 10, rve0W: 0x1DC1F0 << 10,
|
||||||
|
rve0D: 0x1DC1F8 << 10, rve0Q: 0x1DC1FC << 10,
|
||||||
|
xinsW: 0x03B7FE << 13, xinsD: 0x076FFE << 12,
|
||||||
|
xpickW: 0x03B81E << 13, xpickD: 0x07703E << 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +417,7 @@ func init() {
|
|||||||
"ADD": 0x20 << 15, "ADDW": 0x20 << 15, "ADDV": 0x21 << 15, "ADDVU": 0x21 << 15,
|
"ADD": 0x20 << 15, "ADDW": 0x20 << 15, "ADDV": 0x21 << 15, "ADDVU": 0x21 << 15,
|
||||||
"SUB": 0x22 << 15, "SUBW": 0x22 << 15, "SUBV": 0x23 << 15, "SUBVU": 0x23 << 15,
|
"SUB": 0x22 << 15, "SUBW": 0x22 << 15, "SUBV": 0x23 << 15, "SUBVU": 0x23 << 15,
|
||||||
"SGT": 0x24 << 15, "SGTU": 0x25 << 15,
|
"SGT": 0x24 << 15, "SGTU": 0x25 << 15,
|
||||||
"MASKEQZ": 0x26 << 15, "MASKNEZ": 0x27 << 15, "SCQ": 0x070AE << 15,
|
"MASKEQZ": 0x26 << 15, "MASKNEZ": 0x27 << 15,
|
||||||
"NOR": 0x28 << 15, "AND": 0x29 << 15, "OR": 0x2a << 15, "XOR": 0x2b << 15,
|
"NOR": 0x28 << 15, "AND": 0x29 << 15, "OR": 0x2a << 15, "XOR": 0x2b << 15,
|
||||||
"ORN": 0x2c << 15, "ANDN": 0x2d << 15,
|
"ORN": 0x2c << 15, "ANDN": 0x2d << 15,
|
||||||
"SLL": 0x2e << 15, "SRL": 0x2f << 15, "SRA": 0x30 << 15,
|
"SLL": 0x2e << 15, "SRL": 0x2f << 15, "SRA": 0x30 << 15,
|
||||||
@@ -467,6 +511,20 @@ func init() {
|
|||||||
l64InstrTable["RDTIMEHW"] = l64Enc{format: l64Frdtime, op: 0x19 << 10}
|
l64InstrTable["RDTIMEHW"] = l64Enc{format: l64Frdtime, op: 0x19 << 10}
|
||||||
l64InstrTable["RDTIMED"] = l64Enc{format: l64Frdtime, op: 0x1a << 10}
|
l64InstrTable["RDTIMED"] = l64Enc{format: l64Frdtime, op: 0x1a << 10}
|
||||||
|
|
||||||
|
// Acquire/release LL/SC (2R against a zero-offset memory operand):
|
||||||
|
// LLACQV (Rj), Rd loads, SCRELV Rd, (Rj) stores, both encoding
|
||||||
|
// op | rj<<5 | rd. Opcodes from cmd/internal/obj/loong64/instOp.go
|
||||||
|
// (ll.acq.{w,d}, sc.rel.{w,d}).
|
||||||
|
l64InstrTable["LLACQW"] = l64Enc{format: l64Fllsc, op: 0x0E15E0 << 10}
|
||||||
|
l64InstrTable["SCRELW"] = l64Enc{format: l64Fllsc, op: 0x0E15E1 << 10}
|
||||||
|
l64InstrTable["LLACQV"] = l64Enc{format: l64Fllsc, op: 0x0E15E2 << 10}
|
||||||
|
l64InstrTable["SCRELV"] = l64Enc{format: l64Fllsc, op: 0x0E15E3 << 10}
|
||||||
|
|
||||||
|
// SCQ (sc.q first, middle, (base)) keeps its own operand order: the
|
||||||
|
// encoding is op | middle<<10 | base<<5 | first, the memory operand's
|
||||||
|
// base in the rj field, not the toolchain's generic 3R layout.
|
||||||
|
l64InstrTable["SCQ"] = l64Enc{format: l64Fscq, op: 0x070AE << 15}
|
||||||
|
|
||||||
// The dual-form arithmetic mnemonics (register 3R + immediate 2RI12),
|
// The dual-form arithmetic mnemonics (register 3R + immediate 2RI12),
|
||||||
// selected by the operand kind; the shift mnemonics pair the 3R form
|
// selected by the operand kind; the shift mnemonics pair the 3R form
|
||||||
// with a 5/6-bit shift immediate.
|
// with a 5/6-bit shift immediate.
|
||||||
@@ -868,7 +926,7 @@ func init() {
|
|||||||
"VNORB": {0xE7B8 << 15, false, 0, 255, 0, 0xFF},
|
"VNORB": {0xE7B8 << 15, false, 0, 255, 0, 0xFF},
|
||||||
"XVNORB": {0xEFB8 << 15, true, 0, 255, 0, 0xFF},
|
"XVNORB": {0xEFB8 << 15, true, 0, 255, 0, 0xFF},
|
||||||
"VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F},
|
"VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F},
|
||||||
"XVSEQB": {0xE900 << 15, true, -16, 15, 0, 0x1F},
|
"XVSEQB": {0xED00 << 15, true, -16, 15, 0, 0x1F},
|
||||||
// vseqi.h/w accept the same si5 window as vseqi.b; vseqi.d carries a
|
// 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
|
// 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).
|
// (GOARCH=loong64 go tool asm rejects VSEQV $32 and VSEQV $-64).
|
||||||
@@ -877,7 +935,7 @@ func init() {
|
|||||||
"VSEQW": {0xE502 << 15, false, -16, 15, 0, 0x1F},
|
"VSEQW": {0xE502 << 15, false, -16, 15, 0, 0x1F},
|
||||||
"XVSEQW": {0xED02 << 15, true, -16, 15, 0, 0x1F},
|
"XVSEQW": {0xED02 << 15, true, -16, 15, 0, 0x1F},
|
||||||
"VSEQV": {0xE503 << 15, false, -16, 15, 0, 0x7F},
|
"VSEQV": {0xE503 << 15, false, -16, 15, 0, 0x7F},
|
||||||
"XVSEQV": {0xE903 << 15, true, -16, 15, 0, 0x7F},
|
"XVSEQV": {0xED03 << 15, true, -16, 15, 0, 0x7F},
|
||||||
// vslti compares against a signed (or, in the U spellings, unsigned)
|
// vslti compares against a signed (or, in the U spellings, unsigned)
|
||||||
// si5/ui5 constant.
|
// si5/ui5 constant.
|
||||||
"VSLTB": {0xE50C << 15, false, -16, 15, 0, 0x1F},
|
"VSLTB": {0xE50C << 15, false, -16, 15, 0, 0x1F},
|
||||||
|
|||||||
+316
-2
@@ -8,8 +8,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
||||||
@@ -831,3 +831,317 @@ TEXT ·atoms(SB), NOSPLIT, $0
|
|||||||
0x4C000020,
|
0x4C000020,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_llacqScrel pins the acquire/release LL/SC pair. The oracle
|
||||||
|
// words come from GOARCH=loong64 go tool objdump and the toolchain's own
|
||||||
|
// loong64enc1.s golden bytes.
|
||||||
|
func TestLOONG64_llacqScrel(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·llsc(SB), NOSPLIT, $0
|
||||||
|
LLACQW (R5), R4
|
||||||
|
LLACQV (R5), R4
|
||||||
|
SCRELW R4, (R6)
|
||||||
|
SCRELV R4, (R6)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x385780A4, // ll.acq.w r4, r5
|
||||||
|
0x385788A4, // ll.acq.d r4, r5
|
||||||
|
0x385784C4, // sc.rel.w r4, r6
|
||||||
|
0x38578CC4, // sc.rel.d r4, r6
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The toolchain accepts the zero-offset memory form alone.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
LLACQW 4(R5), R4
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
SCRELV R4, 8(R6)
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_vmovqSuffixed pins the element-broadcast and element-move
|
||||||
|
// VMOVQ/XVMOVQ forms, with the oracle words lifted verbatim from the
|
||||||
|
// toolchain's loong64enc1.s.
|
||||||
|
func TestLOONG64_vmovqSuffixed(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·vmovq(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.B[3], V9.B16
|
||||||
|
VMOVQ V2.H[2], V8.H8
|
||||||
|
VMOVQ V3.W[1], V7.W4
|
||||||
|
VMOVQ V4.V[0], V6.V2
|
||||||
|
XVMOVQ X0, X31.B32
|
||||||
|
XVMOVQ X1, X30.H16
|
||||||
|
XVMOVQ X2, X29.W8
|
||||||
|
XVMOVQ X3, X28.V4
|
||||||
|
XVMOVQ X3, X27.Q2
|
||||||
|
XVMOVQ X0, X31.W[7]
|
||||||
|
XVMOVQ X1, X29.W[0]
|
||||||
|
XVMOVQ X3, X28.V[3]
|
||||||
|
XVMOVQ X4, X27.V[0]
|
||||||
|
XVMOVQ X31.W[7], X0
|
||||||
|
XVMOVQ X29.W[0], X1
|
||||||
|
XVMOVQ X28.V[3], X8
|
||||||
|
XVMOVQ X27.V[0], X9
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x72F78C29, // vreplvei.b v9, v1, 3
|
||||||
|
0x72F7C848, // vreplvei.h v8, v2, 2
|
||||||
|
0x72F7E467, // vreplvei.w v7, v3, 1
|
||||||
|
0x72F7F086, // vreplvei.d v6, v4, 0
|
||||||
|
0x7707001F, // xvreplve0.b x31, x0
|
||||||
|
0x7707803E, // xvreplve0.h x30, x1
|
||||||
|
0x7707C05D, // xvreplve0.w x29, x2
|
||||||
|
0x7707E07C, // xvreplve0.d x28, x3
|
||||||
|
0x7707F07B, // xvreplve0.q x27, x3
|
||||||
|
0x76FFDC1F, // xvinsve0.w x31, x0, 7
|
||||||
|
0x76FFC03D, // xvinsve0.w x29, x1, 0
|
||||||
|
0x76FFEC7C, // xvinsve0.d x28, x3, 3
|
||||||
|
0x76FFE09B, // xvinsve0.d x27, x4, 0
|
||||||
|
0x7703DFE0, // xvpickve.w x0, x31, 7
|
||||||
|
0x7703C3A1, // xvpickve.w x1, x29, 0
|
||||||
|
0x7703EF88, // xvpickve.d x8, x28, 3
|
||||||
|
0x7703E369, // xvpickve.d x9, x27, 0
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The rejected shapes: a width mismatch between the element and the
|
||||||
|
// arrangement, an element index past the lane count, a wrong-bank
|
||||||
|
// vreplvei and an arrangement the LASX bank does not spell.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.H[3], V9.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ V1.B[16], V9.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X1.B[3], X9.B32
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X0, X31.B16
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
XVMOVQ X0, X31.W[8]
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_parityFixes pins the operand forms whose encodings were found
|
||||||
|
// diverging from the toolchain by the loong64enc1.s differential: the
|
||||||
|
// $off(reg) address immediate (addi.d), the SCQ operand order, the scaled
|
||||||
|
// vldrepl offsets (with their field masks), the XVSEQB/XVSEQV immediate
|
||||||
|
// opcodes and the zero-register base the toolchain gives FP-relative
|
||||||
|
// VMOVQ/XVMOVQ memory operands. Golden words from loong64enc1.s.
|
||||||
|
func TestLOONG64_parityFixes(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·parity(SB), NOSPLIT, $0-32
|
||||||
|
MOVW $4(R4), R5
|
||||||
|
MOVV $4(R4), R5
|
||||||
|
MOVW $65536(R4), R5
|
||||||
|
MOVW $-4096(R4), R5
|
||||||
|
SCQ R4, R5, (R6)
|
||||||
|
VMOVQ 2(R4), V1.H8
|
||||||
|
VMOVQ -6(R4), V1.H8
|
||||||
|
VMOVQ -12(R4), V2.W4
|
||||||
|
VMOVQ -16(R4), V3.V2
|
||||||
|
XVMOVQ -10(R4), X1.H16
|
||||||
|
XVSEQB $0, X2, X4
|
||||||
|
XVSEQH $3, X2, X4
|
||||||
|
XVSEQW $12, X2, X4
|
||||||
|
XVSEQV $15, X2, X4
|
||||||
|
XVSEQV $-15, X2, X4
|
||||||
|
VMOVQ V2, y+16(FP)
|
||||||
|
VMOVQ y+16(FP), V2
|
||||||
|
VMOVQ V2, x+2030(FP)
|
||||||
|
XVMOVQ X6, y+16(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x02C01085, // addi.d $4, r4, r5
|
||||||
|
0x02C01085, // addi.d $4, r4, r5 (MOVW keeps the 64-bit addi.d)
|
||||||
|
0x1400021E, // lu12i.w $16, r30
|
||||||
|
0x038003DE, // ori $0, r30, r30
|
||||||
|
0x0010F885, // add.d r5, r4, r30
|
||||||
|
0x15FFFFFE, // lu12i.w $-1, r30
|
||||||
|
0x038003DE, // ori $0, r30, r30
|
||||||
|
0x0010F885, // add.d r5, r4, r30
|
||||||
|
0x385714C4, // sc.q r4, r5, (r6): middle<<10 | base<<5 | first
|
||||||
|
0x30400481, // vldrepl.h v1, 2(r4)
|
||||||
|
0x305FF481, // vldrepl.h v1, -6(r4)
|
||||||
|
0x302FF482, // vldrepl.w v2, -12(r4)
|
||||||
|
0x3017F883, // vldrepl.d v3, -16(r4)
|
||||||
|
0x325FEC81, // xvldrepl.h x1, -10(r4)
|
||||||
|
0x76800044, // xvseqi.b x4, x2, 0
|
||||||
|
0x76808C44, // xvseqi.h x4, x2, 3
|
||||||
|
0x76813044, // xvseqi.w x4, x2, 12
|
||||||
|
0x7681BC44, // xvseqi.d x4, x2, 15
|
||||||
|
0x7681C444, // xvseqi.d x4, x2, -15
|
||||||
|
0x2C406002, // vst v2, 24(r0): FP-relative keeps the zero base
|
||||||
|
0x2C006002, // vld v2, 24(r0)
|
||||||
|
0x2C5FD802, // vst v2, 2038(r0)
|
||||||
|
0x2CC06006, // xvst x6, 24(r0)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// Misaligned vldrepl offsets are rejected, as the toolchain does.
|
||||||
|
for i, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
VMOVQ 3(R4), V1.H8
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
MOVW $4(R4), F1
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_bytePseudo pins the BYTE literal-data pseudo-op, which the
|
||||||
|
// loong64 toolchain does not spell but the arm64 and riscv64 encoders of
|
||||||
|
// this package already accept for byte-exact data layout (a superset
|
||||||
|
// spelling, shippable via the goobj path).
|
||||||
|
func TestLOONG64_bytePseudo(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·bytes(SB), NOSPLIT, $0
|
||||||
|
BYTE $2
|
||||||
|
BYTE $1; BYTE $0
|
||||||
|
BYTE $255
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
// Four literal bytes, then RET (jirl r0, r1, 0); the trailing bytes pad
|
||||||
|
// the final word the way any sub-word tail does.
|
||||||
|
want := []byte{2, 1, 0, 0xFF, 0x20, 0x00, 0x00, 0x4C}
|
||||||
|
if !bytes.Equal(code[:len(want)], want) {
|
||||||
|
t.Errorf("bytes = % x, want % x", code, want)
|
||||||
|
}
|
||||||
|
for _, src := range []string{
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
BYTE $256
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
`TEXT ·e(SB), NOSPLIT, $0
|
||||||
|
BYTE $-1
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, src)
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%q: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64PairSugar pins the toolchain's register-pair spellings: a
|
||||||
|
// top-level colon between two registers splits the operand in two with the
|
||||||
|
// halves swapped, so INSTR R4:R5, R6 encodes exactly as INSTR R5, R4, R6.
|
||||||
|
// Each row is pinned two ways: against the word GOARCH=loong64 go tool asm
|
||||||
|
// emits for the pair spelling, and against gasm's own encoding of the
|
||||||
|
// direct spelling, which must be the same bytes.
|
||||||
|
func TestLOONG64PairSugar(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
pair, direct string
|
||||||
|
want uint32 // the word go tool asm emits for the pair
|
||||||
|
}{
|
||||||
|
{"MULV R4:R5, R6", "MULV R5, R4, R6", 0x001D9486},
|
||||||
|
{"MULV R4:R5", "MULV R5, R4", 0x001D9484},
|
||||||
|
{"MULV R6, R4:R5", "MULV R6, R5, R4", 0x001D98A4},
|
||||||
|
{"DIVV R4:R5, R6", "DIVV R5, R4, R6", 0x00221486},
|
||||||
|
{"MULHV R4:R5, R6", "MULHV R5, R4, R6", 0x001E1486},
|
||||||
|
{"ADDV R4:R5, R6", "ADDV R5, R4, R6", 0x00109486},
|
||||||
|
{"AND R4:R5, R6", "AND R5, R4, R6", 0x00149486},
|
||||||
|
{"MOVV R4:R5", "MOVV R5, R4", 0x001500A4},
|
||||||
|
{"MULD F4:F5, F6", "MULD F5, F4, F6", 0x01051486},
|
||||||
|
{"VADDW V1:V2, V3", "VADDW V2, V1, V3", 0x700B0823},
|
||||||
|
} {
|
||||||
|
t.Run(tt.pair, func(t *testing.T) {
|
||||||
|
pair := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·p(SB), NOSPLIT, $0
|
||||||
|
`+tt.pair+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
direct := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·d(SB), NOSPLIT, $0
|
||||||
|
`+tt.direct+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
if len(pair) < 4 {
|
||||||
|
t.Fatalf("pair spelling produced %d bytes", len(pair))
|
||||||
|
}
|
||||||
|
got := binary.LittleEndian.Uint32(pair)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("pair word = %08x, want %08x (go tool asm)", got, tt.want)
|
||||||
|
}
|
||||||
|
if len(direct) < 4 || !bytes.Equal(pair[:4], direct[:4]) {
|
||||||
|
t.Errorf("pair bytes % x differ from the direct spelling's % x", pair[:4], direct[:4])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_addv16 pins the ADDV16 immediate family, the toolchain's
|
||||||
|
// assembler name for the addu16i.d hardware instruction (loong64enc1.s
|
||||||
|
// spells it ADDV16; the spelling ADDU16I.D is the decoder's name for the
|
||||||
|
// encoding and no assembler input at all). The immediate must be a
|
||||||
|
// multiple of 65536 and rides the instruction field shifted right by 16;
|
||||||
|
// the oracle words are the toolchain's own loong64enc1.s rows, each also
|
||||||
|
// confirmed against GOARCH=loong64 go tool asm.
|
||||||
|
func TestLOONG64_addv16(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·a16(SB), NOSPLIT, $0
|
||||||
|
ADDV16 $(-32768<<16), R4, R5
|
||||||
|
ADDV16 $(0<<16), R4, R5
|
||||||
|
ADDV16 $(8<<16), R4, R5
|
||||||
|
ADDV16 $(32767<<16), R4, R5
|
||||||
|
ADDV16 $(16<<16), R4
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x12000085, // addu16i.d r5, r4, -32768
|
||||||
|
0x10000085, // addu16i.d r5, r4, 0
|
||||||
|
0x10002085, // addu16i.d r5, r4, 8
|
||||||
|
0x11FFFC85, // addu16i.d r5, r4, 32767
|
||||||
|
0x10004084, // addu16i.d r4, r4, 16 (destination-only form)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
// The non-multiples the loong64error.s catalogue pins (its rows 3 and
|
||||||
|
// 4): $1 and $65535 both refuse.
|
||||||
|
for _, src := range []string{
|
||||||
|
"ADDV16 $1, R4, R5",
|
||||||
|
"ADDV16 $65535, R4, R5",
|
||||||
|
"ADDV16 $(16<<16), F4",
|
||||||
|
} {
|
||||||
|
fn := firstTextLOONG64(t, "#include \"textflag.h\"\nTEXT ·e(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%q: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loong64AcceptedErrorShapes lists the toolchain's loong64error.s spellings
|
||||||
|
// gasm still accepts, each an acceptance superset with a known shape. The
|
||||||
|
// list only shrinks: every tightening of the encoder moves spellings out of
|
||||||
|
// it, and a spelling reappearing here means a regression.
|
||||||
|
var loong64AcceptedErrorShapes = []string{}
|
||||||
|
|
||||||
|
// TestLoong64ToolchainErrorParity walks the toolchain's loong64error.s (Go
|
||||||
|
// 1.27, loong64) and requires gasm to reject every case the toolchain rejects
|
||||||
|
// with an equivalent diagnostic, the documented acceptance supersets above
|
||||||
|
// excepted. A live Go toolchain is needed for the source file; the test
|
||||||
|
// skips without one or in -short.
|
||||||
|
func TestLoong64ToolchainErrorParity(t *testing.T) {
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
path := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata", "loong64error.s")
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
allowed := map[string]bool{}
|
||||||
|
for _, s := range loong64AcceptedErrorShapes {
|
||||||
|
allowed[s] = true
|
||||||
|
}
|
||||||
|
for raw := range strings.SplitSeq(string(data), "\n") {
|
||||||
|
line := strings.TrimSpace(raw)
|
||||||
|
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "TEXT") || !strings.Contains(line, "ERROR") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body := line
|
||||||
|
if i := strings.Index(body, "//"); i >= 0 {
|
||||||
|
body = strings.TrimSpace(body[:i])
|
||||||
|
}
|
||||||
|
want := ""
|
||||||
|
if m := regexp.MustCompile(`ERROR "([^"]*)"`).FindStringSubmatch(line); m != nil {
|
||||||
|
want = m[1]
|
||||||
|
}
|
||||||
|
body = strings.ReplaceAll(body, "\t", " ")
|
||||||
|
body = strings.Join(strings.Fields(body), " ")
|
||||||
|
// Label-relative and non-operand lines need their function context.
|
||||||
|
if strings.Contains(body, "(PC)") || strings.Contains(body, "(SB)") || strings.Contains(body, "PCALIGN") ||
|
||||||
|
strings.HasPrefix(body, "RET") || strings.HasPrefix(body, "NOP") || strings.HasPrefix(body, "BRK") ||
|
||||||
|
strings.Contains(body, "again") || strings.Contains(body, "next") || strings.Contains(body, "loop") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("errorparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr == nil {
|
||||||
|
if !allowed[body] {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", body)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if want != "" && !loong64DiagEquivalent(want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", body, want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagEquivalent answers whether gasm's rejection carries the same
|
||||||
|
// information as the toolchain's expected message. The two word the same
|
||||||
|
// facts differently: the toolchain writes "operand out of range 0 to 15"
|
||||||
|
// where gasm writes "immediate out of range [0, 15]", and both terminate
|
||||||
|
// sentences the toolchain punctuates. Canonicalisation drops the subject
|
||||||
|
// word and the connectives and compares the remaining token sequence.
|
||||||
|
func loong64DiagEquivalent(want, got string) bool {
|
||||||
|
return loong64DiagSubseq(loong64DiagTokens(want), loong64DiagTokens(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagTokens lower-cases a diagnostic, strips punctuation and the
|
||||||
|
// connective tokens, and returns its words. "to" survives only between
|
||||||
|
// digits, where the toolchain's range wording uses it; gasm's bracketed
|
||||||
|
// form never produces it.
|
||||||
|
func loong64DiagTokens(msg string) []string {
|
||||||
|
msg = strings.ToLower(msg)
|
||||||
|
for _, r := range []string{"[", "]", ",", ".", ":", "\n"} {
|
||||||
|
msg = strings.ReplaceAll(msg, r, " ")
|
||||||
|
}
|
||||||
|
fields := strings.Fields(msg)
|
||||||
|
out := make([]string, 0, len(fields))
|
||||||
|
for i, w := range fields {
|
||||||
|
switch w {
|
||||||
|
case "the", "a", "operand", "immediate":
|
||||||
|
// subjects and articles carry no constraint
|
||||||
|
case "to":
|
||||||
|
if i > 0 && i+1 < len(fields) && isDigits(fields[i-1]) && isDigits(fields[i+1]) {
|
||||||
|
continue // the range connective
|
||||||
|
}
|
||||||
|
out = append(out, w)
|
||||||
|
default:
|
||||||
|
out = append(out, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64DiagSubseq answers whether want is a subsequence of got.
|
||||||
|
func loong64DiagSubseq(want, got []string) bool {
|
||||||
|
i := 0
|
||||||
|
for _, w := range got {
|
||||||
|
if i < len(want) && w == want[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i == len(want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigits(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(s) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ForeignErrorCorpus lists the corpus directories whose .s files the
|
||||||
|
// loong64 build attempts: the toolchain's own assembler testdata (the
|
||||||
|
// foreign-architecture files the corpus audit indicts on loong64) and the
|
||||||
|
// go/build malformed-source probes.
|
||||||
|
var loong64ForeignErrorCorpus = []string{
|
||||||
|
filepath.Join("cmd", "asm", "internal", "asm", "testdata"),
|
||||||
|
filepath.Join("go", "build", "testdata", "bads"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ForeignAcceptedLines lists toolchain rejections gasm still
|
||||||
|
// accepts, keyed by corpus-relative path and line, each with its reason.
|
||||||
|
// The list only shrinks: every tightening moves lines out of it, and a line
|
||||||
|
// reappearing here means a regression.
|
||||||
|
var loong64ForeignAcceptedLines = map[string]string{
|
||||||
|
// duperror.s rejections are whole-file semantics: the toolchain indicts
|
||||||
|
// the second declaration against the first in the same file, and a
|
||||||
|
// single TEXT or GLOBL line is legal on its own. gasm rejects the
|
||||||
|
// whole file with the same redeclaration diagnostic.
|
||||||
|
"cmd/asm/internal/asm/testdata/duperror.s:7": "symbol foo redeclared is a whole-file judgement",
|
||||||
|
"cmd/asm/internal/asm/testdata/duperror.s:11": "symbol bar redeclared is a whole-file judgement",
|
||||||
|
// The toolchain deletes the R22 spelling from the loong64 register map
|
||||||
|
// (its comment: avoid unintentionally clobbering g) and keeps g alone;
|
||||||
|
// gasm resolves R22 to register 22 as part of its deliberate ABI-alias
|
||||||
|
// superset (A0, SP, T0, ... share the table), so the three mips64.s
|
||||||
|
// spellings that name R22 assemble here.
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:135": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:138": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
"cmd/asm/internal/asm/testdata/mips64.s:218": "R22 spelling: gasm's deliberate ABI-alias superset",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64ForeignErrorParity takes go tool asm's own per-line rejections
|
||||||
|
// on GOARCH=loong64 as the ground truth over the foreign corpus files and
|
||||||
|
// requires gasm to reject every one of those lines too, the documented
|
||||||
|
// acceptance supersets above excepted. The toolchain is run once per file;
|
||||||
|
// the test skips without a live toolchain or in -short.
|
||||||
|
func TestLoong64ForeignErrorParity(t *testing.T) {
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
files := loong64CorpusFiles(t, goroot)
|
||||||
|
if len(files) == 0 {
|
||||||
|
t.Skip("no corpus files")
|
||||||
|
}
|
||||||
|
for _, path := range files {
|
||||||
|
if filepath.Base(path) == "loong64error.s" {
|
||||||
|
continue // the native catalogue above walks it deeply
|
||||||
|
}
|
||||||
|
rel := loong64CorpusRel(goroot, path)
|
||||||
|
rejected := loong64ToolchainRejects(t, goroot, path)
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read %s: %v", path, err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, ln := range rejected {
|
||||||
|
if ln < 1 || ln > len(lines) {
|
||||||
|
t.Fatalf("%s: diagnostic names line %d beyond the file", rel, ln)
|
||||||
|
}
|
||||||
|
body := strings.TrimSpace(lines[ln-1])
|
||||||
|
if body == "" || strings.HasPrefix(body, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i := strings.Index(body, "//"); i >= 0 {
|
||||||
|
body = strings.TrimSpace(body[:i])
|
||||||
|
}
|
||||||
|
body = strings.Join(strings.Fields(strings.ReplaceAll(body, "\t", " ")), " ")
|
||||||
|
key := rel + ":" + strconv.Itoa(ln)
|
||||||
|
if _, ok := loong64ForeignAcceptedLines[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if loong64ProbeAssembles(body) {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects at %s: %s", key, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64RejectedNames pins the names the toolchain's loong64 table carries
|
||||||
|
// but refuses to encode: each is an "illegal combination" under every
|
||||||
|
// operand shape GOARCH=loong64 go tool asm accepts. gasm rejects them too,
|
||||||
|
// and the pair of rejections is the parity this catalogue asserts.
|
||||||
|
var loong64RejectedNames = []string{"RFE", "DUFFCOPY", "DUFFZERO", "PCALIGNMAX"}
|
||||||
|
|
||||||
|
// loong64PairErrorShapes walks the register-pair sugar's negative shapes,
|
||||||
|
// every one measured against GOARCH=loong64 go tool asm. want carries the
|
||||||
|
// toolchain's diagnostic where gasm's wording matches it (checked through
|
||||||
|
// the same equivalence as the catalogue above); an empty want pins the
|
||||||
|
// rejection alone, and the row's comment records the toolchain's own words
|
||||||
|
// for the divergence.
|
||||||
|
var loong64PairErrorShapes = []struct {
|
||||||
|
body string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// A pair never splits inside an address; the toolchain's parse stage
|
||||||
|
// refuses it outright.
|
||||||
|
{"MULV (R4:R5), R6", "indirect through register pair"},
|
||||||
|
// The reordered list fails the instruction's operand shape. The
|
||||||
|
// toolchain words these "illegal combination ..." at the class match;
|
||||||
|
// gasm words them at the operand count.
|
||||||
|
{"MOVV R4:R5, R6", ""},
|
||||||
|
{"BEQ R4:R5, R6", ""},
|
||||||
|
{"JMP R4:R5", ""},
|
||||||
|
{"MULV R4:R5, R7:R8, R6", ""},
|
||||||
|
{"MULV R4:R5, (R6)", ""},
|
||||||
|
{"BEQ R4:R5, 2(PC)", ""},
|
||||||
|
// A right half outside the register table; the toolchain's parse stage
|
||||||
|
// again ("illegal or missing addressing mode for symbol label").
|
||||||
|
{"MULV R4:label", "illegal or missing addressing mode for symbol label"},
|
||||||
|
// An immediate half on either side. The toolchain reorders the pair
|
||||||
|
// first and rejects at the class match ("illegal combination MULV
|
||||||
|
// U3CON ...", "MULV: expected register; found $4"); gasm rejects the
|
||||||
|
// shape without the reorder.
|
||||||
|
{"MOVV $4:R5", ""},
|
||||||
|
{"MULV R4:$5", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64PairErrorParity requires gasm to refuse every register-pair
|
||||||
|
// spelling the toolchain refuses, with an equivalent diagnostic where the
|
||||||
|
// row carries one. The toolchain's accept side of the sugar is pinned
|
||||||
|
// byte for byte by TestLOONG64PairSugar; the empty-want rows are documented
|
||||||
|
// wording divergences, not acceptance divergences.
|
||||||
|
func TestLoong64PairErrorParity(t *testing.T) {
|
||||||
|
for _, tt := range loong64PairErrorShapes {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + tt.body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("pairparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr == nil {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", tt.body)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.want != "" && !loong64DiagEquivalent(tt.want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", tt.body, tt.want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64BacklogNameParity walks the audit's known-but-unencodable
|
||||||
|
// backlog: the names go tool asm recognises on loong64 yet refuses to
|
||||||
|
// encode must be refused by gasm as well, and the one name the audit
|
||||||
|
// misses because its probe battery lacks the shape (SCQ, whose only form
|
||||||
|
// is the three-operand store-conditional) must encode to the toolchain's
|
||||||
|
// own bytes. The refusal claim is walked under the full operand-shape
|
||||||
|
// battery below, against the live toolchain and against gasm alike.
|
||||||
|
func TestLoong64BacklogNameParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live toolchain backlog: skipped in -short mode")
|
||||||
|
}
|
||||||
|
for _, name := range loong64RejectedNames {
|
||||||
|
if loong64ProbeAssembles(name) {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The shapes the plain audit probes, walked here so the backlog's
|
||||||
|
// claim holds under any operand shape, not only the bare mnemonic:
|
||||||
|
// the toolchain must refuse every combination, and gasm must refuse
|
||||||
|
// it too.
|
||||||
|
goroot := loong64Goroot(t)
|
||||||
|
for _, name := range loong64RejectedNames {
|
||||||
|
for _, shape := range loong64BacklogShapes {
|
||||||
|
body := strings.TrimSpace(name + " " + shape)
|
||||||
|
if loong64ProbeAssembles(body) {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", body)
|
||||||
|
}
|
||||||
|
// The toolchain side of the same row: the file holds the one
|
||||||
|
// instruction, and the live assembler must report it. A
|
||||||
|
// spelling the toolchain accepts would open the name for
|
||||||
|
// encoding and close nothing here but the parity.
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\np2:\n\t" + body + "\n\tRET\n"
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "backlogshape.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatalf("write: %v", err)
|
||||||
|
}
|
||||||
|
if lines := loong64ToolchainRejects(t, goroot, path); len(lines) == 0 {
|
||||||
|
t.Errorf("the toolchain assembles %q, against the backlog's claim", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sc.q: loong64enc1.s pins the encoding as c4145738
|
||||||
|
// (SCQ R4, R5, (R6): sc.q rd, rk, (rj)).
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tSCQ R4, R5, (R6)\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("backlog.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
t.Fatalf("parse SCQ: %v", perr[0])
|
||||||
|
}
|
||||||
|
img, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr != nil {
|
||||||
|
t.Fatalf("assemble SCQ: %v", aerr)
|
||||||
|
}
|
||||||
|
want := []byte{0xc4, 0x14, 0x57, 0x38, 0x20, 0x00, 0x00, 0x4c}
|
||||||
|
if !slices.Equal(img.Code, want) {
|
||||||
|
t.Errorf("SCQ encodes to % X, want % X (RET included)", img.Code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64BacklogShapes is the operand-shape battery the backlog walk runs
|
||||||
|
// every rejected name through: the register, immediate, memory, vector and
|
||||||
|
// label positions the loong64 grammar has, mirroring the plain audit's
|
||||||
|
// probe battery (cmd/gasm's probeShapes) so the two answer the same
|
||||||
|
// question.
|
||||||
|
var loong64BacklogShapes = []string{
|
||||||
|
"", "R4", "R4, R5", "R4, R5, R6", "$1, R4", "R4, (R5)", "(R4), R5",
|
||||||
|
"F0, F1", "F0, F1, F2", "V1, V2", "X1, X2", "V1, FCC0", "$65536, R4",
|
||||||
|
"R4, R5, p2", "p2(SB)", "R5, (R4), R6", "$1, $0", "R1, R5, 0",
|
||||||
|
"0(R7), $5, $0", "V1, V2, V3, V4", "R4, R5, (R6)", "$16", "(R4)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64ProbeAssembles answers whether the single corpus line, wrapped in
|
||||||
|
// its own function, parses and assembles on loong64.
|
||||||
|
func loong64ProbeAssembles(body string) bool {
|
||||||
|
src := "#include \"textflag.h\"\n\n"
|
||||||
|
if strings.HasPrefix(body, "TEXT ") {
|
||||||
|
src += body + "\n\tRET\n"
|
||||||
|
} else if strings.HasPrefix(body, "DATA ") || strings.HasPrefix(body, "GLOBL ") {
|
||||||
|
src += body + "\n\nTEXT ·probe(SB), NOSPLIT, $0-0\n\tRET\n"
|
||||||
|
} else {
|
||||||
|
src += "TEXT ·probe(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
}
|
||||||
|
f, perr := parser.Parse("foreignparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
return false // the parser already rejects the line
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
return aerr == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64Goroot resolves the live toolchain root, the environment's own
|
||||||
|
// value first, `go env GOROOT` as the fallback.
|
||||||
|
func loong64Goroot(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
if goroot := os.Getenv("GOROOT"); goroot != "" {
|
||||||
|
return goroot
|
||||||
|
}
|
||||||
|
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("no GOROOT: %v", err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64CorpusFiles lists every .s file under the corpus roots, the
|
||||||
|
// testdata tree walked recursively.
|
||||||
|
func loong64CorpusFiles(t *testing.T, goroot string) []string {
|
||||||
|
t.Helper()
|
||||||
|
var files []string
|
||||||
|
for _, rel := range loong64ForeignErrorCorpus {
|
||||||
|
root := filepath.Join(goroot, "src", rel)
|
||||||
|
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() && strings.HasSuffix(path, ".s") {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("walk %s: %v", root, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(files)
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// loong64CorpusRel renders a corpus file's path relative to GOROOT/src, the
|
||||||
|
// key the accepted-lines catalogue uses.
|
||||||
|
func loong64CorpusRel(goroot, path string) string {
|
||||||
|
rel, err := filepath.Rel(filepath.Join(goroot, "src"), path)
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// loong64ParseDiag matches the parse-stage "file:line: message".
|
||||||
|
loong64ParseDiag = regexp.MustCompile(`(?m)^(?:\S*asm: )?([^:\s]+\.s):(\d+):`)
|
||||||
|
// loong64EncodeDiag matches the encode-stage
|
||||||
|
// "asm: message: 003c (file.s:61)".
|
||||||
|
loong64EncodeDiag = regexp.MustCompile(`\(([^:\s()]+\.s):(\d+)\)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// loong64ToolchainRejects runs go tool asm on the file for GOARCH=loong64
|
||||||
|
// and returns the line numbers it rejects, both diagnostic shapes
|
||||||
|
// collected. An infrastructure failure fails the test: without the ground
|
||||||
|
// truth there is no parity to assert.
|
||||||
|
func loong64ToolchainRejects(t *testing.T, goroot, path string) []int {
|
||||||
|
t.Helper()
|
||||||
|
obj := filepath.Join(t.TempDir(), "probe.o")
|
||||||
|
cmd := exec.Command("go", "tool", "asm", "-I", filepath.Join(goroot, "pkg", "include"), "-o", obj, path)
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=loong64")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
var lines []int
|
||||||
|
seen := map[int]bool{}
|
||||||
|
for _, m := range loong64ParseDiag.FindAllStringSubmatch(string(out), -1) {
|
||||||
|
if strings.HasSuffix(m[1], ".s") {
|
||||||
|
ln, perr := strconv.Atoi(m[2])
|
||||||
|
if perr == nil && !seen[ln] {
|
||||||
|
seen[ln] = true
|
||||||
|
lines = append(lines, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range loong64EncodeDiag.FindAllStringSubmatch(string(out), -1) {
|
||||||
|
ln, perr := strconv.Atoi(m[2])
|
||||||
|
if perr == nil && !seen[ln] {
|
||||||
|
seen[ln] = true
|
||||||
|
lines = append(lines, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(lines)
|
||||||
|
if err != nil && len(lines) == 0 {
|
||||||
|
t.Fatalf("go tool asm %s: %v\n%s", filepath.Base(path), err, out)
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loong64 frame mapping, matching the Go toolchain's loong64 backend.
|
// Loong64 frame mapping, matching the Go toolchain's loong64 backend.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestLOONG64_sys exercises the no-operand system instructions and the
|
// TestLOONG64_sys exercises the no-operand system instructions and the
|
||||||
@@ -131,6 +131,98 @@ TEXT ·ptr(SB), NOSPLIT, $0
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_firr14Spans pins the 2RI14 offset spans of the LL/SC/MOVWP
|
||||||
|
// families against the toolchain's operand classes: an unaligned offset is
|
||||||
|
// rejected ("offset must be a multiple of 4"), a signed 16-bit offset rides
|
||||||
|
// in si14 alone, and anything wider materialises its high half in R30 with
|
||||||
|
// addu16i.d before the base folds in, with si14 keeping bits 15:2. The
|
||||||
|
// pinned words are GOARCH=loong64 go tool asm's own bytes for the same
|
||||||
|
// sources, boundaries included (-32766 and 32766 are the class edges).
|
||||||
|
func TestLOONG64_firr14Spans(t *testing.T) {
|
||||||
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·spans(SB), NOSPLIT, $0
|
||||||
|
SC R4, 32764(R5)
|
||||||
|
SC R4, -32764(R5)
|
||||||
|
SC R4, 32768(R5)
|
||||||
|
SC R4, -32768(R5)
|
||||||
|
SC R4, -32772(R5)
|
||||||
|
LL 65540(R5), R4
|
||||||
|
LLV 65540(R5), R4
|
||||||
|
SCV R4, 65540(R5)
|
||||||
|
MOVWP R4, 65540(R5)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleLOONG64Helper(t, fn)
|
||||||
|
wantWords(t, code,
|
||||||
|
0x217FFCA4, // sc.w r4, 8191(r5) — inside si14
|
||||||
|
0x218004A4, // sc.w r4, -8191(r5)
|
||||||
|
0x1000001E, // addu16i.d r30, r0, 0
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x218003C4, // sc.w r4, 8192(r30)
|
||||||
|
0x13FFFC1E, // addu16i.d r30, r0, -1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x218003C4, // sc.w r4, 8192(r30)
|
||||||
|
0x13FFFC1E, // addu16i.d r30, r0, -1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x217FFFC4, // sc.w r4, 8191(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x200007C4, // ll.w r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x220007C4, // ldptr.d r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x230007C4, // sc.d r4, 1(r30)
|
||||||
|
0x1000041E, // addu16i.d r30, r0, 1
|
||||||
|
0x001097DE, // add.d r30, r30, r5
|
||||||
|
0x250007C4, // stptr.w r4, 1(r30)
|
||||||
|
0x4C000020,
|
||||||
|
)
|
||||||
|
|
||||||
|
// The expansion participates in layout: the function size carries the
|
||||||
|
// six three-word forms plus the single-word pair and the RET.
|
||||||
|
f, errs := parser.Parse("spans_loong64.s", `#include "textflag.h"
|
||||||
|
TEXT ·spans(SB), NOSPLIT, $0
|
||||||
|
SC R4, 32768(R5)
|
||||||
|
LL 65540(R5), R4
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileLOONG64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileLOONG64: %v", err)
|
||||||
|
}
|
||||||
|
if img.Funcs[0].Size != 8*3+4 {
|
||||||
|
t.Errorf("function size = %d, want %d", img.Funcs[0].Size, 8*3+4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLOONG64_firr14Alignment checks the LL/SC/MOVWP alignment rule the
|
||||||
|
// toolchain enforces as "offset must be a multiple of 4": the LoongArch
|
||||||
|
// ll/sc family requires a naturally scaled offset, and si14 cannot express
|
||||||
|
// the remainder. GOARCH=loong64 go tool asm rejects every case here.
|
||||||
|
func TestLOONG64_firr14Alignment(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
"SC R4, 1(R5)",
|
||||||
|
"SCW R4, -1(R5)",
|
||||||
|
"SCV R4, 2(R5)",
|
||||||
|
"LL 1(R5), R4",
|
||||||
|
"LLW 3(R5), R4",
|
||||||
|
"LLV 6(R5), R4",
|
||||||
|
"MOVWP R4, 1(R5)",
|
||||||
|
"MOVVP R4, 2(R5)",
|
||||||
|
}
|
||||||
|
for _, src := range cases {
|
||||||
|
fn := firstTextLOONG64(t, "#include \"textflag.h\"\nTEXT ·e(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
||||||
|
t.Errorf("%s: expected an error, got none", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestLOONG64_atomics exercises the AM* read-modify-write forms and
|
// TestLOONG64_atomics exercises the AM* read-modify-write forms and
|
||||||
// RDTIME, plus the MOVV FP→GP move.
|
// RDTIME, plus the MOVV FP→GP move.
|
||||||
func TestLOONG64_atomics(t *testing.T) {
|
func TestLOONG64_atomics(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative
|
// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package asm
|
package asm
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// Operand is an instruction operand: a Reg, a Mem reference or an Imm value.
|
// Operand is an instruction operand: a Reg, a Mem reference or an Imm value.
|
||||||
type Operand interface {
|
type Operand interface {
|
||||||
isOperand()
|
isOperand()
|
||||||
@@ -107,3 +109,84 @@ func isX86Mem(o Operand) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// byteReg reports whether r is a general-purpose register whose name spells
|
||||||
|
// a byte width (AL, CL, DL, BL, AH-DH, SPL-DIL, R8B-R15B): the name itself
|
||||||
|
// fixes an 8-bit access, unlike the size-agnostic spellings (AX, EAX, RAX,
|
||||||
|
// R11) whose width the mnemonic supplies. The vector, opmask, x87, MMX,
|
||||||
|
// control and segment registers never name a byte access.
|
||||||
|
func byteReg(r Reg) bool {
|
||||||
|
return r.size == 1 && !r.isVec() && !r.mask && !r.fp && !r.mmx && r.ctl == 0 && r.seg == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// byteFormBase lists the scalar families that own an 8-bit encoding beside
|
||||||
|
// the 16/32/64-bit ones: the arithmetic and logic group, TEST, the moves,
|
||||||
|
// the unary group, the shifts, the exchanges and atomics, the one-operand
|
||||||
|
// IMUL and CRC32. Families without a byte form (LEA, BT, the bit scans,
|
||||||
|
// PUSH/POP, ADCX/ADOX, BSWAP) stay out, so their width comes from the
|
||||||
|
// mnemonic alone and a byte register in them is the handler's own error to
|
||||||
|
// make.
|
||||||
|
var byteFormBase = map[string]bool{
|
||||||
|
"MOV": true,
|
||||||
|
"ADD": true, "OR": true, "ADC": true, "SBB": true,
|
||||||
|
"AND": true, "SUB": true, "XOR": true, "CMP": true,
|
||||||
|
"TEST": true,
|
||||||
|
"INC": true, "DEC": true, "NEG": true, "NOT": true,
|
||||||
|
"MUL": true, "DIV": true, "IDIV": true,
|
||||||
|
"SHL": true, "SAL": true, "SHR": true, "SAR": true,
|
||||||
|
"ROL": true, "ROR": true, "RCL": true, "RCR": true,
|
||||||
|
"XCHG": true, "CMPXCHG": true, "XADD": true,
|
||||||
|
"CRC32": true,
|
||||||
|
"IMUL": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// operandWidth settles the operand width of a suffixed scalar instruction
|
||||||
|
// against the widths its register operands spell. A byte-spelled register
|
||||||
|
// operand (AL, DL, R8B, ...) forces the 8-bit form whatever the mnemonic's
|
||||||
|
// L suffix or lack of one says: that is the text the toolchain's own
|
||||||
|
// disassembly prints for the byte encodings (the rendered suffix rides the
|
||||||
|
// operand-size attribute, so an L or no suffix at all can name a byte
|
||||||
|
// form), and every register operand joins the form at its low byte, the way
|
||||||
|
// go tool asm reads the classic names there (TESTL R11, DL encodes TESTB
|
||||||
|
// R11B, DL). A W or Q suffix never rides a byte form, so that mix is a
|
||||||
|
// conflict rejected the way the toolchain rejects it (MOVQ AL, AX). With
|
||||||
|
// no byte operand the mnemonic decides alone and 0 returns, keeping the
|
||||||
|
// caller's width: the classic names are size-agnostic at every width.
|
||||||
|
func operandWidth(mnem string, suffix int, ops []Operand) (int, error) {
|
||||||
|
for _, o := range ops {
|
||||||
|
r, ok := o.(Reg)
|
||||||
|
if !ok || !byteReg(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if suffix == 2 || suffix == 8 {
|
||||||
|
kind := "quad"
|
||||||
|
if suffix == 2 {
|
||||||
|
kind = "word"
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("%s: byte register cannot take the %s form", mnem, kind)
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// widthOperands selects the operands that carry the instruction's data
|
||||||
|
// width for base: every operand of the scalar families but a shift's count,
|
||||||
|
// which names the CL register without narrowing the shifted value (RCLW CL,
|
||||||
|
// 0(R11) stays 16-bit), and nothing of IMUL's two- and three-operand forms,
|
||||||
|
// which have no byte encoding at all.
|
||||||
|
func widthOperands(base string, ops []Operand) []Operand {
|
||||||
|
switch base {
|
||||||
|
case "SHL", "SAL", "SHR", "SAR", "ROL", "ROR", "RCL", "RCR":
|
||||||
|
if len(ops) > 1 {
|
||||||
|
return ops[1:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "IMUL":
|
||||||
|
if len(ops) == 1 {
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ops
|
||||||
|
}
|
||||||
+30
-1
@@ -17,13 +17,28 @@ import "strings"
|
|||||||
// size. The high flag marks the legacy high-byte registers AH/CH/DH/BH, which
|
// size. The high flag marks the legacy high-byte registers AH/CH/DH/BH, which
|
||||||
// occupy indices 4-7 yet take no REX prefix, unlike SPL/BPL/SIL/DIL that share
|
// occupy indices 4-7 yet take no REX prefix, unlike SPL/BPL/SIL/DIL that share
|
||||||
// those indices but require one. The mask flag marks the AVX-512 opmask
|
// those indices but require one. The mask flag marks the AVX-512 opmask
|
||||||
// registers K0-K7, the fp flag the x87 stack registers F0-F7.
|
// registers K0-K7, the fp flag the x87 stack registers F0-F7, the mmx flag the
|
||||||
|
// MMX registers M0-M7, the seg field a bare segment register (FS, GS) and the
|
||||||
|
// ctl field the control and debug registers, whose number rides an
|
||||||
|
// instruction's reg field rather than r/m.
|
||||||
type Reg struct {
|
type Reg struct {
|
||||||
idx int
|
idx int
|
||||||
size int // informational width implied by the name; the mnemonic decides
|
size int // informational width implied by the name; the mnemonic decides
|
||||||
high bool // AH/CH/DH/BH
|
high bool // AH/CH/DH/BH
|
||||||
mask bool // K0-K7 opmask register
|
mask bool // K0-K7 opmask register
|
||||||
fp bool // F0-F7 x87 stack register
|
fp bool // F0-F7 x87 stack register
|
||||||
|
mmx bool // M0-M7 MMX register
|
||||||
|
seg int // segment register number plus one (ES=1..GS=6); 0 = not one
|
||||||
|
ctl byte // 0 none, 1 CRn control register, 2 DRn debug register
|
||||||
|
}
|
||||||
|
|
||||||
|
// segNumber returns the segment register number (ES=0..GS=5) when r names a
|
||||||
|
// bare segment register.
|
||||||
|
func (r Reg) segNumber() (int, bool) {
|
||||||
|
if r.seg == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return r.seg - 1, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns the register number (0-15 for GPRs, 0-31 for vectors).
|
// Index returns the register number (0-15 for GPRs, 0-31 for vectors).
|
||||||
@@ -149,6 +164,20 @@ func buildRegByName() map[string]Reg {
|
|||||||
for i := 0; i <= 7; i++ {
|
for i := 0; i <= 7; i++ {
|
||||||
m["F"+itoa(i)] = Reg{idx: i, size: 8, fp: true}
|
m["F"+itoa(i)] = Reg{idx: i, size: 8, fp: true}
|
||||||
}
|
}
|
||||||
|
// MMX: M0..M7.
|
||||||
|
for i := 0; i <= 7; i++ {
|
||||||
|
m["M"+itoa(i)] = Reg{idx: i, size: 8, mmx: true}
|
||||||
|
}
|
||||||
|
// Bare segment registers: ES, CS, SS, DS, FS, GS (the memory-base and
|
||||||
|
// index spellings of FS and GS are handled before register lookup).
|
||||||
|
for i, n := range []string{"ES", "CS", "SS", "DS", "FS", "GS"} {
|
||||||
|
m[n] = Reg{idx: i, size: 2, seg: i + 1}
|
||||||
|
}
|
||||||
|
// Control and debug registers: CR0..CR15, DR0..DR15.
|
||||||
|
for i := 0; i <= 15; i++ {
|
||||||
|
m["CR"+itoa(i)] = Reg{idx: i, size: 8, ctl: 1}
|
||||||
|
m["DR"+itoa(i)] = Reg{idx: i, size: 8, ctl: 2}
|
||||||
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRenderedTextEncodes pins the encoder against the disassembler's
|
||||||
|
// renderings: every line here is a text the toolchain's own decoder prints
|
||||||
|
// for bytes its assembler produced (the disasm package's parity fixtures),
|
||||||
|
// so assembling the printed text must reproduce those bytes. Each row was
|
||||||
|
// read back through the toolchain's decoder; the hex is the fixture's.
|
||||||
|
func TestRenderedTextEncodes(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
text string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// The segment-absolute rendering lowers to the FS-prefixed disp32
|
||||||
|
// absolute, exactly what the 0(FS) spelling encodes.
|
||||||
|
{"MOVQ FS:0, DX", "64488b142500000000"},
|
||||||
|
// The bank-crossing quadword move takes the prefix by direction: the
|
||||||
|
// MMX source carries F3, the XMM source F2.
|
||||||
|
{"MOVQ2DQ M2, X11", "f3440fd6da"},
|
||||||
|
} {
|
||||||
|
src := "TEXT \u00b7k(SB), NOSPLIT, $0\n\t" + tt.text + "\n\tRET\n"
|
||||||
|
f, errs := parser.Parse("k.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Errorf("%s: parse: %v", tt.text, errs[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: assemble: %v", tt.text, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||||
|
// The no-fallthrough rule appends the RET; the pinned instruction is
|
||||||
|
// the prefix.
|
||||||
|
got := hex.EncodeToString(code)
|
||||||
|
if !strings.HasPrefix(got, tt.want) {
|
||||||
|
t.Errorf("%s: bytes %s, want prefix %s", tt.text, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3284
-430
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+752
-171
File diff suppressed because it is too large.
Load diff
+168
-16
@@ -7,11 +7,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
||||||
@@ -33,7 +35,7 @@ func firstTextRISCV(t *testing.T, src string) *ast.Text {
|
|||||||
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,34 @@ TEXT ·imm(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_utypeImmediate pins the U-type immediate semantics byte for byte
|
||||||
|
// against go tool asm (riscv64.s lines 76 to 85): the source immediate is the
|
||||||
|
// raw 20-bit field, not a byte address to divide, and both sign extremes
|
||||||
|
// encode. The range beyond the signed 20 bits is the toolchain's own error.
|
||||||
|
func TestRISCV_utypeImmediate(t *testing.T) {
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·utype(SB), NOSPLIT, $0
|
||||||
|
AUIPC $524287, X10
|
||||||
|
LUI $524287, X15
|
||||||
|
LUI $167, X15
|
||||||
|
AUIPC $-524288, X15
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleRISCVHelper(t, fn)
|
||||||
|
want := "17f5ff7f" + "b7f7ff7f" + "b7770a00" + "97070080"
|
||||||
|
if got := hex.EncodeToString(code[:16]); got != want {
|
||||||
|
t.Errorf("U-type immediates: %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·wide(SB), NOSPLIT, $0
|
||||||
|
AUIPC $524288, X10
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
|
t.Error("AUIPC $524288: expected the 20-bit range error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRISCV_branches(t *testing.T) {
|
func TestRISCV_branches(t *testing.T) {
|
||||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
TEXT ·br(SB), NOSPLIT, $0
|
TEXT ·br(SB), NOSPLIT, $0
|
||||||
@@ -785,6 +815,50 @@ TEXT ·sys(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_privilegedWords pins the privileged ISA slice. The toolchain's
|
||||||
|
// object table carries the encodings (its assembler accepts no mnemonic for
|
||||||
|
// them), so the golden vectors come from the privileged and debug
|
||||||
|
// specifications: SFENCEVMA X10, X11 is 0x12b50073, SRET 0x10200073,
|
||||||
|
// MRET 0x30200073, WFI 0x10500073 and DRET 0x7b200073.
|
||||||
|
func TestRISCV_privilegedWords(t *testing.T) {
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·priv(SB), NOSPLIT, $0
|
||||||
|
SFENCEVMA X10, X11
|
||||||
|
SRET
|
||||||
|
MRET
|
||||||
|
WFI
|
||||||
|
DRET
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleRISCVHelper(t, fn)
|
||||||
|
riscvWants(t, code,
|
||||||
|
0x12B50073, // sfence.vma x10, x11
|
||||||
|
0x10200073, // sret
|
||||||
|
0x30200073, // mret
|
||||||
|
0x10500073, // wfi
|
||||||
|
0x7B200073, // dret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCV_privilegedAliases checks the supervisor spellings SCALL and
|
||||||
|
// SBREAK against the toolchain: both alias ECALL and EBREAK and must come
|
||||||
|
// out byte-identical.
|
||||||
|
func TestRISCV_privilegedAliases(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·alias(SB), NOSPLIT, $0
|
||||||
|
SCALL
|
||||||
|
SBREAK
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "alias_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "alias")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
||||||
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
||||||
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
||||||
@@ -794,7 +868,7 @@ TEXT ·argfp(SB), NOSPLIT, $0
|
|||||||
MOV $arg(FP), X10
|
MOV $arg(FP), X10
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -817,7 +891,7 @@ TEXT ·book(SB), NOSPLIT, $0-8
|
|||||||
MOV X10, ret+0(FP)
|
MOV X10, ret+0(FP)
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -841,7 +915,7 @@ TEXT ·slots(SB), NOSPLIT, $0-0
|
|||||||
JMP -3(PC)
|
JMP -3(PC)
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -869,7 +943,7 @@ TEXT ·wide(SB), NOSPLIT, $0-0
|
|||||||
MOV $0x000fffffffffffda, X5
|
MOV $0x000fffffffffffda, X5
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -929,7 +1003,7 @@ TEXT ·calltest(SB), NOSPLIT, $0
|
|||||||
CALL ext(SB)
|
CALL ext(SB)
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, relocs, _, _, _, err := assembleRISCV(fn)
|
code, _, relocs, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -958,7 +1032,7 @@ TEXT ·calllocal(SB), NOSPLIT, $0
|
|||||||
sub:
|
sub:
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
_, _, _, _, _, _, err := assembleRISCV(fn)
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for CALL to local label, got nil")
|
t.Error("expected error for CALL to local label, got nil")
|
||||||
}
|
}
|
||||||
@@ -992,7 +1066,7 @@ func encodeOneInstrRISCV(t *testing.T, src string, pc int, offsets map[string]in
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
||||||
instr := fn.Body[0].(*ast.Instr)
|
instr := fn.Body[0].(*ast.Instr)
|
||||||
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil, nil)
|
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
||||||
@@ -1041,7 +1115,7 @@ func TestRISCVBranchFarBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
sb.WriteString("done:\n\tRET\n")
|
sb.WriteString("done:\n\tRET\n")
|
||||||
fn := firstTextRISCV(t, sb.String())
|
fn := firstTextRISCV(t, sb.String())
|
||||||
out, _, _, _, _, _, err := assembleRISCV(fn)
|
out, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1067,7 +1141,7 @@ TEXT ·csrhi(SB), NOSPLIT, $0
|
|||||||
CSRRW $4096, X10, X11
|
CSRRW $4096, X10, X11
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
t.Error("expected an out-of-range error for CSR $4096, got none")
|
t.Error("expected an out-of-range error for CSR $4096, got none")
|
||||||
}
|
}
|
||||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
@@ -1075,7 +1149,7 @@ TEXT ·csrmax(SB), NOSPLIT, $0
|
|||||||
CSRRW $4095, X10, X11
|
CSRRW $4095, X10, X11
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
t.Errorf("CSR $4095 must assemble: %v", err)
|
t.Errorf("CSR $4095 must assemble: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1092,7 +1166,7 @@ func TestRISCV_Imm64Rejected(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, src := range cases {
|
for _, src := range cases {
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err == nil {
|
||||||
t.Errorf("%s: expected an out-of-range error, got none", src)
|
t.Errorf("%s: expected an out-of-range error, got none", src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,7 +1179,7 @@ TEXT ·edge(SB), NOSPLIT, $0
|
|||||||
SUB $0x80000000, X12, X13
|
SUB $0x80000000, X12, X13
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
t.Errorf("int32-span immediates must assemble: %v", err)
|
t.Errorf("int32-span immediates must assemble: %v", err)
|
||||||
}
|
}
|
||||||
// Beyond the span the MOV forms materialise the constant like the
|
// Beyond the span the MOV forms materialise the constant like the
|
||||||
@@ -1115,7 +1189,7 @@ TEXT ·pool(SB), NOSPLIT, $0
|
|||||||
MOV $0x123456789, X10
|
MOV $0x123456789, X10
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn, nil); err != nil {
|
||||||
t.Errorf("MOV with a 64-bit immediate must assemble: %v", err)
|
t.Errorf("MOV with a 64-bit immediate must assemble: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1340,3 +1414,81 @@ TEXT ·v(SB), NOSPLIT, $0
|
|||||||
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_rawDataRange pins the WORD and BYTE immediate ranges at the
|
||||||
|
// toolchain's own boundaries: WORD takes [0, 0xffffffff] and BYTE [0, 0xff],
|
||||||
|
// and a value the source spells wider must reach the check whole. The read
|
||||||
|
// once truncated through int32, which rejected WORD $0xffffffff as -1 while
|
||||||
|
// accepting WORD $0x100000000 as 0.
|
||||||
|
func TestRISCV_rawDataRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) ([]byte, error) {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·w(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
t.Run("word bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "WORD $0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\x00\x00\x00\x00" {
|
||||||
|
t.Errorf("WORD $0 = % x", code[:4])
|
||||||
|
}
|
||||||
|
code, err = asmOne(t, "WORD $0xffffffff")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0xffffffff must assemble, as go tool asm accepts it: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\xff\xff\xff\xff" {
|
||||||
|
t.Errorf("WORD $0xffffffff = % x", code[:4])
|
||||||
|
}
|
||||||
|
for _, w := range []string{"$-1", "$0x100000000", "$-4294967296"} {
|
||||||
|
if _, err := asmOne(t, "WORD "+w); err == nil {
|
||||||
|
t.Errorf("WORD %s must be rejected, as go tool asm rejects it", w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("byte bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "BYTE $255")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BYTE $255: %v", err)
|
||||||
|
}
|
||||||
|
if code[0] != 0xff {
|
||||||
|
t.Errorf("BYTE $255 = % x", code[:1])
|
||||||
|
}
|
||||||
|
for _, b := range []string{"$256", "$0x100000001", "$-1"} {
|
||||||
|
if _, err := asmOne(t, "BYTE "+b); err == nil {
|
||||||
|
t.Errorf("BYTE %s must be rejected: out of the byte range", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCV_shiftImmediateRange pins the shift immediate at the toolchain's
|
||||||
|
// validation boundary: 0-63 on the doubleword forms, 0-31 on the word forms,
|
||||||
|
// values beyond rejected on sight rather than masked into the field.
|
||||||
|
func TestRISCV_shiftImmediateRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) error {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·s(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"SLLI $63, X5, X6", "SLLI $0, X5, X6", "SRLI $63, X5", "SRAI $1, X5, X6",
|
||||||
|
"SLLIW $31, X5, X6", "SRLIW $31, X5", "SRAIW $1, X5, X6",
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err != nil {
|
||||||
|
t.Errorf("%s must assemble: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"SLLI $64, X5, X6", "SLLI $-1, X5", "SLLI $0x100000000, X5, X6",
|
||||||
|
"SRLI $64, X5", "SRAI $-1, X5, X6",
|
||||||
|
"SLLIW $32, X5, X6", "SRLIW $-1, X5", "SRAIW $32, X5, X6",
|
||||||
|
} {
|
||||||
|
if err := asmOne(t, s); err == nil {
|
||||||
|
t.Errorf("%s must be rejected, as go tool asm rejects it", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// riscv64ErrorCatalogues are the toolchain's own negative-case files for
|
||||||
|
// riscv64, relative to cmd/asm's testdata: riscv64error.s carries the
|
||||||
|
// preprocess-stage rejections and riscv64validation.s the validate-stage
|
||||||
|
// ones (register banks, compressed constraints, vector shapes).
|
||||||
|
var riscv64ErrorCatalogues = []string{"riscv64error.s", "riscv64validation.s"}
|
||||||
|
|
||||||
|
// riscv64AcceptedErrorShapes lists the toolchain's catalogue spellings gasm
|
||||||
|
// still accepts, each an acceptance superset with a documented reason. The
|
||||||
|
// list only shrinks: every tightening of the encoder moves spellings out of
|
||||||
|
// it, and a spelling reappearing here means a regression.
|
||||||
|
var riscv64AcceptedErrorShapes = []string{}
|
||||||
|
|
||||||
|
// TestRISCVToolchainErrorParity walks the toolchain's riscv64error.s and
|
||||||
|
// riscv64validation.s (Go 1.27) 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
|
||||||
|
// files; the test skips without one or in -short.
|
||||||
|
func TestRISCVToolchainErrorParity(t *testing.T) {
|
||||||
|
goroot := riscv64Goroot(t)
|
||||||
|
dir := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata")
|
||||||
|
allowed := map[string]bool{}
|
||||||
|
for _, s := range riscv64AcceptedErrorShapes {
|
||||||
|
allowed[s] = true
|
||||||
|
}
|
||||||
|
for _, name := range riscv64ErrorCatalogues {
|
||||||
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
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), " ")
|
||||||
|
// A symbol reference needs the file's own declarations, which a
|
||||||
|
// one-line probe cannot carry.
|
||||||
|
if strings.Contains(body, "(SB)") {
|
||||||
|
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 := AssembleFileRISCV(f)
|
||||||
|
if aerr == nil {
|
||||||
|
if !allowed[body] {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", body)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if want != "" && !riscv64DiagEquivalent(want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", body, want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64DiagEquivalent 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 "immediate out of range 0 to 31"
|
||||||
|
// where gasm may spell the field's own name, and gasm prefixes the mnemonic.
|
||||||
|
// Canonicalisation drops the subjects and connectives and compares the
|
||||||
|
// remaining token sequence.
|
||||||
|
func riscv64DiagEquivalent(want, got string) bool {
|
||||||
|
return riscv64DiagSubseq(riscv64DiagTokens(want), riscv64DiagTokens(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64DiagTokens lower-cases a diagnostic, strips punctuation and the
|
||||||
|
// connective tokens, and returns its words. "to" survives only between
|
||||||
|
// digits, where the range wording uses it.
|
||||||
|
func riscv64DiagTokens(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", "an", "for":
|
||||||
|
// subjects and articles carry no constraint
|
||||||
|
case "to":
|
||||||
|
if i > 0 && i+1 < len(fields) && riscv64IsNumeric(fields[i-1]) && riscv64IsNumeric(fields[i+1]) {
|
||||||
|
continue // the range connective
|
||||||
|
}
|
||||||
|
out = append(out, w)
|
||||||
|
default:
|
||||||
|
out = append(out, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64DiagSubseq answers whether want is a subsequence of got.
|
||||||
|
func riscv64DiagSubseq(want, got []string) bool {
|
||||||
|
i := 0
|
||||||
|
for _, w := range got {
|
||||||
|
if i < len(want) && w == want[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i == len(want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64IsNumeric reports whether s parses as a signed decimal number, the
|
||||||
|
// token shape the range connective sits between.
|
||||||
|
func riscv64IsNumeric(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] == '-' || s[0] == '+' {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range s {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64Goroot resolves the live toolchain root, the environment's own value
|
||||||
|
// first, `go env GOROOT` as the fallback.
|
||||||
|
func riscv64Goroot(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64RejectedNames pins the names the toolchain's riscv64 table carries
|
||||||
|
// but refuses to encode: each is a "no encoding for instruction" under every
|
||||||
|
// operand shape GOARCH=riscv64 go tool asm accepts. gasm rejects them too,
|
||||||
|
// and the pair of rejections is the parity this catalogue asserts.
|
||||||
|
var riscv64RejectedNames = []string{"DUFFCOPY", "DUFFZERO", "PCALIGNMAX"}
|
||||||
|
|
||||||
|
// TestRISCVBacklogNameParity walks the audit's known-but-unencodable
|
||||||
|
// backlog: the names go tool asm recognises on riscv64 yet refuses to encode
|
||||||
|
// must be refused by gasm as well.
|
||||||
|
func TestRISCVBacklogNameParity(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live toolchain backlog: skipped in -short mode")
|
||||||
|
}
|
||||||
|
for _, name := range riscv64RejectedNames {
|
||||||
|
if riscv64ProbeAssembles(name + " X5") {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", name)
|
||||||
|
}
|
||||||
|
if riscv64ProbeAssembles(name) {
|
||||||
|
t.Errorf("gasm encodes %s, which the toolchain refuses under every shape", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64ProbeAssembles answers whether a single statement wrapped in its
|
||||||
|
// own function parses and assembles on riscv64.
|
||||||
|
func riscv64ProbeAssembles(body string) bool {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·probe(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("backlogparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
return false // the parser already rejects the line
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileRISCV(f)
|
||||||
|
return aerr == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// riscv64CatalogueSelfCheck guards the catalogue walk itself: every ERROR
|
||||||
|
// line in the two files must yield a probe body, so a formatting change in
|
||||||
|
// the toolchain's files cannot silently empty the parity set.
|
||||||
|
func TestRISCVErrorCatalogueSelfCheck(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("live toolchain catalogue: skipped in -short mode")
|
||||||
|
}
|
||||||
|
goroot := riscv64Goroot(t)
|
||||||
|
dir := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata")
|
||||||
|
total := 0
|
||||||
|
for _, name := range riscv64ErrorCatalogues {
|
||||||
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
n := strings.Count(string(data), "ERROR")
|
||||||
|
total += n
|
||||||
|
if n == 0 {
|
||||||
|
t.Errorf("%s carries no ERROR lines; the catalogue walk is empty", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if total < 1000 {
|
||||||
|
t.Errorf("catalogue carries %d ERROR lines, want at least 1000", total)
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-8
@@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RISC-V frame mapping, matching the Go toolchain's riscv64 backend.
|
// RISC-V frame mapping, matching the Go toolchain's riscv64 backend.
|
||||||
@@ -34,6 +34,11 @@ import (
|
|||||||
type riscvFrameInfo struct {
|
type riscvFrameInfo struct {
|
||||||
autosize int // the real SP adjustment (locals + saved LR)
|
autosize int // the real SP adjustment (locals + saved LR)
|
||||||
|
|
||||||
|
// leaf mirrors the toolchain's cursym.Leaf: the body makes no call, so
|
||||||
|
// the link register still holds the caller's address. GETCALLERPC reads
|
||||||
|
// it there; a framed body reads the prologue's save at 0(SP) instead.
|
||||||
|
leaf bool
|
||||||
|
|
||||||
// Stack-split guard state: the toolchain emits the check for every
|
// Stack-split guard state: the toolchain emits the check for every
|
||||||
// non-NOSPLIT function whose autosize is nonzero (a zero autosize is
|
// non-NOSPLIT function whose autosize is nonzero (a zero autosize is
|
||||||
// "effectively NOSPLIT"); unlike amd64 and arm64 there is no leaf
|
// "effectively NOSPLIT"); unlike amd64 and arm64 there is no leaf
|
||||||
@@ -44,12 +49,12 @@ type riscvFrameInfo struct {
|
|||||||
|
|
||||||
// riscvComputeFrame derives the frame layout for a TEXT function.
|
// riscvComputeFrame derives the frame layout for a TEXT function.
|
||||||
func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
||||||
frame := frameSize(t)
|
leaf := riscvIsLeaf(t)
|
||||||
if frame != 0 || !riscvIsLeaf(t) {
|
if frame := frameSize(t); frame != 0 || !leaf {
|
||||||
// FixedFrameSize = 8: space for the saved link register. A
|
// FixedFrameSize = 8: space for the saved link register. A
|
||||||
// zero-frame non-leaf function still opens an 8-byte frame for LR.
|
// zero-frame non-leaf function still opens an 8-byte frame for LR.
|
||||||
autosize := frame + 8
|
autosize := frame + 8
|
||||||
fi := riscvFrameInfo{autosize: autosize}
|
fi := riscvFrameInfo{autosize: autosize, leaf: leaf}
|
||||||
if !hasNoSplitFlag(t) {
|
if !hasNoSplitFlag(t) {
|
||||||
fi.needSplit = true
|
fi.needSplit = true
|
||||||
switch {
|
switch {
|
||||||
@@ -63,7 +68,9 @@ func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
|||||||
}
|
}
|
||||||
return fi
|
return fi
|
||||||
}
|
}
|
||||||
return riscvFrameInfo{}
|
// A zero-frame leaf: no prologue, no guard, and the link register still
|
||||||
|
// holding the caller's address.
|
||||||
|
return riscvFrameInfo{leaf: leaf}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasNoSplitFlag reports whether the TEXT directive carries NOSPLIT.
|
// hasNoSplitFlag reports whether the TEXT directive carries NOSPLIT.
|
||||||
@@ -96,8 +103,10 @@ func riscvIsLeaf(t *ast.Text) bool {
|
|||||||
case "JALR":
|
case "JALR":
|
||||||
// JALR rd, offset(rs1) links when the destination register (the
|
// JALR rd, offset(rs1) links when the destination register (the
|
||||||
// first operand) is X1; JALR rs1, rd links when the second
|
// first operand) is X1; JALR rs1, rd links when the second
|
||||||
// register is X1; JALR offset(rs1) always links to X1.
|
// register is X1; JALR offset(rs1) always links to X1. A bare
|
||||||
if len(in.Operands) == 1 {
|
// operand-less spelling is malformed and encoding rejects it;
|
||||||
|
// conservatively count it as a link so the frame stays honest.
|
||||||
|
if len(in.Operands) <= 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if isMemOperand(in.Operands[1]) {
|
if isMemOperand(in.Operands[1]) {
|
||||||
@@ -344,7 +353,7 @@ func riscvGuard(fi riscvFrameInfo) ([]byte, Reloc, error) {
|
|||||||
off := int32(fi.autosize - stackSmall)
|
off := int32(fi.autosize - stackSmall)
|
||||||
mov := encodeRISCVLoadImm(7, off)
|
mov := encodeRISCVLoadImm(7, off)
|
||||||
out = append(out, mov...)
|
out = append(out, mov...)
|
||||||
addiLen := riscvItypeImmediateSize("ADDI", -off)
|
addiLen := riscvItypeImmediateSize("ADDI", 7, 2, -off)
|
||||||
out = append(out, wordLE(riscvBType(riscvEnc{0x63, 0x06, 0x00}, 2, 7, int32(addiLen+8)))...)
|
out = append(out, wordLE(riscvBType(riscvEnc{0x63, 0x06, 0x00}, 2, 7, int32(addiLen+8)))...)
|
||||||
addi, err := encodeRISCVItypeImmediate("ADDI", riscvEnc{0x13, 0x0, 0x00}, 7, 2, -off)
|
addi, err := encodeRISCVItypeImmediate("ADDI", riscvEnc{0x13, 0x0, 0x00}, 7, 2, -off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRISCVFrameSpadjAndLines checks that a framed function records its
|
// TestRISCVFrameSpadjAndLines checks that a framed function records its
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGOObjectRISCVCallReloc checks that CALL sym(SB) emits a single JAL
|
// TestGOObjectRISCVCallReloc checks that CALL sym(SB) emits a single JAL
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRISCVBigMemOffset_Differential proves the memory-offset expansion
|
||||||
|
// against the oracle: a load or store whose offset leaves the signed 12-bit
|
||||||
|
// span materialises the high part in the assembler's temporary register
|
||||||
|
// (C.LUI, or LUI beyond its six-bit immediate) followed by C.ADD of the base,
|
||||||
|
// and accesses through it, exactly as the toolchain's instructionsForLoad and
|
||||||
|
// instructionsForStore synthesise. The battery spans both sides of the
|
||||||
|
// 12-bit edge, both int32 boundaries, the compressed and uncompressed LUI
|
||||||
|
// halves, every width family and the MOV spellings, under a prologue too so
|
||||||
|
// the size accounting keeps the layout honest.
|
||||||
|
func TestRISCVBigMemOffset_Differential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·bigmem(SB), NOSPLIT, $16
|
||||||
|
LD 2047(X6), X5 // single word: the edge that still fits
|
||||||
|
LD 2048(X6), X5 // hi 1, lo -2048
|
||||||
|
LD 4096(X6), X5 // hi 1, lo 0
|
||||||
|
SD X5, 8192(X6) // store side of the same split
|
||||||
|
FLD 4096(X6), F5 // FP widths share the expansion
|
||||||
|
FSD F5, 8192(X6)
|
||||||
|
MOVB 4097(X6), X8 // the MOV widths route through the same load
|
||||||
|
MOVHU 4098(X6), X9
|
||||||
|
MOV X7, 16384(X6) // and the same store
|
||||||
|
LD 1048576(X6), X10 // hi 256: LUI, not C.LUI
|
||||||
|
LD -4097(X6), X11 // hi -1, lo -1
|
||||||
|
SD X11, -8192(X6)
|
||||||
|
LD 2147483647(X6), X12 // int32 upper boundary
|
||||||
|
LD -2147483648(X6), X13 // int32 lower boundary
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
path := writeRISCVSrc(t, "bigmem_riscv64.s", src)
|
||||||
|
assertRISCVDifferential(t, path, src, "bigmem")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCVBigMemOffsetFrame_Differential proves the expansion under the
|
||||||
|
// stack-split guard: a function that opens a frame and calls out gets the
|
||||||
|
// guard, the prologue and the epilogue ahead of the body, so every body
|
||||||
|
// offset now depends on the expansion's size accounting having kept the
|
||||||
|
// layout in step with the emitted bytes.
|
||||||
|
func TestRISCVBigMemOffsetFrame_Differential(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·bigframe(SB), $16
|
||||||
|
CALL extcal(SB)
|
||||||
|
LD 4096(X6), X5
|
||||||
|
SD X5, 4096(X6)
|
||||||
|
FLD 8192(X6), F5
|
||||||
|
FSD F5, 8192(X6)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
path := writeRISCVSrc(t, "bigframe_riscv64.s", src)
|
||||||
|
assertRISCVDifferential(t, path, src, "bigframe")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCVBigMemOffsetRejections pins the honest rejections the expansion
|
||||||
|
// cannot carry: a constant beyond the signed 32-bit span is the toolchain's
|
||||||
|
// "constant too large" (its Split32BitImmediate has no wider split), and a
|
||||||
|
// JALR displacement beyond the signed 12-bit span is its I-type range check,
|
||||||
|
// because the jump would land somewhere else entirely.
|
||||||
|
func TestRISCVBigMemOffsetRejections(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "store beyond int32",
|
||||||
|
src: "\tSD X5, 4294967295(X6)\n",
|
||||||
|
want: "SD: constant 4294967295 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "load below int32",
|
||||||
|
src: "\tLD -2147483649(X6), X5\n",
|
||||||
|
want: "LD: constant -2147483649 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MOV load beyond int32",
|
||||||
|
src: "\tMOV 4294967296(X6), X5\n",
|
||||||
|
want: "MOV: constant 4294967296 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MOV store beyond int32",
|
||||||
|
src: "\tMOV X5, 4294967296(X6)\n",
|
||||||
|
want: "MOV: constant 4294967296 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FP store beyond int32",
|
||||||
|
src: "\tFSD F5, 4294967296(X6)\n",
|
||||||
|
want: "FSD: constant 4294967296 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FP load beyond int32",
|
||||||
|
src: "\tFLD 4294967296(X6), F5\n",
|
||||||
|
want: "FLD: constant 4294967296 too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JALR displacement above 12 bits",
|
||||||
|
src: "\tJALR 4096(X7)\n",
|
||||||
|
want: "JALR: signed immediate 4096 must be in range [-2048, 2047] (12 bits)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JALR displacement below 12 bits",
|
||||||
|
src: "\tJALR X5, -2049(X7)\n",
|
||||||
|
want: "JALR: signed immediate -2049 must be in range [-2048, 2047] (12 bits)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\n\nTEXT ·r(SB), NOSPLIT, $0\n"+tc.src+"\tRET\n")
|
||||||
|
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("source assembled, want rejection %q", tc.want)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.want) {
|
||||||
|
t.Errorf("error %q does not carry %q", err.Error(), tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 295 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user