Compare commits

..
7 Commits
Author SHA1 Message Date
petrbalvin 75e9fd771b feat(parser): split plain statements on semicolons in the raw parse
Test / test (push) Failing after 2m30s
Assisted-by: GLM 5.3 Flash
2026-09-20 19:15:35 +02:00
petrbalvin 863926abd6 test(verify): register the loong64 vector kernels
Assisted-by: GLM 5.3 Flash
2026-09-20 19:15:05 +02:00
petrbalvin 241e7256f6 fix(arm64): reject bare BTI with a diagnostic and accept the full family
Assisted-by: GLM 5.3 Flash
2026-09-20 19:15:05 +02:00
petrbalvin 6556b85abf feat(asm): symbol-valued DATA, division slash in symbols and plain semicolons
Assisted-by: GLM 5.3 Flash
2026-09-20 19:15:05 +02:00
petrbalvin 289cabe993 feat(loong64): encode the full LSX and LASX table
Assisted-by: GLM 5.3 Flash
2026-09-20 19:14:43 +02:00
petrbalvin d6cf7cfa44 fix(format): keep statement separators and canonical macro bodies
Assisted-by: GLM 5.3 Flash
2026-09-20 19:14:43 +02:00
petrbalvin 4cc2f0eba5 feat(cmd): generate go_asm.h for package-context assembly
Assisted-by: GLM 5.3 Flash
2026-09-20 19:14:43 +02:00
49 changed files with 5708 additions and 48 deletions
+101
View File
@@ -245,3 +245,104 @@ func main() {
t.Error("binary does not contain expected symbol") t.Error("binary does not contain expected symbol")
} }
} }
// TestGOObjectAARCH64DataSymbolLink does for symbol-valued DATA fields what
// the rt0 files do ("DATA _rt0…lib+0(SB)/8, $_rt0…lib(SB)"): the gasm object
// carries an R_ADDR against the file's own TEXT symbol, the toolchain links
// it, and the binary is checked for the symbol (no arm64 host to run it).
func TestGOObjectAARCH64DataSymbolLink(t *testing.T) {
goBin, err := exec.LookPath("go")
if err != nil {
t.Skip("no Go toolchain available")
}
dir := t.TempDir()
asmSrc := `#include "textflag.h"
GLOBL entry(SB), NOPTR, $8
DATA entry+0(SB)/8, $·keepme(SB)
TEXT ·keepme(SB), NOSPLIT, $0-0
RET
TEXT ·entryptr(SB), NOSPLIT, $0-8
MOVD entry+0(SB), R4
MOVD R4, ret+0(FP)
RET
`
if err := os.WriteFile(filepath.Join(dir, "main_arm64.s"), []byte(asmSrc), 0o644); err != nil {
t.Fatal(err)
}
mainSrc := `package main
func keepme()
func entryptr() uintptr
func main() {
if entryptr() == 0 {
panic("the entry word is empty")
}
}
`
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module a64dlink\n\ngo 1.21\n"), 0o644); err != nil {
t.Fatal(err)
}
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
build.Dir = dir
build.Env = append(os.Environ(), "GOARCH=arm64")
buildLog, err := build.CombinedOutput()
if err != nil {
t.Fatalf("baseline build: %v\n%s", err, buildLog)
}
var work, linkLine, asmObj string
for line := range strings.SplitSeq(string(buildLog), "\n") {
switch {
case strings.HasPrefix(line, "WORK="):
work = strings.TrimPrefix(line, "WORK=")
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_arm64.s") && !strings.Contains(line, "-gensymabis"):
asmObj = fieldAfter(line, "-o")
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
linkLine = line
}
}
if work == "" || asmObj == "" || 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"))
if err != nil {
t.Fatal(err)
}
f, errs := parser.Parse("main_arm64.s", string(src))
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFileARM64(f)
if err != nil {
t.Fatalf("AssembleFileARM64: %v", err)
}
gasmObj, err := img.GOObjectAARCH64("a64dlink", "main_arm64.s")
if err != nil {
t.Fatalf("GOObjectAARCH64: %v", err)
}
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
t.Fatalf("write gasm object: %v", err)
}
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 {
t.Fatal(err)
}
if !strings.Contains(string(binData), "keepme") {
t.Error("binary does not contain the keepme symbol")
}
}
+7 -1
View File
@@ -2856,8 +2856,14 @@ func encodeARM64Sys(mnem string, ops []*ast.Operand) ([]byte, error) {
} }
return a64wordLE(0xd503201f | uint32(v)<<5), nil return a64wordLE(0xd503201f | uint32(v)<<5), nil
case "BTI": case "BTI":
// The toolchain requires the landing-pad kind: bare BTI is
// rejected ("missing operand"), and only the uppercase C/J/JC
// spellings assemble (0xd503245f/49f/4df).
if len(ops) != 1 {
return nil, fmt.Errorf("%s expects C, J, or JC", mnem)
}
op := operandRegName(ops[0]) op := operandRegName(ops[0])
base, ok := map[string]uint32{"C": 0xd503245f}[op] base, ok := map[string]uint32{"C": 0xd503245f, "J": 0xd503249f, "JC": 0xd50324df}[op]
if !ok { if !ok {
return nil, fmt.Errorf("%s: unknown kind %q", mnem, op) return nil, fmt.Errorf("%s: unknown kind %q", mnem, op)
} }
+30
View File
@@ -675,6 +675,36 @@ func TestArm64AcquireRelease(t *testing.T) {
} }
} }
// TestArm64BTI pins the landing-pad family against the toolchain words:
// only the uppercase C/J/JC spellings assemble, and bare BTI is a
// diagnostic, never a panic.
func TestArm64BTI(t *testing.T) {
got := arm64Words(t, "\tBTI C\n\tBTI J\n\tBTI JC\n")
want := []uint32{
0xd503245f, // BTI C
0xd503249f, // BTI J
0xd50324df, // BTI JC
0xd65f03c0, // RET
}
if len(got) != len(want) {
t.Fatalf("got %d words, want %d", len(got), len(want))
}
for i := range want {
if got[i] != want[i] {
t.Errorf("word %d = %#x, want %#x", i, got[i], want[i])
}
}
for _, src := range []string{"\tBTI\n", "\tBTI c\n", "\tBTI B\n"} {
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+src+"\tRET\n")
if len(errs) > 0 {
continue
}
if _, err := AssembleFileARM64(f); err == nil {
t.Errorf("BTI spelling %q should be rejected, as go tool asm rejects it", src)
}
}
}
// TestArm64System pins BRK, SVC, the barriers, cache maintenance and the // TestArm64System pins BRK, SVC, the barriers, cache maintenance and the
// system register accesses. // system register accesses.
func TestArm64System(t *testing.T) { func TestArm64System(t *testing.T) {
+65 -10
View File
@@ -43,6 +43,9 @@ const (
stInfoShift = 4 stInfoShift = 4
rX8664PC32 = 2 rX8664PC32 = 2
// R_X86_64_32 (debug/elf): the absolute 32-bit address of a symbol, the
// R_ADDR shape a 4-byte DATA field carries.
rX8664Abs32 = 10
// R_X86_64_TPOFF32 (debug/elf): the local-exec TLS offset the stack // R_X86_64_TPOFF32 (debug/elf): the local-exec TLS offset the stack
// guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation. // guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation.
rX8664TPOFF32 = 23 rX8664TPOFF32 = 23
@@ -158,6 +161,50 @@ func (img *Image) ELFObject() ([]byte, error) {
} }
} }
// The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8,
// $other(SB)") become .rela.data entries: an absolute relocation of the
// DATA line's width at the field's data-section offset, S + A with no
// PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields
// cannot hold an address, so they are refused rather than truncated.
var dataRelas []elfRela
for _, d := range img.DataSyms {
for _, r := range d.Relocs {
idx, ok := symIdx[r.Name]
if !ok {
return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name)
}
var typ uint32
switch r.Siz {
case 8:
typ = rX8664Abs64
case 4:
typ = rX8664Abs32
default:
return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz)
}
dataRelas = append(dataRelas, elfRela{
off: uint64(d.Offset + r.Off),
sym: idx,
typ: typ,
addend: r.Addend,
})
}
}
// Section presence: .rela.text only when there are code relocations,
// .rela.data only when a DATA line holds a symbol value.
hasRela := len(relas) > 0
hasDataRela := len(dataRelas) > 0
nSections := 6 // NULL, .text, .data, .symtab, .strtab, .shstrtab
if hasRela {
nSections++
}
if hasDataRela {
nSections++
}
secSymtab, secStrtab := 3, 4
secShstr := nSections - 1
// Serialise the string tables. // Serialise the string tables.
stNames := newElfStrtab() stNames := newElfStrtab()
for _, s := range syms { for _, s := range syms {
@@ -167,19 +214,13 @@ func (img *Image) ELFObject() ([]byte, error) {
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
stSections.add(n) stSections.add(n)
} }
if hasDataRela {
stSections.add(".rela.data")
}
for _, n := range dwarfSectionNames { for _, n := range dwarfSectionNames {
stSections.add(n) stSections.add(n)
} }
// Section presence: .rela.text only when there are relocations.
hasRela := len(relas) > 0
nSections := 6 // NULL, .text, .data, .symtab, .strtab, .shstrtab
if hasRela {
nSections = 7
}
secSymtab, secStrtab := 3, 4
secShstr := nSections - 1
// Lay the file out: header, section data, section headers. // Lay the file out: header, section data, section headers.
var out []byte var out []byte
out = append(out, make([]byte, 64)...) // ELF header, filled last out = append(out, make([]byte, 64)...) // ELF header, filled last
@@ -214,7 +255,7 @@ func (img *Image) ELFObject() ([]byte, error) {
strtabOff := len(out) strtabOff := len(out)
out = append(out, stNames.bytes()...) out = append(out, stNames.bytes()...)
var relaOff int var relaOff, relaDataOff int
if hasRela { if hasRela {
align(8) align(8)
relaOff = len(out) relaOff = len(out)
@@ -226,6 +267,17 @@ func (img *Image) ELFObject() ([]byte, error) {
out = append(out, b[:]...) out = append(out, b[:]...)
} }
} }
if hasDataRela {
align(8)
relaDataOff = len(out)
for _, r := range dataRelas {
var b [24]byte
le.PutUint64(b[0:], r.off)
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
le.PutUint64(b[16:], uint64(r.addend))
out = append(out, b[:]...)
}
}
shstrOff := len(out) shstrOff := len(out)
out = append(out, stSections.bytes()...) out = append(out, stSections.bytes()...)
@@ -284,6 +336,9 @@ func (img *Image) ELFObject() ([]byte, error) {
if hasRela { if hasRela {
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
} }
if hasDataRela {
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
}
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
// DWARF section headers; their indices follow the write order. // DWARF section headers; their indices follow the write order.
+108
View File
@@ -750,3 +750,111 @@ func readFormSkip(t *testing.T, r *ulebIter, form uint64) {
t.Fatalf("unsupported form %#x", form) t.Fatalf("unsupported form %#x", form)
} }
} }
// TestELFObjectDataRelocation checks that a symbol-valued DATA field ("DATA
// s+0(SB)/8, $other(SB)") reaches the ELF object as a .rela.data entry: an
// absolute 64-bit relocation at the field's offset within .data, against
// the named symbol, external targets included.
func TestELFObjectDataRelocation(t *testing.T) {
f, errs := parser.Parse("t_amd64.s", `#include "textflag.h"
TEXT ·Keep(SB), NOSPLIT, $0-8
RET
GLOBL holder(SB), NOPTR, $24
DATA holder+0(SB)/8, $·Keep+5(SB)
DATA holder+8(SB)/8, $holder(SB)
DATA holder+16(SB)/8, $extvar(SB)
`)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFile(f)
if err != nil {
t.Fatalf("assemble: %v", err)
}
obj, err := img.ELFObject()
if err != nil {
t.Fatalf("ELFObject: %v", err)
}
ef, err := elf.NewFile(bytes.NewReader(obj))
if err != nil {
t.Fatalf("parse emitted object: %v", err)
}
defer ef.Close()
relaData := ef.Section(".rela.data")
if relaData == nil {
t.Fatal("missing .rela.data section")
}
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
}
if ef.Sections[relaData.Info].Name != ".data" {
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
}
relas, err := relaData.Data()
if err != nil {
t.Fatal(err)
}
var got []struct {
off uint64
sym uint32
typ uint32
addend int64
}
for i := 0; i+24 <= len(relas); i += 24 {
got = append(got, struct {
off uint64
sym uint32
typ uint32
addend int64
}{
off: binary.LittleEndian.Uint64(relas[i:]),
// r_info packs the type in the low dword and the symbol index
// in the high dword.
typ: binary.LittleEndian.Uint32(relas[i+8:]),
sym: binary.LittleEndian.Uint32(relas[i+12:]),
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
})
}
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
syms, err := ef.Symbols()
if err != nil {
t.Fatal(err)
}
name := func(idx uint32) string {
if idx >= 1 && int(idx) <= len(syms) {
return syms[idx-1].Name
}
return ""
}
// The offsets are data-section-relative: the field's DATA offset plus
// the symbol's position in .data (the layout aligns each symbol to 16).
base := uint64(0)
for _, d := range img.DataSyms {
if d.Name == "holder" {
base = uint64(d.Offset)
}
}
want := []struct {
off uint64
typ uint32
addend int64
target string
}{
{off: base + 0, typ: uint32(elf.R_X86_64_64), addend: 5, target: "Keep"},
{off: base + 8, typ: uint32(elf.R_X86_64_64), addend: 0, target: "holder"},
{off: base + 16, typ: uint32(elf.R_X86_64_64), addend: 0, target: "extvar"},
}
if len(got) != len(want) {
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
}
for i, w := range want {
g := got[i]
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
}
if n := name(g.sym); n != w.target {
t.Errorf("entry %d names %q, want %q", i, n, w.target)
}
}
}
+55
View File
@@ -463,6 +463,61 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
symRelocs[si] = append(symRelocs[si], rec[:]...) symRelocs[si] = append(symRelocs[si], rec[:]...)
} }
} }
// The data symbols' own relocations: the symbol-valued DATA fields
// ("DATA s+0(SB)/8, $other(SB)"). The toolchain patches each field
// with the target's absolute address through an R_ADDR of the DATA
// line's width, on every architecture (the code relocations are
// per-architecture PC-relative shapes; a data pointer word is not), so
// this mapping bypasses relocField. The definitions were appended in
// DataSyms order, so data symbol i is definition index i.
for i, d := range img.DataSyms {
for _, r := range d.Relocs {
if r.Kind != RelAddr {
return nil, fmt.Errorf("GOOBJ emission: data symbol %q carries a non-data relocation", d.Name)
}
var rec [23]byte
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
rec[4] = r.Siz
binary.LittleEndian.PutUint16(rec[5:], relocAddr)
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
switch {
case r.External && r.Name == goobjBuiltinMorestack:
binary.LittleEndian.PutUint32(rec[15:], pkgIdxBuiltin)
binary.LittleEndian.PutUint32(rec[19:], goobjBuiltinMorestackNoctxt)
case r.External:
pkg, name := splitQualified(r.Name)
if pkg == "" {
return nil, fmt.Errorf("GOOBJ emission: external symbol %q has no package prefix", r.Name)
}
pIdx, ok := extPkgIdx[pkg]
if !ok {
return nil, fmt.Errorf("GOOBJ emission: package %q not resolved", pkg)
}
sIdx, ok := extSymIdx[pkg+"·"+name]
if !ok {
return nil, fmt.Errorf("GOOBJ emission: symbol %s·%s not resolved", pkg, name)
}
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
default:
if di, ok := defIdx[r.Name]; ok {
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
break
}
// A DATA field may hold the address of a TEXT function of
// the same file (the rt0 lib entry spelling), which is a
// non-package definition.
ni, isText := textNpIdx[r.Name]
if !isText {
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
}
binary.LittleEndian.PutUint32(rec[15:], pkgIdxNone)
binary.LittleEndian.PutUint32(rec[19:], uint32(ni))
}
symRelocs[i] = append(symRelocs[i], rec[:]...)
}
}
// The DWARF symbols' own relocations (the function address references). // The DWARF symbols' own relocations (the function address references).
for _, ds := range dwarfRelocs { for _, ds := range dwarfRelocs {
for _, r := range ds.relocs { for _, r := range ds.relocs {
+217
View File
@@ -0,0 +1,217 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package asm
import (
"bytes"
"encoding/binary"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
)
// The differential kernels for the DATA-path and front-end gaps are kept in
// testdata/verify beside the campaign's other kernels; the verify package's
// suites are not open to the asm package, so this test is their runner: each
// kernel assembles through gasm and through go tool asm, and the functions'
// bytes must agree with the relocation sites masked on both sides.
// toolAsmObject assembles path with the installed toolchain's assembler for
// goarch ("" = the host) and returns the object bytes.
func toolAsmObject(t *testing.T, path, goarch string) []byte {
t.Helper()
goBin, err := exec.LookPath("go")
if err != nil {
t.Skip("no Go toolchain available")
}
out, err := exec.Command(goBin, "env", "GOROOT").Output()
if err != nil {
t.Fatalf("go env GOROOT: %v", err)
}
includeDir := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include")
pkg := strings.TrimSuffix(filepath.Base(path), ".s")
pkg = strings.TrimSuffix(pkg, "_amd64")
pkg = strings.TrimSuffix(pkg, "_arm64")
objPath := filepath.Join(t.TempDir(), "oracle.o")
cmd := exec.Command(goBin, "tool", "asm", "-I", includeDir, "-p", pkg, "-o", objPath, path)
if goarch != "" {
environ := os.Environ()
env := make([]string, 0, len(environ)+1)
for _, e := range environ {
if !strings.HasPrefix(e, "GOARCH=") {
env = append(env, e)
}
}
cmd.Env = append(env, "GOARCH="+goarch)
}
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("go tool asm %s: %v\n%s", filepath.Base(path), err, out)
}
obj, err := os.ReadFile(objPath)
if err != nil {
t.Fatal(err)
}
return obj
}
// oracleFuncCode extracts the non-package TEXT functions' code bytes from a
// toolchain object, keyed by the name the object records (pkg.name).
func oracleFuncCode(t *testing.T, obj []byte) map[string][]byte {
t.Helper()
v := openGoobj(t, obj)
le := binary.LittleEndian
const symSize = 21
nps := v.syms(blkNonpkgdef)
data := v.blk(blkData)
didx := v.blk(blkDataIdx)
preceding := 0
for _, bi := range []int{blkSymdef, blkHashed64def, blkHasheddef} {
preceding += len(v.blk(bi)) / symSize
}
total := preceding + len(nps)
out := make(map[string][]byte, len(nps))
for i, s := range nps {
if s.typ != kindSTEXT {
continue
}
start := le.Uint32(didx[4*(preceding+i):])
end := uint32(len(data))
if preceding+i+1 < total {
end = le.Uint32(didx[4*(preceding+i+1):])
}
out[s.name] = data[start:end]
}
return out
}
// maskCode zeroes every relocation field, the way the toolchain's object
// leaves them for the linker.
func maskCode(code []byte, relocs []Reloc) []byte {
for _, r := range relocs {
for j := r.Off; j < r.Off+4 && j < len(code); j++ {
code[j] = 0
}
}
return code
}
// code assembles src for amd64 and returns the image's code bytes.
func code(path, src string) []byte {
f, errs := parser.Parse(path, src)
if len(errs) > 0 {
return nil
}
img, err := AssembleFile(f)
if err != nil {
return nil
}
return img.Code
}
// TestDifferentialKernels pins the new kernels against the oracle.
func TestDifferentialKernels(t *testing.T) {
if runtime.GOARCH != "amd64" {
t.Skip("the amd64 kernels assume an amd64 host assembler default")
}
for _, k := range []struct {
path string
goarch string
arm64 bool
}{
{filepath.Join("..", "testdata", "verify", "datarel_amd64.s"), "", false},
{filepath.Join("..", "testdata", "verify", "divslash_amd64.s"), "", false},
{filepath.Join("..", "testdata", "verify", "semicolons_amd64.s"), "", false},
{filepath.Join("..", "testdata", "verify", "datarel_arm64.s"), "arm64", true},
{filepath.Join("..", "testdata", "verify", "divslash_arm64.s"), "arm64", true},
} {
t.Run(filepath.Base(k.path), func(t *testing.T) {
src, err := os.ReadFile(k.path)
if err != nil {
t.Fatalf("read: %v", err)
}
f, errs := parser.Parse(k.path, string(src))
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
var img *Image
if k.arm64 {
img, err = AssembleFileARM64(f)
} else {
img, err = AssembleFile(f)
}
if err != nil {
t.Fatalf("assemble: %v", err)
}
gt := oracleFuncCode(t, toolAsmObject(t, k.path, k.goarch))
// The oracle keys its functions by the qualified object name
// (pkg.name); match on the local part.
byLocal := make(map[string][]byte, len(gt))
for name, code := range gt {
if _, after, ok := strings.Cut(name, "."); ok {
name = after
}
byLocal[name] = code
}
matched := 0
for _, fn := range img.Funcs {
gasmCode := maskCode(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
goCode, ok := byLocal[fn.Name]
if !ok {
t.Errorf("%s: not in ground truth (%d functions: %v)", fn.Name, len(gt), keysOf(byLocal))
continue
}
goCode = maskCode(append([]byte(nil), goCode...), fn.Relocs)
cmpLen := min(len(goCode), len(gasmCode))
if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) {
t.Errorf("%s: MISMATCH gasm=%d go=%d bytes\ngasm %x\ngo %x", fn.Name, len(gasmCode), len(goCode), gasmCode, goCode)
continue
}
for _, b := range goCode[len(gasmCode):] {
if b != 0 {
t.Errorf("%s: non-zero trailing bytes in go tool asm output", fn.Name)
break
}
}
matched++
t.Logf("%s: MATCH (%d bytes)", fn.Name, len(gasmCode))
}
if matched == 0 {
t.Fatal("no functions matched")
}
})
}
}
func keysOf(m map[string][]byte) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
// TestSemicolonSpellingParity pins that the ';' statement separator changes
// nothing about the encoding: the one-line spelling assembles to exactly the
// bytes of the same statements written one per line.
func TestSemicolonSpellingParity(t *testing.T) {
for _, tt := range []struct{ one, two string }{
{"\tROLQ $3, DI; ROLQ $13, DI\n", "\tROLQ $3, DI\n\tROLQ $13, DI\n"},
{"\tREP; MOVSQ\n", "\tREP\n\tMOVSQ\n"},
{"\tXORQ AX, AX; XORQ CX, CX\n", "\tXORQ AX, AX\n\tXORQ CX, CX\n"},
} {
one := code("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n"+tt.one+"\tRET\n")
two := code("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n"+tt.two+"\tRET\n")
if !bytes.Equal(one, two) {
t.Errorf("semicolon spelling %q: %x, want the two-line bytes %x", tt.one, one, two)
}
}
}
+68 -3
View File
@@ -103,6 +103,7 @@ const (
RelArm64Branch // R_CALLARM64 (BL instruction) RelArm64Branch // R_CALLARM64 (BL instruction)
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair) RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
RelLoong64Branch // R_CALLLOONG64 (BL instruction) RelLoong64Branch // R_CALLLOONG64 (BL instruction)
RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field
) )
type Reloc struct { type Reloc struct {
@@ -112,13 +113,17 @@ type Reloc struct {
// Addend select the target: the symbol plus the byte offset. An // Addend select the target: the symbol plus the byte offset. An
// External relocation names a symbol no GLOBL in the file defines; // External relocation names a symbol no GLOBL in the file defines;
// the object-file emitters carry it into the output's relocation // the object-file emitters carry it into the output's relocation
// table. // table. Siz is the width of the patched field and is set only for
// data-field relocations (RelAddr, Off relative to the data symbol),
// whose width is the DATA line's; code relocations take their width
// from the architecture's instruction encoding.
Off int Off int
After int After int
Name string Name string
Addend int64 Addend int64
External bool External bool
Kind RelocKind Kind RelocKind
Siz uint8
} }
// DataSymbol describes one GLOBL symbol laid out in the data section. // DataSymbol describes one GLOBL symbol laid out in the data section.
@@ -130,6 +135,11 @@ type DataSymbol struct {
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
Dupok bool // the DUPOK flag: duplicate-OK Dupok bool // the DUPOK flag: duplicate-OK
// Relocs carries the symbol-valued DATA initialisers ("DATA s+0(SB)/8,
// $other(SB)"): fields of this symbol's data that hold another symbol's
// address, resolved by the linker. Off is relative to the symbol's
// data start.
Relocs []Reloc
} }
// Bytes returns the whole image: code, then data. // Bytes returns the whole image: code, then data.
@@ -257,6 +267,22 @@ func AssembleFile(f *ast.File) (*Image, error) {
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc) img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
} }
} }
// The data symbols' symbol-valued DATA fields resolve the same way the
// code references do: a name the file defines (GLOBL or TEXT) stays an
// internal reference the emitters resolve, anything else is external.
// img.DataSyms was laid out in dataSyms order, so the indexes line up.
for i := range img.DataSyms {
for _, r := range dataSyms[i].relocs {
reloc := r
if _, ok := img.Symbols[reloc.Name]; !ok {
if _, ok := textOff[reloc.Name]; !ok {
reloc.External = true
externals[reloc.Name] = true
}
}
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
}
}
for name := range externals { for name := range externals {
img.Externals = append(img.Externals, name) img.Externals = append(img.Externals, name)
} }
@@ -429,6 +455,23 @@ func markExternals(img *Image, dataSyms []dataSym) {
} }
} }
} }
// The declared data symbols carry the file's own relocations (the
// symbol-valued DATA fields); the layouts appended img.DataSyms in
// dataSyms order, so the indexes line up. The trailing entries (the
// pooled arm64 literals) have no source relocations.
for i := range img.DataSyms {
if i >= len(dataSyms) {
break
}
for _, r := range dataSyms[i].relocs {
reloc := r
if !known[reloc.Name] {
reloc.External = true
externals[reloc.Name] = true
}
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
}
}
for name := range externals { for name := range externals {
img.Externals = append(img.Externals, name) img.Externals = append(img.Externals, name)
} }
@@ -444,6 +487,9 @@ type dataSym struct {
static bool static bool
rodata bool rodata bool
dupok bool dupok bool
// relocs are the symbol-valued DATA fields, in declaration order; Off
// is relative to the symbol's data start.
relocs []Reloc
} }
// collectData gathers the file's static symbols (GLOBL) and their initial // collectData gathers the file's static symbols (GLOBL) and their initial
@@ -511,8 +557,8 @@ func collectData(f *ast.File) ([]dataSym, error) {
if !ok { if !ok {
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name) return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
} }
if dd.Value == nil || !dd.Value.Imm.HasVal { if dd.Value == nil {
return nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name) return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
} }
w := dd.Width w := dd.Width
switch w { switch w {
@@ -525,6 +571,25 @@ func collectData(f *ast.File) ([]dataSym, error) {
if off < 0 || off+int64(w) > int64(len(buf)) { if off < 0 || off+int64(w) > int64(len(buf)) {
return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf)) return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
} }
// A symbol value ("DATA s+0(SB)/8, $other(SB)", the rt0 spelling)
// leaves the field zero and records a relocation against the named
// symbol: the linker patches the absolute address at this data
// offset. The toolchain emits the same shape, an R_ADDR of the
// DATA width with the value's offset as the addend, on every
// architecture.
if sym := dd.Value.Imm.Sym; !dd.Value.Imm.HasVal && sym != nil {
syms[i].relocs = append(syms[i].relocs, Reloc{
Off: int(off),
Name: sym.Name,
Addend: sym.Offset,
Kind: RelAddr,
Siz: uint8(w),
})
continue
}
if !dd.Value.Imm.HasVal {
return nil, fmt.Errorf("DATA %q: value must be an integer immediate or a symbol address", dd.Name.Name)
}
v := dd.Value.Imm.Val v := dd.Value.Imm.Val
if dd.Value.Imm.Neg { if dd.Value.Imm.Neg {
v = -v v = -v
+252
View File
@@ -4,6 +4,10 @@
package asm package asm
import ( import (
"encoding/binary"
"os"
"os/exec"
"path/filepath"
"strings" "strings"
"testing" "testing"
@@ -166,3 +170,251 @@ func TestCollectDataNumericFlags(t *testing.T) {
} }
} }
} }
// TestCollectDataSymbolValue covers the symbol-valued DATA field ("DATA
// s+0(SB)/8, $other(SB)", the rt0 spelling): the field stays zero in the
// image and the relocation is recorded against the named symbol, whatever
// the file defines (a TEXT function, a GLOBL) or leaves external.
func TestCollectDataSymbolValue(t *testing.T) {
src := `#include "textflag.h"
TEXT ·Keep(SB), NOSPLIT, $0-8
MOVQ target+0(FP), AX
RET
GLOBL holder(SB), NOPTR, $32
DATA holder+0(SB)/8, $·Keep(SB)
DATA holder+8(SB)/8, $·Keep+5(SB)
DATA holder+16(SB)/8, $holder(SB)
GLOBL spare(SB), NOPTR, $8
DATA spare+0(SB)/8, $extvar(SB)
`
f, errs := parser.Parse("f_amd64.s", src)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFile(f)
if err != nil {
t.Fatalf("assemble: %v", err)
}
byName := map[string]DataSymbol{}
for _, d := range img.DataSyms {
byName[d.Name] = d
}
want := []struct {
sym string
off int
name string
addend int64
ext bool
}{
{"holder", 0, "Keep", 0, false},
{"holder", 8, "Keep", 5, false},
{"holder", 16, "holder", 0, false},
{"spare", 0, "extvar", 0, true},
}
var flat []struct {
sym string
r Reloc
}
for _, d := range img.DataSyms {
for _, r := range d.Relocs {
flat = append(flat, struct {
sym string
r Reloc
}{d.Name, r})
}
}
if len(flat) != len(want) {
t.Fatalf("data relocations = %d, want %d", len(flat), len(want))
}
for i, w := range want {
g := flat[i]
r := g.r
if g.sym != w.sym {
t.Errorf("relocation %d sits on %q, want %q", i, g.sym, w.sym)
continue
}
if r.Off != w.off || r.Name != w.name || r.Addend != w.addend || r.External != w.ext {
t.Errorf("relocation %d = {+%d %q addend %d ext %v}, want {+%d %q addend %d ext %v}",
i, r.Off, r.Name, r.Addend, r.External, w.off, w.name, w.addend, w.ext)
}
if r.Kind != RelAddr {
t.Errorf("relocation %d kind = %v, want RelAddr", i, r.Kind)
}
if r.Siz != 8 {
t.Errorf("relocation %d siz = %d, want 8", i, r.Siz)
}
}
// The fields themselves stay zero: only the linker fills them.
for _, b := range img.Data {
if b != 0 {
t.Fatal("data section is not all zero before relocation")
}
}
if len(img.Externals) != 1 || img.Externals[0] != "extvar" {
t.Errorf("Externals = %v, want [extvar]", img.Externals)
}
}
// TestGOObjectDataSymbolReloc pins the GOOBJ record a symbol-valued DATA
// field produces, against the shape the toolchain emits for the same
// source: an R_ADDR of the DATA width at the field offset, pkgIdxNone plus
// the non-package definition index when the target is the file's own TEXT
// function (the rt0 lib entry spelling).
func TestGOObjectDataSymbolReloc(t *testing.T) {
f, errs := parser.Parse("f_amd64.s", `#include "textflag.h"
TEXT ·Keep(SB), NOSPLIT, $0-8
RET
GLOBL holder(SB), NOPTR, $16
DATA holder+0(SB)/8, $·Keep+5(SB)
`)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFile(f)
if err != nil {
t.Fatalf("assemble: %v", err)
}
obj, err := img.GOObject("main", "f_amd64.s")
if err != nil {
t.Fatalf("GOObject: %v", err)
}
v := openGoobj(t, obj)
// Walk every relocation record; the data record is the one of Siz 8
// and type R_ADDR.
var off, add int64
var pkg, sym uint32
found := false
for data := v.blk(blkReloc); len(data) >= 23; data = data[23:] {
if data[4] != 8 || binary.LittleEndian.Uint16(data[5:]) != relocAddr {
continue
}
found = true
off = int64(int32(binary.LittleEndian.Uint32(data[0:])))
add = int64(binary.LittleEndian.Uint64(data[7:]))
pkg = binary.LittleEndian.Uint32(data[15:])
sym = binary.LittleEndian.Uint32(data[19:])
break
}
if !found {
t.Fatal("no data relocation record in the object")
}
if off != 0 || add != 5 {
t.Errorf("data reloc = {off %d addend %d}, want {off 0 addend 5}", off, add)
}
if pkg != pkgIdxNone {
t.Errorf("data reloc pkg = %#x, want pkgIdxNone (the TEXT function)", pkg)
}
// The function's non-package definition index: the four pc tables
// precede it, so index 4.
if sym != 4 {
t.Errorf("data reloc sym = %d, want 4", sym)
}
}
// TestGOObjectDataSymbolLink is the end-to-end proof for symbol-valued DATA
// fields: the gasm object is substituted for the toolchain's and re-linked,
// then executed, and the linked data word must hold the real address of the
// function the DATA line named (runtime.FuncForPC identifies it).
func TestGOObjectDataSymbolLink(t *testing.T) {
goBin, err := exec.LookPath("go")
if err != nil {
t.Skip("no Go toolchain available")
}
dir := t.TempDir()
asmSrc := `#include "textflag.h"
GLOBL entry(SB), NOPTR, $8
DATA entry+0(SB)/8, $·keepme(SB)
TEXT ·keepme(SB), NOSPLIT, $0-0
RET
TEXT ·entryptr(SB), NOSPLIT, $0-8
MOVQ entry+0(SB), AX
MOVQ AX, ret+0(FP)
RET
`
if err := os.WriteFile(filepath.Join(dir, "main_amd64.s"), []byte(asmSrc), 0o644); err != nil {
t.Fatal(err)
}
mainSrc := `package main
import "runtime"
func keepme()
func entryptr() uintptr
func main() {
pc := entryptr()
fn := runtime.FuncForPC(pc)
if fn == nil {
panic("the entry word does not point at a function")
}
if fn.Name() != "main.keepme" {
panic("the entry word points at " + fn.Name())
}
}
`
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module dlink\n\ngo 1.21\n"), 0o644); err != nil {
t.Fatal(err)
}
// Capture the build: the package archive's asm object and the link line.
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
build.Dir = dir
buildLog, err := build.CombinedOutput()
if err != nil {
t.Fatalf("baseline build: %v\n%s", err, buildLog)
}
var work, linkLine, asmObj string
for line := range strings.SplitSeq(string(buildLog), "\n") {
switch {
case strings.HasPrefix(line, "WORK="):
work = strings.TrimPrefix(line, "WORK=")
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_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.
src, err := os.ReadFile(filepath.Join(dir, "main_amd64.s"))
if err != nil {
t.Fatal(err)
}
f, errs := parser.Parse("main_amd64.s", string(src))
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFile(f)
if err != nil {
t.Fatalf("AssembleFile: %v", err)
}
gasmObj, err := img.GOObject("dlink", "main_amd64.s")
if err != nil {
t.Fatalf("GOObject: %v", err)
}
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
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
// data word.
out, err := exec.Command(filepath.Join(dir, "prog")).CombinedOutput()
if err != nil {
t.Fatalf("linked program failed: %v\n%s", err, out)
}
}
+58
View File
@@ -372,6 +372,10 @@ func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
return len(loong64Return(fi)) return len(loong64Return(fi))
} }
switch mnem { switch mnem {
case "END", "FUNCDATA", "PCDATA":
return 0 // bookkeeping statements contribute no bytes
case "GETCALLERPC":
return 4 // or rd, r1, r0
case "TEQ", "TNE": case "TEQ", "TNE":
return 8 // bne/beq over the BREAK, then BREAK return 8 // bne/beq over the BREAK, then BREAK
case "PRELDX": case "PRELDX":
@@ -480,6 +484,36 @@ 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 "END", "FUNCDATA", "PCDATA", "GETCALLERPC":
// The assembler's bookkeeping statements. END, FUNCDATA and PCDATA
// contribute no bytes, the same shapes GOARCH=loong64 go tool asm
// accepts and emits nothing for; GETCALLERPC reads the caller's
// address out of R1 (RA) as or rd, r1, r0.
switch mnem {
case "END":
if len(ops) != 0 {
return nil, fmt.Errorf("END expects no operands, got %d", len(ops))
}
return nil, nil
case "FUNCDATA":
if len(ops) != 2 || !isImmOperand(ops[0]) {
return nil, fmt.Errorf("FUNCDATA expects $n, sym(SB)")
}
return nil, nil
case "PCDATA":
if len(ops) != 2 || !isImmOperand(ops[0]) || !isImmOperand(ops[1]) {
return nil, fmt.Errorf("PCDATA expects $n, $n")
}
return nil, nil
}
if len(ops) != 1 || isMemOperand(ops[0]) || loong64RegClass(operandRegName(ops[0])) != l64ClsGR {
return nil, fmt.Errorf("GETCALLERPC expects a general register")
}
rd := loong64RegNum(operandRegName(ops[0]))
if rd < 0 {
return nil, fmt.Errorf("GETCALLERPC: invalid register operand")
}
return l64wordLE(l64rrr(l64movRegTable["MOVV"].op, 0, 1, rd)), nil
case "NEGW", "NEGV": case "NEGW", "NEGV":
// The integer negation pseudo is a subtract from zero: // The integer negation pseudo is a subtract from zero:
// NEGW src, dst → sub.w r0, src, dst. // NEGW src, dst → sub.w r0, src, dst.
@@ -2084,6 +2118,30 @@ func encodeLOONG64Vector(instr *ast.Instr, mnem string, fi loong64FrameInfo) ([]
return l64wordLE(l64rr(l64InstrTable[mnem].op, vj, fcc)), true, nil return l64wordLE(l64rr(l64InstrTable[mnem].op, vj, fcc)), true, nil
} }
// Four-register forms (vshuf.b): INSTR va, vk, vj, vd.
if l64Vec4R[mnem] {
if len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
}
va, err := vec(ops[0])
if err != nil {
return nil, true, err
}
vk, err := vec(ops[1])
if err != nil {
return nil, true, err
}
vj, err := vec(ops[2])
if err != nil {
return nil, true, err
}
vd, err := vec(ops[3])
if err != nil {
return nil, true, err
}
return l64wordLE(l64InstrTable[mnem].op | uint32(va&0x1f)<<15 | uint32(vk&0x1f)<<10 | uint32(vj&0x1f)<<5 | uint32(vd&0x1f)), true, nil
}
// Three-register forms: INSTR vk, vj, vd or INSTR vk, vd (vj = vd). // Three-register forms: INSTR vk, vj, vd or INSTR vk, vd (vj = vd).
if len(ops) != 2 && len(ops) != 3 { if len(ops) != 2 && len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops)) return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
+449 -12
View File
@@ -278,6 +278,7 @@ const (
l64Fpreld // preld (2RI12 + 5-bit hint) l64Fpreld // preld (2RI12 + 5-bit hint)
l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd l64Fvvv // 3R vector (LSX/LASX): op | vk<<10 | vj<<5 | vd
l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc l64Fvcf // vector-to-condition: op | subop<<10 | vj<<5 | fcc
l64Fvvvv // 4R vector shuffle: op | va<<15 | vk<<10 | vj<<5 | vd
) )
// l64Enc is one instruction's encoding: its bit layout (format) and the // l64Enc is one instruction's encoding: its bit layout (format) and the
@@ -336,6 +337,10 @@ var l64VecImmInfo = map[string]l64VecImmEnc{}
// vpcnt.v). // vpcnt.v).
var l64Vec2R = map[string]bool{} var l64Vec2R = map[string]bool{}
// l64Vec4R marks the four-operand vector mnemonics (INSTR va, vk, vj, vd,
// such as vshuf.b).
var l64Vec4R = map[string]bool{}
// l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants (pre-shifted to bit // l64VmovqOps holds the VMOVQ/XVMOVQ opcode constants (pre-shifted to bit
// 15), read off `go tool objdump` of GOARCH=loong64 `go tool asm` kernels. // 15), read off `go tool objdump` of GOARCH=loong64 `go tool asm` kernels.
type l64VmovqEnc struct { type l64VmovqEnc struct {
@@ -445,6 +450,14 @@ func init() {
// bank is the FP registers (the toolchain spells it `FFINTDV F0, F1`), // bank is the FP registers (the toolchain spells it `FFINTDV F0, F1`),
// so the entry stays on the 2R integer/FP format. // so the entry stays on the 2R integer/FP format.
"FFINTDV": 0x474a << 10, "FFINTDV": 0x474a << 10,
// The rest of the scalar conversions (all F-bank, 2R).
"FFINTFW": 0x4744 << 10, // ffint.s.w
"FFINTFV": 0x4746 << 10, // ffint.s.l
"FFINTDW": 0x4748 << 10, // ffint.d.w
"FTINTWF": 0x46c1 << 10, // ftint.w.s
"FTINTWD": 0x46c2 << 10, // ftint.w.d
"FTINTVF": 0x46c9 << 10, // ftint.l.s
"FTINTVD": 0x46ca << 10, // ftint.l.d
} }
for m, op := range rr { for m, op := range rr {
l64InstrTable[m] = l64Enc{format: l64Frr, op: op} l64InstrTable[m] = l64Enc{format: l64Frr, op: op}
@@ -568,6 +581,12 @@ func init() {
"AMADDDBW": 0x070D4 << 15, "AMADDDBV": 0x070D5 << 15, "AMADDDBW": 0x070D4 << 15, "AMADDDBV": 0x070D5 << 15,
"AMANDDBW": 0x070D6 << 15, "AMANDDBV": 0x070D7 << 15, "AMANDDBW": 0x070D6 << 15, "AMANDDBV": 0x070D7 << 15,
"AMORDBW": 0x070D8 << 15, "AMORDBV": 0x070D9 << 15, "AMORDBW": 0x070D8 << 15, "AMORDBV": 0x070D9 << 15,
// The remaining _dbar exchange variants (loong64enc1.s).
"AMXORDBW": 0x070DA << 15, "AMXORDBV": 0x070DB << 15,
"AMMAXDBW": 0x070DC << 15, "AMMAXDBV": 0x070DD << 15,
"AMMINDBW": 0x070DE << 15, "AMMINDBV": 0x070DF << 15,
"AMMAXDBWU": 0x070E0 << 15, "AMMAXDBVU": 0x070E1 << 15,
"AMMINDBWU": 0x070E2 << 15, "AMMINDBVU": 0x070E3 << 15,
} }
for m, op := range am { for m, op := range am {
l64InstrTable[m] = l64Enc{format: l64Fam, op: op} l64InstrTable[m] = l64Enc{format: l64Fam, op: op}
@@ -590,6 +609,240 @@ func init() {
"XVANDV": {0xEA4C << 15, true}, "XVXORV": {0xEA4E << 15, true}, "XVANDV": {0xEA4C << 15, true}, "XVXORV": {0xEA4E << 15, true},
"XVSEQB": {0xE800 << 15, true}, "XVSEQV": {0xE803 << 15, true}, "XVSEQB": {0xE800 << 15, true}, "XVSEQV": {0xE803 << 15, true},
} }
// The integer and FP add/subtract families: [X]VADD and [X]VSUB by lane
// width, plus the [X]VSADD/[X]VSSUB saturating pairs.
// Opcodes transcribed from the toolchain's loong64enc1.s.
addsub := map[string]l64Vec3Enc{
"VADDB": {0xE014 << 15, false}, "VADDH": {0xE015 << 15, false},
"VADDD": {0xE262 << 15, false}, "VADDF": {0xE261 << 15, false},
"VADDQ": {0xE25A << 15, false},
"VSUBB": {0xE018 << 15, false}, "VSUBH": {0xE019 << 15, false},
"VSUBW": {0xE01A << 15, false}, "VSUBV": {0xE01B << 15, false},
"VSUBQ": {0xE25B << 15, false},
"VSUBF": {0xE265 << 15, false}, "VSUBD": {0xE266 << 15, false},
"VSADDB": {0xE08C << 15, false}, "VSADDH": {0xE08D << 15, false},
"VSADDW": {0xE08E << 15, false}, "VSADDV": {0xE08F << 15, false},
"VSADDBU": {0xE094 << 15, false}, "VSADDHU": {0xE095 << 15, false},
"VSADDWU": {0xE096 << 15, false}, "VSADDVU": {0xE097 << 15, false},
"VSSUBB": {0xE090 << 15, false}, "VSSUBH": {0xE091 << 15, false},
"VSSUBW": {0xE092 << 15, false}, "VSSUBV": {0xE093 << 15, false},
"VSSUBBU": {0xE098 << 15, false}, "VSSUBHU": {0xE099 << 15, false},
"VSSUBWU": {0xE09A << 15, false}, "VSSUBVU": {0xE09B << 15, false},
"XVADDB": {0xE814 << 15, true}, "XVADDH": {0xE815 << 15, true},
"XVADDW": {0xE816 << 15, true},
"XVADDD": {0xEA62 << 15, true}, "XVADDF": {0xEA61 << 15, true},
"XVADDQ": {0xEA5A << 15, true},
"XVSUBB": {0xE818 << 15, true}, "XVSUBH": {0xE819 << 15, true},
"XVSUBW": {0xE81A << 15, true}, "XVSUBV": {0xE81B << 15, true},
"XVSUBQ": {0xEA5B << 15, true},
"XVSUBF": {0xEA65 << 15, true}, "XVSUBD": {0xEA66 << 15, true},
"XVSADDB": {0xE88C << 15, true}, "XVSADDH": {0xE88D << 15, true},
"XVSADDW": {0xE88E << 15, true}, "XVSADDV": {0xE88F << 15, true},
"XVSADDBU": {0xE894 << 15, true}, "XVSADDHU": {0xE895 << 15, true},
"XVSADDWU": {0xE896 << 15, true}, "XVSADDVU": {0xE897 << 15, true},
"XVSSUBB": {0xE890 << 15, true}, "XVSSUBH": {0xE891 << 15, true},
"XVSSUBW": {0xE892 << 15, true}, "XVSSUBV": {0xE893 << 15, true},
"XVSSUBBU": {0xE898 << 15, true}, "XVSSUBHU": {0xE899 << 15, true},
"XVSSUBWU": {0xE89A << 15, true}, "XVSSUBVU": {0xE89B << 15, true},
}
// The multiply families: plain and high-half [X]VMUL/[X]VMUH, the
// widening [X]VMULW{EV,OD} ladder and its accumulating [X]VMADDW twins,
// plus the [X]VMADD/[X]VMSUB fused multiply-add and the [X]VDIV/[X]VMOD
// divide and modulo pairs.
muldiv := map[string]l64Vec3Enc{
"VMULB": {0xE108 << 15, false}, "VMULH": {0xE109 << 15, false},
"VMULW": {0xE10A << 15, false}, "VMULV": {0xE10B << 15, false},
"VMUHB": {0xE10C << 15, false}, "VMUHH": {0xE10D << 15, false},
"VMUHW": {0xE10E << 15, false}, "VMUHV": {0xE10F << 15, false},
"VMUHBU": {0xE110 << 15, false}, "VMUHHU": {0xE111 << 15, false},
"VMUHWU": {0xE112 << 15, false}, "VMUHVU": {0xE113 << 15, false},
"VMULWEVHB": {0xE120 << 15, false}, "VMULWEVWH": {0xE121 << 15, false},
"VMULWEVVW": {0xE122 << 15, false}, "VMULWEVQV": {0xE123 << 15, false},
"VMULWODHB": {0xE124 << 15, false}, "VMULWODWH": {0xE125 << 15, false},
"VMULWODVW": {0xE126 << 15, false}, "VMULWODQV": {0xE127 << 15, false},
"VMULWEVHBU": {0xE130 << 15, false}, "VMULWEVWHU": {0xE131 << 15, false},
"VMULWEVVWU": {0xE132 << 15, false}, "VMULWEVQVU": {0xE133 << 15, false},
"VMULWODHBU": {0xE134 << 15, false}, "VMULWODWHU": {0xE135 << 15, false},
"VMULWODVWU": {0xE136 << 15, false}, "VMULWODQVU": {0xE137 << 15, false},
"VMULWEVHBUB": {0xE140 << 15, false}, "VMULWEVWHUH": {0xE141 << 15, false},
"VMULWEVVWUW": {0xE142 << 15, false}, "VMULWEVQVUV": {0xE143 << 15, false},
"VMULWODHBUB": {0xE144 << 15, false}, "VMULWODWHUH": {0xE145 << 15, false},
"VMULWODVWUW": {0xE146 << 15, false}, "VMULWODQVUV": {0xE147 << 15, false},
"VMADDB": {0xE150 << 15, false}, "VMADDH": {0xE151 << 15, false},
"VMADDW": {0xE152 << 15, false}, "VMADDV": {0xE153 << 15, false},
"VMSUBB": {0xE154 << 15, false}, "VMSUBH": {0xE155 << 15, false},
"VMSUBW": {0xE156 << 15, false}, "VMSUBV": {0xE157 << 15, false},
"VMADDWEVHB": {0xE158 << 15, false}, "VMADDWEVWH": {0xE159 << 15, false},
"VMADDWEVVW": {0xE15A << 15, false}, "VMADDWEVQV": {0xE15B << 15, false},
"VMADDWODHB": {0xE15C << 15, false}, "VMADDWODWH": {0xE15D << 15, false},
"VMADDWODVW": {0xE15E << 15, false}, "VMADDWODQV": {0xE15F << 15, false},
"VMADDWEVHBU": {0xE168 << 15, false}, "VMADDWEVWHU": {0xE169 << 15, false},
"VMADDWEVVWU": {0xE16A << 15, false}, "VMADDWEVQVU": {0xE16B << 15, false},
"VMADDWODHBU": {0xE16C << 15, false}, "VMADDWODWHU": {0xE16D << 15, false},
"VMADDWODVWU": {0xE16E << 15, false}, "VMADDWODQVU": {0xE16F << 15, false},
"VMADDWEVHBUB": {0xE178 << 15, false}, "VMADDWEVWHUH": {0xE179 << 15, false},
"VMADDWEVVWUW": {0xE17A << 15, false}, "VMADDWEVQVUV": {0xE17B << 15, false},
"VMADDWODHBUB": {0xE17C << 15, false}, "VMADDWODWHUH": {0xE17D << 15, false},
"VMADDWODVWUW": {0xE17E << 15, false}, "VMADDWODQVUV": {0xE17F << 15, false},
"VDIVB": {0xE1C0 << 15, false}, "VDIVH": {0xE1C1 << 15, false},
"VDIVW": {0xE1C2 << 15, false}, "VDIVV": {0xE1C3 << 15, false},
"VMODB": {0xE1C4 << 15, false}, "VMODH": {0xE1C5 << 15, false},
"VMODW": {0xE1C6 << 15, false}, "VMODV": {0xE1C7 << 15, false},
"VDIVBU": {0xE1C8 << 15, false}, "VDIVHU": {0xE1C9 << 15, false},
"VDIVWU": {0xE1CA << 15, false}, "VDIVVU": {0xE1CB << 15, false},
"VMODBU": {0xE1CC << 15, false}, "VMODHU": {0xE1CD << 15, false},
"VMODWU": {0xE1CE << 15, false}, "VMODVU": {0xE1CF << 15, false},
"VMULF": {0xE271 << 15, false}, "VMULD": {0xE272 << 15, false},
"VDIVF": {0xE275 << 15, false}, "VDIVD": {0xE276 << 15, false},
"XVMULB": {0xE908 << 15, true}, "XVMULH": {0xE909 << 15, true},
"XVMULW": {0xE90A << 15, true}, "XVMULV": {0xE90B << 15, true},
"XVMUHB": {0xE90C << 15, true}, "XVMUHH": {0xE90D << 15, true},
"XVMUHW": {0xE90E << 15, true}, "XVMUHV": {0xE90F << 15, true},
"XVMUHBU": {0xE910 << 15, true}, "XVMUHHU": {0xE911 << 15, true},
"XVMUHWU": {0xE912 << 15, true}, "XVMUHVU": {0xE913 << 15, true},
"XVMULWEVHB": {0xE920 << 15, true}, "XVMULWEVWH": {0xE921 << 15, true},
"XVMULWEVVW": {0xE922 << 15, true}, "XVMULWEVQV": {0xE923 << 15, true},
"XVMULWODHB": {0xE924 << 15, true}, "XVMULWODWH": {0xE925 << 15, true},
"XVMULWODVW": {0xE926 << 15, true}, "XVMULWODQV": {0xE927 << 15, true},
"XVMULWEVHBU": {0xE930 << 15, true}, "XVMULWEVWHU": {0xE931 << 15, true},
"XVMULWEVVWU": {0xE932 << 15, true}, "XVMULWEVQVU": {0xE933 << 15, true},
"XVMULWODHBU": {0xE934 << 15, true}, "XVMULWODWHU": {0xE935 << 15, true},
"XVMULWODVWU": {0xE936 << 15, true}, "XVMULWODQVU": {0xE937 << 15, true},
"XVMULWEVHBUB": {0xE940 << 15, true}, "XVMULWEVWHUH": {0xE941 << 15, true},
"XVMULWEVVWUW": {0xE942 << 15, true}, "XVMULWEVQVUV": {0xE943 << 15, true},
"XVMULWODHBUB": {0xE944 << 15, true}, "XVMULWODWHUH": {0xE945 << 15, true},
"XVMULWODVWUW": {0xE946 << 15, true}, "XVMULWODQVUV": {0xE947 << 15, true},
"XVMADDB": {0xE950 << 15, true}, "XVMADDH": {0xE951 << 15, true},
"XVMADDW": {0xE952 << 15, true}, "XVMADDV": {0xE953 << 15, true},
"XVMSUBB": {0xE954 << 15, true}, "XVMSUBH": {0xE955 << 15, true},
"XVMSUBW": {0xE956 << 15, true}, "XVMSUBV": {0xE957 << 15, true},
"XVMADDWEVHB": {0xE958 << 15, true}, "XVMADDWEVWH": {0xE959 << 15, true},
"XVMADDWEVVW": {0xE95A << 15, true}, "XVMADDWEVQV": {0xE95B << 15, true},
"XVMADDWODHB": {0xE95C << 15, true}, "XVMADDWODWH": {0xE95D << 15, true},
"XVMADDWODVW": {0xE95E << 15, true}, "XVMADDWODQV": {0xE95F << 15, true},
"XVMADDWEVHBU": {0xE968 << 15, true}, "XVMADDWEVWHU": {0xE969 << 15, true},
"XVMADDWEVVWU": {0xE96A << 15, true}, "XVMADDWEVQVU": {0xE96B << 15, true},
"XVMADDWODHBU": {0xE96C << 15, true}, "XVMADDWODWHU": {0xE96D << 15, true},
"XVMADDWODVWU": {0xE96E << 15, true}, "XVMADDWODQVU": {0xE96F << 15, true},
"XVMADDWEVHBUB": {0xE978 << 15, true}, "XVMADDWEVWHUH": {0xE979 << 15, true},
"XVMADDWEVVWUW": {0xE97A << 15, true}, "XVMADDWEVQVUV": {0xE97B << 15, true},
"XVMADDWODHBUB": {0xE97C << 15, true}, "XVMADDWODWHUH": {0xE97D << 15, true},
"XVMADDWODVWUW": {0xE97E << 15, true}, "XVMADDWODQVUV": {0xE97F << 15, true},
"XVDIVB": {0xE9C0 << 15, true}, "XVDIVH": {0xE9C1 << 15, true},
"XVDIVW": {0xE9C2 << 15, true}, "XVDIVV": {0xE9C3 << 15, true},
"XVMODB": {0xE9C4 << 15, true}, "XVMODH": {0xE9C5 << 15, true},
"XVMODW": {0xE9C6 << 15, true}, "XVMODV": {0xE9C7 << 15, true},
"XVDIVBU": {0xE9C8 << 15, true}, "XVDIVHU": {0xE9C9 << 15, true},
"XVDIVWU": {0xE9CA << 15, true}, "XVDIVVU": {0xE9CB << 15, true},
"XVMODBU": {0xE9CC << 15, true}, "XVMODHU": {0xE9CD << 15, true},
"XVMODWU": {0xE9CE << 15, true}, "XVMODVU": {0xE9CF << 15, true},
"XVMULF": {0xEA71 << 15, true}, "XVMULD": {0xEA72 << 15, true},
"XVDIVF": {0xEA75 << 15, true}, "XVDIVD": {0xEA76 << 15, true},
}
// The lane-wise shifts and rotates (three-register forms; the immediate
// forms live in l64VecImmInfo), the interleave families, the bit
// clear/set/rev register forms, the remaining logic and compare
// spellings, the widening add/subtract ladder and the vector FP
// arithmetic.
vecmisc := map[string]l64Vec3Enc{
"VSLLB": {0xE1D0 << 15, false}, "VSLLH": {0xE1D1 << 15, false},
"VSLLW": {0xE1D2 << 15, false}, "VSLLV": {0xE1D3 << 15, false},
"VSRLB": {0xE1D4 << 15, false}, "VSRLH": {0xE1D5 << 15, false},
"VSRLW": {0xE1D6 << 15, false}, "VSRLV": {0xE1D7 << 15, false},
"VSRAH": {0xE1D9 << 15, false}, "VSRAW": {0xE1DA << 15, false},
"VSRAV": {0xE1DB << 15, false},
"VROTRB": {0xE1DC << 15, false}, "VROTRH": {0xE1DD << 15, false},
"VROTRV": {0xE1DF << 15, false},
"VILVLB": {0xE234 << 15, false}, "VILVLH": {0xE235 << 15, false},
"VILVLW": {0xE236 << 15, false}, "VILVLV": {0xE237 << 15, false},
"VILVHB": {0xE238 << 15, false}, "VILVHH": {0xE239 << 15, false},
"VILVHW": {0xE23A << 15, false}, "VILVHV": {0xE23B << 15, false},
"VBITCLRB": {0xE218 << 15, false}, "VBITCLRH": {0xE219 << 15, false},
"VBITCLRW": {0xE21A << 15, false}, "VBITCLRV": {0xE21B << 15, false},
"VBITSETB": {0xE21C << 15, false}, "VBITSETH": {0xE21D << 15, false},
"VBITSETW": {0xE21E << 15, false}, "VBITSETV": {0xE21F << 15, false},
"VBITREVB": {0xE220 << 15, false}, "VBITREVH": {0xE221 << 15, false},
"VBITREVW": {0xE222 << 15, false}, "VBITREVV": {0xE223 << 15, false},
"VORV": {0xE24D << 15, false}, "VNORV": {0xE24F << 15, false},
"VANDNV": {0xE250 << 15, false}, "VORNV": {0xE251 << 15, false},
"VSEQH": {0xE001 << 15, false}, "VSEQW": {0xE002 << 15, false},
"VSLTB": {0xE00C << 15, false}, "VSLTH": {0xE00D << 15, false},
"VSLTW": {0xE00E << 15, false}, "VSLTV": {0xE00F << 15, false},
"VSLTBU": {0xE010 << 15, false}, "VSLTHU": {0xE011 << 15, false},
"VSLTWU": {0xE012 << 15, false}, "VSLTVU": {0xE013 << 15, false},
"VADDWEVHB": {0xE03C << 15, false}, "VADDWEVWH": {0xE03D << 15, false},
"VADDWEVVW": {0xE03E << 15, false}, "VADDWEVQV": {0xE03F << 15, false},
"VSUBWEVHB": {0xE040 << 15, false}, "VSUBWEVWH": {0xE041 << 15, false},
"VSUBWEVVW": {0xE042 << 15, false}, "VSUBWEVQV": {0xE043 << 15, false},
"VADDWODHB": {0xE044 << 15, false}, "VADDWODWH": {0xE045 << 15, false},
"VADDWODVW": {0xE046 << 15, false}, "VADDWODQV": {0xE047 << 15, false},
"VSUBWODHB": {0xE048 << 15, false}, "VSUBWODWH": {0xE049 << 15, false},
"VSUBWODVW": {0xE04A << 15, false}, "VSUBWODQV": {0xE04B << 15, false},
"VSUBWEVHBU": {0xE060 << 15, false}, "VSUBWEVWHU": {0xE061 << 15, false},
"VSUBWEVVWU": {0xE062 << 15, false}, "VSUBWEVQVU": {0xE063 << 15, false},
"VADDWEVHBU": {0xE05C << 15, false}, "VADDWEVWHU": {0xE05D << 15, false},
"VADDWEVVWU": {0xE05E << 15, false}, "VADDWEVQVU": {0xE05F << 15, false},
"VADDWODHBU": {0xE064 << 15, false}, "VADDWODWHU": {0xE065 << 15, false},
"VADDWODVWU": {0xE066 << 15, false}, "VADDWODQVU": {0xE067 << 15, false},
"VSUBWODHBU": {0xE068 << 15, false}, "VSUBWODWHU": {0xE069 << 15, false},
"VSUBWODVWU": {0xE06A << 15, false}, "VSUBWODQVU": {0xE06B << 15, false},
"VSHUFH": {0xE2F5 << 15, false}, "VSHUFW": {0xE2F6 << 15, false},
"VSHUFV": {0xE2F7 << 15, false},
"XVSLLB": {0xE9D0 << 15, true}, "XVSLLH": {0xE9D1 << 15, true},
"XVSLLW": {0xE9D2 << 15, true}, "XVSLLV": {0xE9D3 << 15, true},
"XVSRLB": {0xE9D4 << 15, true}, "XVSRLH": {0xE9D5 << 15, true},
"XVSRLW": {0xE9D6 << 15, true}, "XVSRLV": {0xE9D7 << 15, true},
"XVSRAB": {0xE9D8 << 15, true}, "XVSRAH": {0xE9D9 << 15, true},
"XVSRAW": {0xE9DA << 15, true}, "XVSRAV": {0xE9DB << 15, true},
"XVROTRB": {0xE9DC << 15, true}, "XVROTRH": {0xE9DD << 15, true},
"XVROTRW": {0xE9DE << 15, true}, "XVROTRV": {0xE9DF << 15, true},
"XVILVLB": {0xEA34 << 15, true}, "XVILVLH": {0xEA35 << 15, true},
"XVILVLW": {0xEA36 << 15, true}, "XVILVLV": {0xEA37 << 15, true},
"XVILVHB": {0xEA38 << 15, true}, "XVILVHH": {0xEA39 << 15, true},
"XVILVHW": {0xEA3A << 15, true}, "XVILVHV": {0xEA3B << 15, true},
"XVBITCLRB": {0xEA18 << 15, true}, "XVBITCLRH": {0xEA19 << 15, true},
"XVBITCLRW": {0xEA1A << 15, true}, "XVBITCLRV": {0xEA1B << 15, true},
"XVBITSETB": {0xEA1C << 15, true}, "XVBITSETH": {0xEA1D << 15, true},
"XVBITSETW": {0xEA1E << 15, true}, "XVBITSETV": {0xEA1F << 15, true},
"XVBITREVB": {0xEA20 << 15, true}, "XVBITREVH": {0xEA21 << 15, true},
"XVBITREVW": {0xEA22 << 15, true}, "XVBITREVV": {0xEA23 << 15, true},
"XVORV": {0xEA4D << 15, true}, "XVNORV": {0xEA4F << 15, true},
"XVANDNV": {0xEA50 << 15, true}, "XVORNV": {0xEA51 << 15, true},
"XVSEQH": {0xE801 << 15, true}, "XVSEQW": {0xE802 << 15, true},
"XVSLTB": {0xE80C << 15, true}, "XVSLTH": {0xE80D << 15, true},
"XVSLTW": {0xE80E << 15, true}, "XVSLTV": {0xE80F << 15, true},
"XVSLTBU": {0xE810 << 15, true}, "XVSLTHU": {0xE811 << 15, true},
"XVSLTWU": {0xE812 << 15, true}, "XVSLTVU": {0xE813 << 15, true},
"XVADDWEVHB": {0xE83C << 15, true}, "XVADDWEVWH": {0xE83D << 15, true},
"XVADDWEVVW": {0xE83E << 15, true}, "XVADDWEVQV": {0xE83F << 15, true},
"XVSUBWEVHB": {0xE840 << 15, true}, "XVSUBWEVWH": {0xE841 << 15, true},
"XVSUBWEVVW": {0xE842 << 15, true}, "XVSUBWEVQV": {0xE843 << 15, true},
"XVADDWODHB": {0xE844 << 15, true}, "XVADDWODWH": {0xE845 << 15, true},
"XVADDWODVW": {0xE846 << 15, true}, "XVADDWODQV": {0xE847 << 15, true},
"XVSUBWODHB": {0xE848 << 15, true}, "XVSUBWODWH": {0xE849 << 15, true},
"XVSUBWODVW": {0xE84A << 15, true}, "XVSUBWODQV": {0xE84B << 15, true},
"XVADDWEVHBU": {0xE85C << 15, true}, "XVADDWEVWHU": {0xE85D << 15, true},
"XVADDWEVVWU": {0xE85E << 15, true}, "XVADDWEVQVU": {0xE85F << 15, true},
"XVSUBWEVHBU": {0xE860 << 15, true}, "XVSUBWEVWHU": {0xE861 << 15, true},
"XVSUBWEVVWU": {0xE862 << 15, true}, "XVSUBWEVQVU": {0xE863 << 15, true},
"XVADDWODHBU": {0xE864 << 15, true}, "XVADDWODWHU": {0xE865 << 15, true},
"XVADDWODVWU": {0xE866 << 15, true}, "XVADDWODQVU": {0xE867 << 15, true},
"XVSUBWODHBU": {0xE868 << 15, true}, "XVSUBWODWHU": {0xE869 << 15, true},
"XVSUBWODVWU": {0xE86A << 15, true}, "XVSUBWODQVU": {0xE86B << 15, true},
"XVSHUFH": {0xEAF5 << 15, true}, "XVSHUFW": {0xEAF6 << 15, true},
"XVSHUFV": {0xEAF7 << 15, true},
}
for _, tab := range []map[string]l64Vec3Enc{addsub, muldiv, vecmisc} {
for m, e := range tab {
if _, dup := vec3[m]; dup {
panic("loong64: duplicate vector mnemonic " + m)
}
vec3[m] = e
}
}
for m, e := range vec3 { for m, e := range vec3 {
l64InstrTable[m] = l64Enc{format: l64Fvvv, op: e.op} l64InstrTable[m] = l64Enc{format: l64Fvvv, op: e.op}
l64VecBank[m] = e.lasx l64VecBank[m] = e.lasx
@@ -597,21 +850,156 @@ func init() {
// Immediate forms: INSTR $imm, vj, vd (or INSTR $imm, vd). The immediate // Immediate forms: INSTR $imm, vj, vd (or INSTR $imm, vd). The immediate
// range, bias and field mask are the ones the toolchain encodes: vandi.b // range, bias and field mask are the ones the toolchain encodes: vandi.b
// stores the raw 8-bit constant, vsrai.b stores imm+8 (byte-lane bias), // stores the raw 8-bit constant, vsrari.b stores imm+8 (lane-width
// vseqi.b and vseqi.d store 5-bit and 7-bit two's-complement values. // bias), the si5 compares store 5-bit two's-complement values and vseqi.d
// The mnemonics that also have a register form (VSEQB, VSEQV, VSRAB, // a 7-bit field the toolchain range-checks down to si5.
// VROTRW) keep their three-register entry in l64InstrTable; the // The mnemonics that also have a register form (the shifts, the bit
// dispatcher picks the immediate opcode from l64VecImmInfo by operand // clear/set/rev families, VSEQ and the logic immediates) keep their
// kind, so the immediate entries must not overwrite the table. // three-register entry in l64InstrTable; the dispatcher picks the
// immediate opcode from l64VecImmInfo by operand kind, so the immediate
// entries must not overwrite the table.
vecImm := map[string]l64VecImmEnc{ vecImm := map[string]l64VecImmEnc{
"VANDB": {0xE7A0 << 15, false, 0, 255, 0, 0xFF}, "VANDB": {0xE7A0 << 15, false, 0, 255, 0, 0xFF},
"XVANDB": {0xEFA0 << 15, true, 0, 255, 0, 0xFF}, "XVANDB": {0xEFA0 << 15, true, 0, 255, 0, 0xFF},
"VORB": {0xE7A8 << 15, false, 0, 255, 0, 0xFF},
"XVORB": {0xEFA8 << 15, true, 0, 255, 0, 0xFF},
"VXORB": {0xE7B0 << 15, false, 0, 255, 0, 0xFF},
"XVXORB": {0xEFB0 << 15, true, 0, 255, 0, 0xFF},
"VNORB": {0xE7B8 << 15, false, 0, 255, 0, 0xFF},
"XVNORB": {0xEFB8 << 15, true, 0, 255, 0, 0xFF},
"VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F}, "VSEQB": {0xE500 << 15, false, -16, 15, 0, 0x1F},
"XVSEQB": {0xE900 << 15, true, -16, 15, 0, 0x1F}, "XVSEQB": {0xE900 << 15, true, -16, 15, 0, 0x1F},
"VSEQV": {0xE503 << 15, false, -64, 63, 0, 0x7F}, // vseqi.h/w accept the same si5 window as vseqi.b; vseqi.d carries a
"XVSEQV": {0xE903 << 15, true, -64, 63, 0, 0x7F}, // 7-bit field, but the toolchain range-checks it down to si5 as well
"VSRAB": {0xE668 << 15, false, 0, 7, 8, 0x1F}, // (GOARCH=loong64 go tool asm rejects VSEQV $32 and VSEQV $-64).
"VROTRW": {0xE541 << 15, false, 0, 31, 0, 0x1F}, "VSEQH": {0xE501 << 15, false, -16, 15, 0, 0x1F},
"XVSEQH": {0xED01 << 15, true, -16, 15, 0, 0x1F},
"VSEQW": {0xE502 << 15, false, -16, 15, 0, 0x1F},
"XVSEQW": {0xED02 << 15, true, -16, 15, 0, 0x1F},
"VSEQV": {0xE503 << 15, false, -16, 15, 0, 0x7F},
"XVSEQV": {0xE903 << 15, true, -16, 15, 0, 0x7F},
// vslti compares against a signed (or, in the U spellings, unsigned)
// si5/ui5 constant.
"VSLTB": {0xE50C << 15, false, -16, 15, 0, 0x1F},
"XVSLTB": {0xED0C << 15, true, -16, 15, 0, 0x1F},
"VSLTH": {0xE50D << 15, false, -16, 15, 0, 0x1F},
"XVSLTH": {0xED0D << 15, true, -16, 15, 0, 0x1F},
"VSLTW": {0xE50E << 15, false, -16, 15, 0, 0x1F},
"XVSLTW": {0xED0E << 15, true, -16, 15, 0, 0x1F},
"VSLTV": {0xE50F << 15, false, -16, 15, 0, 0x1F},
"XVSLTV": {0xED0F << 15, true, -16, 15, 0, 0x1F},
"VSLTBU": {0xE510 << 15, false, 0, 31, 0, 0x1F},
"XVSLTBU": {0xED10 << 15, true, 0, 31, 0, 0x1F},
"VSLTHU": {0xE511 << 15, false, 0, 31, 0, 0x1F},
"XVSLTHU": {0xED11 << 15, true, 0, 31, 0, 0x1F},
"VSLTWU": {0xE512 << 15, false, 0, 31, 0, 0x1F},
"XVSLTWU": {0xED12 << 15, true, 0, 31, 0, 0x1F},
"VSLTVU": {0xE513 << 15, false, 0, 31, 0, 0x1F},
"XVSLTVU": {0xED13 << 15, true, 0, 31, 0, 0x1F},
// vaddi/vsubi take ui5 constants for every width on this toolchain
// (VADDVU $32 is rejected by the oracle although the field is ui8).
"VADDBU": {0xE514 << 15, false, 0, 31, 0, 0x1F},
"XVADDBU": {0xED14 << 15, true, 0, 31, 0, 0x1F},
"VADDHU": {0xE515 << 15, false, 0, 31, 0, 0x1F},
"XVADDHU": {0xED15 << 15, true, 0, 31, 0, 0x1F},
"VADDWU": {0xE516 << 15, false, 0, 31, 0, 0x1F},
"XVADDWU": {0xED16 << 15, true, 0, 31, 0, 0x1F},
"VADDVU": {0xE517 << 15, false, 0, 31, 0, 0x1F},
"XVADDVU": {0xED17 << 15, true, 0, 31, 0, 0x1F},
"VSUBBU": {0xE518 << 15, false, 0, 31, 0, 0x1F},
"XVSUBBU": {0xED18 << 15, true, 0, 31, 0, 0x1F},
"VSUBHU": {0xE519 << 15, false, 0, 31, 0, 0x1F},
"XVSUBHU": {0xED19 << 15, true, 0, 31, 0, 0x1F},
"VSUBWU": {0xE51A << 15, false, 0, 31, 0, 0x1F},
"XVSUBWU": {0xED1A << 15, true, 0, 31, 0, 0x1F},
"VSUBVU": {0xE51B << 15, false, 0, 31, 0, 0x1F},
"XVSUBVU": {0xED1B << 15, true, 0, 31, 0, 0x1F},
// The shift/rotate immediates ride in a width-sized field whose upper
// bits carry the lane-width code: vslli.b stores ui3 at [12:0] with
// bits [14:13] inside the opcode, vslli.h ui4 under a 4 bit mask, and
// the .w/.d spellings a raw ui5/ui6.
"VSLLB": {0x732C2000, false, 0, 7, 0, 0x7},
"XVSLLB": {0x772C2000, true, 0, 7, 0, 0x7},
"VSLLH": {0x732C4000, false, 0, 15, 0, 0xF},
"XVSLLH": {0x772C4000, true, 0, 15, 0, 0xF},
"VSLLW": {0xE659 << 15, false, 0, 31, 0, 0x1F},
"XVSLLW": {0xEE59 << 15, true, 0, 31, 0, 0x1F},
"VSLLV": {0xE65A << 15, false, 0, 63, 0, 0x3F},
"XVSLLV": {0xEE5A << 15, true, 0, 63, 0, 0x3F},
"VSRLB": {0x73302000, false, 0, 7, 0, 0x7},
"XVSRLB": {0x77302000, true, 0, 7, 0, 0x7},
"VSRLH": {0x73304000, false, 0, 15, 0, 0xF},
"XVSRLH": {0x77304000, true, 0, 15, 0, 0xF},
"VSRLW": {0xE661 << 15, false, 0, 31, 0, 0x1F},
"XVSRLW": {0xEE61 << 15, true, 0, 31, 0, 0x1F},
"VSRLV": {0xE662 << 15, false, 0, 63, 0, 0x3F},
"XVSRLV": {0xEE62 << 15, true, 0, 63, 0, 0x3F},
// vsrari/vrotri bias the field so the lane-width code rides above the
// shift amount (.b adds 8, .h 16, .w 32; .d is a raw ui6).
"VSRAB": {0xE668 << 15, false, 0, 7, 8, 0x1F},
"XVSRAB": {0xEE68 << 15, true, 0, 7, 8, 0x1F},
"VSRAH": {0x73344000, false, 0, 15, 0, 0xF},
"XVSRAH": {0x77344000, true, 0, 15, 0, 0xF},
"VSRAW": {0xE669 << 15, false, 0, 31, 0, 0x1F},
"XVSRAW": {0xEE69 << 15, true, 0, 31, 0, 0x1F},
"VSRAV": {0xE66A << 15, false, 0, 63, 0, 0x3F},
"XVSRAV": {0xEE6A << 15, true, 0, 63, 0, 0x3F},
"VROTRB": {0x72A02000, false, 0, 7, 0, 0x7},
"XVROTRB": {0x76A02000, true, 0, 7, 0, 0x7},
"VROTRH": {0x72A04000, false, 0, 15, 0, 0xF},
"XVROTRH": {0x76A04000, true, 0, 15, 0, 0xF},
"VROTRW": {0xE541 << 15, false, 0, 31, 0, 0x1F},
"XVROTRW": {0xED41 << 15, true, 0, 31, 0, 0x1F},
"VROTRV": {0xE542 << 15, false, 0, 63, 0, 0x3F},
"XVROTRV": {0xED42 << 15, true, 0, 63, 0, 0x3F},
// vbitclri/vbitseti/vbitrevi follow the same width-coded layout.
"VBITCLRB": {0x73102000, false, 0, 7, 0, 0x7},
"XVBITCLRB": {0x77102000, true, 0, 7, 0, 0x7},
"VBITCLRH": {0x73104000, false, 0, 15, 0, 0xF},
"XVBITCLRH": {0x77104000, true, 0, 15, 0, 0xF},
"VBITCLRW": {0xE621 << 15, false, 0, 31, 0, 0x1F},
"XVBITCLRW": {0xEE21 << 15, true, 0, 31, 0, 0x1F},
"VBITCLRV": {0xE622 << 15, false, 0, 63, 0, 0x3F},
"XVBITCLRV": {0xEE22 << 15, true, 0, 63, 0, 0x3F},
"VBITSETB": {0x73142000, false, 0, 7, 0, 0x7},
"XVBITSETB": {0x77142000, true, 0, 7, 0, 0x7},
"VBITSETH": {0x73144000, false, 0, 15, 0, 0xF},
"XVBITSETH": {0x77144000, true, 0, 15, 0, 0xF},
"VBITSETW": {0xE629 << 15, false, 0, 31, 0, 0x1F},
"XVBITSETW": {0xEE29 << 15, true, 0, 31, 0, 0x1F},
"VBITSETV": {0xE62A << 15, false, 0, 63, 0, 0x3F},
"XVBITSETV": {0xEE2A << 15, true, 0, 63, 0, 0x3F},
"VBITREVB": {0x73182000, false, 0, 7, 0, 0x7},
"XVBITREVB": {0x77182000, true, 0, 7, 0, 0x7},
"VBITREVH": {0x73184000, false, 0, 15, 0, 0xF},
"XVBITREVH": {0x77184000, true, 0, 15, 0, 0xF},
"VBITREVW": {0xE631 << 15, false, 0, 31, 0, 0x1F},
"XVBITREVW": {0xEE31 << 15, true, 0, 31, 0, 0x1F},
"VBITREVV": {0xE632 << 15, false, 0, 63, 0, 0x3F},
"XVBITREVV": {0xEE32 << 15, true, 0, 63, 0, 0x3F},
// The 4-bit-select shuffles and the byte-extract/insert permutations
// take ui8 (the .d shuffle ui4 range-checked to 0..15 by the
// toolchain) packing both position nibbles.
"VSHUF4IB": {0xE720 << 15, false, 0, 255, 0, 0xFF},
"XVSHUF4IB": {0xEF20 << 15, true, 0, 255, 0, 0xFF},
"VSHUF4IH": {0xE728 << 15, false, 0, 255, 0, 0xFF},
"XVSHUF4IH": {0xEF28 << 15, true, 0, 255, 0, 0xFF},
"VSHUF4IW": {0xE730 << 15, false, 0, 255, 0, 0xFF},
"XVSHUF4IW": {0xEF30 << 15, true, 0, 255, 0, 0xFF},
"VSHUF4IV": {0xE738 << 15, false, 0, 15, 0, 0xFF},
"XVSHUF4IV": {0xEF38 << 15, true, 0, 15, 0, 0xFF},
"VPERMIW": {0xE7C8 << 15, false, 0, 255, 0, 0xFF},
"XVPERMIW": {0xEFC8 << 15, true, 0, 255, 0, 0xFF},
"XVPERMIV": {0xEFD0 << 15, true, 0, 255, 0, 0xFF},
"XVPERMIQ": {0xEFD8 << 15, true, 0, 255, 0, 0xFF},
"VEXTRINSB": {0xE718 << 15, false, 0, 255, 0, 0xFF},
"XVEXTRINSB": {0xEF18 << 15, true, 0, 255, 0, 0xFF},
"VEXTRINSH": {0xE710 << 15, false, 0, 255, 0, 0xFF},
"XVEXTRINSH": {0xEF10 << 15, true, 0, 255, 0, 0xFF},
"VEXTRINSW": {0xE708 << 15, false, 0, 255, 0, 0xFF},
"XVEXTRINSW": {0xEF08 << 15, true, 0, 255, 0, 0xFF},
"VEXTRINSV": {0xE700 << 15, false, 0, 255, 0, 0xFF},
"XVEXTRINSV": {0xEF00 << 15, true, 0, 255, 0, 0xFF},
} }
for m, e := range vecImm { for m, e := range vecImm {
l64VecImmInfo[m] = e l64VecImmInfo[m] = e
@@ -625,22 +1013,71 @@ func init() {
"VSETANYEQB": 0xE539<<15 | 8<<10, "XVSETANYEQB": 0xED39<<15 | 8<<10, "VSETANYEQB": 0xE539<<15 | 8<<10, "XVSETANYEQB": 0xED39<<15 | 8<<10,
"VSETANYEQV": 0xE539<<15 | 11<<10, "XVSETANYEQV": 0xED39<<15 | 11<<10, "VSETANYEQV": 0xE539<<15 | 11<<10, "XVSETANYEQV": 0xED39<<15 | 11<<10,
"VSETALLNEV": 0xE539<<15 | 15<<10, "XVSETALLNEV": 0xED39<<15 | 15<<10, "VSETALLNEV": 0xE539<<15 | 15<<10, "XVSETALLNEV": 0xED39<<15 | 15<<10,
"VSETEQV": 0xE539<<15 | 6<<10, "XVSETEQV": 0xED39<<15 | 6<<10,
"VSETANYEQH": 0xE539<<15 | 9<<10, "XVSETANYEQH": 0xED39<<15 | 9<<10,
"VSETANYEQW": 0xE539<<15 | 10<<10, "XVSETANYEQW": 0xED39<<15 | 10<<10,
"VSETALLNEB": 0xE539<<15 | 12<<10, "XVSETALLNEB": 0xED39<<15 | 12<<10,
"VSETALLNEH": 0xE539<<15 | 13<<10, "XVSETALLNEH": 0xED39<<15 | 13<<10,
"VSETALLNEW": 0xE539<<15 | 14<<10, "XVSETALLNEW": 0xED39<<15 | 14<<10,
} }
for m, op := range vecCf { for m, op := range vecCf {
l64InstrTable[m] = l64Enc{format: l64Fvcf, op: op} l64InstrTable[m] = l64Enc{format: l64Fvcf, op: op}
l64VecBank[m] = strings.HasPrefix(m, "XV") l64VecBank[m] = strings.HasPrefix(m, "XV")
} }
// Lane popcount: INSTR vj, vd (the 2R layout with the opcode extending // Lane popcount and the two-operand vector FP/unary spellings: INSTR vj,
// over the unused vk field). // vd (the 2R layout with the opcode extending over the unused vk field;
// the low byte of each constant is the instruction's own sub-op).
vec2r := map[string]l64Vec3Enc{ vec2r := map[string]l64Vec3Enc{
"VPCNTV": {0x1CA70B << 10, false}, "XVPCNTV": {0x1DA70B << 10, true}, "VPCNTV": {0x1CA70B << 10, false}, "XVPCNTV": {0x1DA70B << 10, true},
} }
// The rest of the lane popcounts, the vector negations and the vector FP
// unary conversions (loong64enc1.s).
vec2rMore := map[string]l64Vec3Enc{
"VPCNTB": {0x1CA708 << 10, false}, "VPCNTH": {0x1CA709 << 10, false},
"VPCNTW": {0x1CA70A << 10, false},
"VNEGB": {0x1CA70C << 10, false}, "VNEGH": {0x1CA70D << 10, false},
"VNEGW": {0x1CA70E << 10, false}, "VNEGV": {0x1CA70F << 10, false},
"VFCLASSF": {0x1CA735 << 10, false}, "VFCLASSD": {0x1CA736 << 10, false},
"VFSQRTF": {0x1CA739 << 10, false}, "VFSQRTD": {0x1CA73A << 10, false},
"VFRECIPF": {0x1CA73D << 10, false}, "VFRECIPD": {0x1CA73E << 10, false},
"VFRSQRTF": {0x1CA741 << 10, false}, "VFRSQRTD": {0x1CA742 << 10, false},
"VFRINTF": {0x1CA74D << 10, false}, "VFRINTD": {0x1CA74E << 10, false},
"VFRINTRMF": {0x1CA751 << 10, false}, "VFRINTRMD": {0x1CA752 << 10, false},
"VFRINTRPF": {0x1CA755 << 10, false}, "VFRINTRPD": {0x1CA756 << 10, false},
"VFRINTRZF": {0x1CA759 << 10, false}, "VFRINTRZD": {0x1CA75A << 10, false},
"VFRINTRNEF": {0x1CA75D << 10, false}, "VFRINTRNED": {0x1CA75E << 10, false},
"XVPCNTB": {0x1DA708 << 10, true}, "XVPCNTH": {0x1DA709 << 10, true},
"XVPCNTW": {0x1DA70A << 10, true},
"XVNEGB": {0x1DA70C << 10, true}, "XVNEGH": {0x1DA70D << 10, true},
"XVNEGW": {0x1DA70E << 10, true}, "XVNEGV": {0x1DA70F << 10, true},
"XVFCLASSF": {0x1DA735 << 10, true}, "XVFCLASSD": {0x1DA736 << 10, true},
"XVFSQRTF": {0x1DA739 << 10, true}, "XVFSQRTD": {0x1DA73A << 10, true},
"XVFRECIPF": {0x1DA73D << 10, true}, "XVFRECIPD": {0x1DA73E << 10, true},
"XVFRSQRTF": {0x1DA741 << 10, true}, "XVFRSQRTD": {0x1DA742 << 10, true},
"XVFRINTF": {0x1DA74D << 10, true}, "XVFRINTD": {0x1DA74E << 10, true},
"XVFRINTRMF": {0x1DA751 << 10, true}, "XVFRINTRMD": {0x1DA752 << 10, true},
"XVFRINTRPF": {0x1DA755 << 10, true}, "XVFRINTRPD": {0x1DA756 << 10, true},
"XVFRINTRZF": {0x1DA759 << 10, true}, "XVFRINTRZD": {0x1DA75A << 10, true},
"XVFRINTRNEF": {0x1DA75D << 10, true}, "XVFRINTRNED": {0x1DA75E << 10, true},
}
maps.Copy(vec2r, vec2rMore)
for m, e := range vec2r { for m, e := range vec2r {
l64InstrTable[m] = l64Enc{format: l64Frr, op: e.op} l64InstrTable[m] = l64Enc{format: l64Frr, op: e.op}
l64VecBank[m] = e.lasx l64VecBank[m] = e.lasx
l64Vec2R[m] = true l64Vec2R[m] = true
} }
// The four-register byte shuffle: INSTR va, vk, vj, vd (the operand the
// table reads in each field position, va at bits [19:15]).
vec4r := map[string]l64Vec3Enc{
"VSHUFB": {0x0D50 << 16, false}, "XVSHUFB": {0x0D60 << 16, true},
}
for m, e := range vec4r {
l64InstrTable[m] = l64Enc{format: l64Fvvvv, op: e.op}
l64VecBank[m] = e.lasx
l64Vec4R[m] = true
}
} }
// l64FpMovTable maps (mnemonic, from-class, to-class) to the 2R opcode of the // l64FpMovTable maps (mnemonic, from-class, to-class) to the 2R opcode of the
+242
View File
@@ -507,6 +507,218 @@ TEXT ·v(SB), NOSPLIT, $0
0x4C000020, 0x4C000020,
) )
}) })
// The integer and FP add/subtract families with their saturating pairs
// and immediate spellings (loong64enc1.s words).
t.Run("add and subtract families", func(t *testing.T) {
fn := firstTextLOONG64(t, `#include "textflag.h"
TEXT ·v(SB), NOSPLIT, $0
VADDB V1, V2, V3
VADDF V1, V2, V3
VADDD V1, V2, V3
VSUBD V1, V2, V3
VSADDV V1, V2, V3
VSSUBVU V1, V2, V3
VADDBU $1, V2, V1
VADDBU $1, V2
VSUBVU $31, V2
XVSADDV X3, X2, X1
XVSUBD X1, X2, X3
RET
`)
code := assembleLOONG64Helper(t, fn)
wantWords(t, code,
0x700A0443, // vadd.b
0x71308443, // vadd.f
0x71310443, // vadd.d
0x71330443, // vsub.d
0x70478443, // vsadd.v
0x704D8443, // vssub.u.d
0x728A0441, // vaddi.bu v1, v2, 1
0x728A0442, // vaddi.bu v2, v2, 1 (two-operand form)
0x728DFC42, // vsubi.du v2, v2, 31 (two-operand form)
0x74478C41, // xvsadd.d x1, x2, x3
0x75330443, // xvsub.d x3, x2, x1
0x4C000020,
)
})
// The multiply, divide and accumulate families.
t.Run("multiply and divide families", func(t *testing.T) {
fn := firstTextLOONG64(t, `#include "textflag.h"
TEXT ·v(SB), NOSPLIT, $0
VMULV V1, V2, V3
VMUHHU V1, V2, V3
VDIVBU V1, V2, V3
VMODV V1, V2, V3
VMADDB V1, V2, V3
VMSUBV V1, V2, V3
VMULWEVHB V1, V2, V3
VMULWODQV V1, V2, V3
VMADDWEVHBUB V1, V2, V3
XVDIVD X1, X2, X3
RET
`)
code := assembleLOONG64Helper(t, fn)
wantWords(t, code,
0x70858443, // vmul.v
0x70888443, // vmuh.u.d
0x70E40443, // vdiv.u.b
0x70E38443, // vmod.d
0x70A80443, // vmadd.b
0x70AB8443, // vmsub.d
0x70900443, // vmulwev.h.b
0x70938443, // vmulwod.q.d
0x70BC0443, // vmaddwev.h.bu.b
0x753B0443, // xvdiv.d
0x4C000020,
)
})
// The shift, bit and interleave families in register and immediate
// spellings, with the width-coded shift immediates.
t.Run("shift, bit and interleave families", func(t *testing.T) {
fn := firstTextLOONG64(t, `#include "textflag.h"
TEXT ·v(SB), NOSPLIT, $0
VSLLV V1, V2, V3
VROTRB V1, V2, V3
VBITCLRV V1, V2, V3
VBITSETW V1, V2, V3
VBITREVV V1, V2, V3
VILVLB V1, V2, V3
VILVHV V1, V2, V3
VSLLB $7, V1, V2
VSLLB $5, V1
VSRLH $15, V1, V2
VSRAW $31, V1, V2
VSRAV $63, V1, V2
VROTRV $63, V1, V2
VBITCLRB $7, V2, V3
VBITREVV $63, V2, V3
VSEQH $-16, V2, V3
VSLTB $1, V2, V3
VSLTHU $31, V2, V3
XVILVLV X3, X2, X1
XVSLLB $7, X2, X1
XVSRAV $63, X2, X1
XVBITREVV $63, X2, X1
RET
`)
code := assembleLOONG64Helper(t, fn)
wantWords(t, code,
0x70E98443, // vsll.d
0x70EE0443, // vrotr.b
0x710D8443, // vbitclr.d
0x710F0443, // vbitset.w
0x71118443, // vbitrev.d
0x711A0443, // vilvl.b
0x711D8443, // vilvh.d
0x732C3C22, // vslli.b v2, v1, 7
0x732C3421, // vslli.b v1, v1, 5 (two-operand form)
0x73307C22, // vsrli.h v2, v1, 15
0x7334FC22, // vsrai.w v2, v1, 31
0x7335FC22, // vsrai.d v2, v1, 63
0x72A1FC22, // vrotri.d v2, v1, 63
0x73103C43, // vbitclri.b v3, v2, 7
0x7319FC43, // vbitrevi.d v3, v2, 63
0x7280C043, // vseqi.h v3, v2, -16
0x72860443, // vslti.b v3, v2, 1
0x7288FC43, // vslti.hu v3, v2, 31
0x751B8C41, // xvilvl.d x1, x2, x3
0x772C3C41, // xvslli.b x1, x2, 7
0x7735FC41, // xvsrai.d x1, x2, 63
0x7719FC41, // xvbitrevi.d x1, x2, 63
0x4C000020,
)
})
// The shuffle, select and permutation families, including the
// four-register byte shuffle.
t.Run("shuffle and permutation families", func(t *testing.T) {
fn := firstTextLOONG64(t, `#include "textflag.h"
TEXT ·v(SB), NOSPLIT, $0
VSHUFH V1, V2, V3
VSHUFW V1, V2, V3
VSHUFV V1, V2, V3
VSHUFB V1, V2, V3, V4
XVSHUFB X1, X2, X3, X4
VSHUF4IB $255, V2, V1
VSHUF4IV $15, V2, V1
XVSHUF4IV $15, X1, X2
VEXTRINSB $0x18, V1, V2
XVEXTRINSV $0x81, X1, X2
VPERMIW $0x1B, V1, V2
XVPERMIQ $0x4B, X1, X2
RET
`)
code := assembleLOONG64Helper(t, fn)
wantWords(t, code,
0x717A8443, // vshuf.h
0x717B0443, // vshuf.w
0x717B8443, // vshuf.d
0x0D508864, // vshuf.b v4, v3, v2, v1
0x0D608864, // xvshuf.b
0x7393FC41, // vshuf4i.b v1, v2, 255
0x739C3C41, // vshuf4i.d v1, v2, 15
0x779C3C22, // xvshuf4i.d x2, x1, 15
0x738C6022, // vextrins.b v2, v1, 0x18
0x77820422, // xvextrins.d x2, x1, 0x81
0x73E46C22, // vpermi.w v2, v1, 0x1b
0x77ED2C22, // xvpermi.q x2, x1, 0x4b
0x4C000020,
)
})
// The vector FP families, the unary spellings, the compare-to-flag
// additions and the scalar int/float conversions.
t.Run("FP and conversion families", func(t *testing.T) {
fn := firstTextLOONG64(t, `#include "textflag.h"
TEXT ·v(SB), NOSPLIT, $0
VADDF V1, V2, V3
VMULF V1, V2, V3
VFCLASSD V1, V2
VFSQRTF V1, V2
VFRECIPD V1, V2
VFRSQRTF V1, V2
VFRINTF V1, V2
VFRINTRNED V1, V2
VNEGB V1, V2
VPCNTB V1, V2
XVNEGV X2, X1
XVPCNTW X3, X2
XVFRINTRNEF X1, X2
VSETEQV V1, FCC0
VSETANYEQH V1, FCC0
VSETALLNEB V1, FCC0
XVSETALLNEW X1, FCC0
FFINTFW F0, F1
FTINTVD F0, F1
RET
`)
code := assembleLOONG64Helper(t, fn)
wantWords(t, code,
0x71308443, // vfadd.s
0x71388443, // vfmul.s
0x729CD822, // vfclass.d
0x729CE422, // vfsqrt.s
0x729CF822, // vfrecip.d
0x729D0422, // vfrsqrt.s
0x729D3422, // vfrint.s
0x729D7822, // vfrintne.s
0x729C3022, // vneg.b
0x729C2022, // vpcnt.b
0x769C3C41, // xvneg.d x1, x2
0x769C2862, // xvpcnt.w x2, x3
0x769D7422, // xvfrintne.s x2, x1
0x729C9820, // vseteqz.d fcc0, v1
0x729CA420, // vsetanyeqz.h
0x729CB020, // vsetallnez.b
0x769CB820, // xvsetallnez.w
0x011D1001, // ffint.s.w f1, f0
0x011B2801, // ftint.l.d f1, f0
0x4C000020,
)
})
} }
// TestLOONG64_vectorErrors pins the register-class and range diagnostics of // TestLOONG64_vectorErrors pins the register-class and range diagnostics of
@@ -545,6 +757,36 @@ func TestLOONG64_vectorErrors(t *testing.T) {
`TEXT ·e(SB), NOSPLIT, $0 `TEXT ·e(SB), NOSPLIT, $0
VROTRW $32, V1, V2 VROTRW $32, V1, V2
RET RET
`,
`TEXT ·e(SB), NOSPLIT, $0
VADDVU $32, V2
RET
`,
`TEXT ·e(SB), NOSPLIT, $0
VSEQV $32, V2, V3
RET
`,
`TEXT ·e(SB), NOSPLIT, $0
VSHUF4IV $16, V2, V1
RET
`,
`TEXT ·e(SB), NOSPLIT, $0
VEXTRINSB $256, V1, V2
RET
`,
`TEXT ·e(SB), NOSPLIT, $0
VSLTV $-17, V2, V3
RET
`,
// VSHUFB wants four vector registers.
`TEXT ·e(SB), NOSPLIT, $0
VSHUFB V1, V2, V3
RET
`,
// The FCC forms still refuse vector registers.
`TEXT ·e(SB), NOSPLIT, $0
VSETEQV V1, V2
RET
`, `,
// VSET* wants an FCC flag, not a vector register. // VSET* wants an FCC flag, not a vector register.
`TEXT ·e(SB), NOSPLIT, $0 `TEXT ·e(SB), NOSPLIT, $0
+354
View File
@@ -0,0 +1,354 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
"regexp"
"strings"
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
)
// go_asm.h is the header the Go compiler writes for every package that
// carries assembly (the compiler's -asmhdr output): "#define const_NAME
// value" for each package constant, and for each named struct type
// "#define TYPE__size size" plus one "#define TYPE_field offset" per field.
// GOROOT assembly includes it, and a standalone assembler has no compiler
// to have produced it, so gasm generates the equivalent itself: the package
// the .s file lives in is parsed and type-checked here, with the target
// architecture's own sizes, and the same defines are written out.
//
// The emitter mirrors cmd/compile's dumpasmhdr exactly: constants come out
// as "const_NAME", struct entries as "NAME__size" followed by the fields in
// declaration order, blank names are skipped, and float and complex
// constants are omitted (the assembler carries integers, bools and strings
// only). Aliases to structs are emitted, generic types are not: they have
// no fixed size. A define the assembly references but this header does not
// carry surfaces later as the assembler's own "undefined" diagnostic naming
// the define, which is the honest failure.
// goAsmInclude matches the #include "go_asm.h" directive, tolerant of
// whitespace, so the wiring knows which files need a generated header
// before the preprocessor runs and would report the header as missing.
var goAsmInclude = regexp.MustCompile(`(?m)^\s*#\s*include\s+"go_asm\.h"`)
// needsGoAsmHeader reports whether src includes go_asm.h.
func needsGoAsmHeader(src string) bool {
return goAsmInclude.MatchString(src)
}
// goAsmHeaderResolved reports whether the include of go_asm.h from a file in
// asmDir already resolves: to a header in the package directory itself, or
// in one of the -I directories, the way the preprocessor searches. Only an
// unresolved include is generated for; a header someone placed by hand is
// the tool the author chose, and it also wins the preprocessor's own search
// order, so generating a second copy would be dead weight at best.
func goAsmHeaderResolved(asmDir string, dirs []string) bool {
candidates := []string{filepath.Join(asmDir, "go_asm.h")}
for _, d := range dirs {
candidates = append(candidates, filepath.Join(d, "go_asm.h"))
}
for _, candidate := range candidates {
if st, err := os.Stat(candidate); err == nil && !st.IsDir() {
return true
}
}
return false
}
// generateGoAsmHeader type-checks the Go package in pkgDir for goarch,
// writes its go_asm.h equivalent into dir, and returns dir. The caller
// owns the directory and its removal.
func generateGoAsmHeader(pkgDir, goarch, dir string) (string, error) {
imp := newSourceImporter(goarch)
if imp.sizes == nil {
return "", fmt.Errorf("go_asm.h: unknown GOARCH %q", goarch)
}
bp, err := imp.ctxt.ImportDir(pkgDir, 0)
if err != nil {
return "", fmt.Errorf("go_asm.h for GOARCH %s in %s: %w", goarch, pkgDir, err)
}
files, errs := imp.parse(bp)
if len(errs) > 0 {
return "", fmt.Errorf("go_asm.h for GOARCH %s in %s: %s", goarch, pkgDir, errorList(errs))
}
_, info, errs := imp.checkPackage(bp, files)
if len(errs) > 0 {
return "", fmt.Errorf("go_asm.h for GOARCH %s in %s: package does not type-check: %s", goarch, pkgDir, errorList(errs))
}
var b strings.Builder
fmt.Fprintf(&b, "// generated by gasm from package %s (GOARCH %s)\n\n", bp.Name, goarch)
// Files in the build's own order and declarations in source order: the
// same walk the compiler's reader makes, so the header reads the same
// way the toolchain's does. Order carries no meaning to the assembler
// (defines form a table), only to a human diffing against one.
for _, f := range files {
for _, decl := range f.Decls {
gd, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
for _, spec := range gd.Specs {
switch gd.Tok {
case token.CONST:
vs, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}
for _, name := range vs.Names {
emitConst(&b, info.Defs[name], name.Name)
}
case token.TYPE:
ts, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
emitStruct(&b, imp.sizes, info.Defs[ts.Name], ts.Name.Name)
}
}
}
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("go_asm.h for GOARCH %s in %s: %w", goarch, pkgDir, err)
}
out := filepath.Join(dir, "go_asm.h")
if err := os.WriteFile(out, []byte(b.String()), 0o644); err != nil {
return "", fmt.Errorf("go_asm.h for GOARCH %s in %s: %w", goarch, pkgDir, err)
}
return dir, nil
}
// emitConst writes one const define, skipping what the toolchain skips:
// blank names, and float and complex values the assembler has no syntax for.
func emitConst(b *strings.Builder, obj types.Object, name string) {
c, ok := obj.(*types.Const)
if !ok || name == "_" {
return
}
switch c.Val().Kind() {
case constant.Float, constant.Complex, constant.Unknown:
return
}
fmt.Fprintf(b, "#define const_%s %s\n", name, c.Val().ExactString())
}
// emitStruct writes one named struct type's size and field offsets,
// skipping what the toolchain skips: blank names, non-struct types, and
// generic types, whose size depends on their instantiation.
func emitStruct(b *strings.Builder, sizes types.Sizes, obj types.Object, name string) {
tn, ok := obj.(*types.TypeName)
if !ok || name == "_" {
return
}
t := types.Unalias(tn.Type())
// Generic types are spelled *types.Named with a type-parameter list;
// a plain struct type or an instantiated one carries none.
if named, ok := t.(*types.Named); ok && named.TypeParams().Len() > 0 {
return
}
st, ok := t.Underlying().(*types.Struct)
if !ok {
return
}
fmt.Fprintf(b, "#define %s__size %d\n", name, sizes.Sizeof(t))
fields := make([]*types.Var, st.NumFields())
for i := range st.NumFields() {
fields[i] = st.Field(i)
}
for i, off := range sizes.Offsetsof(fields) {
fld := fields[i]
if fld.Name() == "_" {
continue
}
fmt.Fprintf(b, "#define %s_%s %d\n", name, fld.Name(), off)
}
}
// errorList renders at most three errors, enough to say what is wrong
// without burying the diagnostic the caller actually reads.
func errorList(errs []error) string {
if len(errs) > 3 {
errs = errs[:3]
}
msgs := make([]string, len(errs))
for i, err := range errs {
msgs[i] = err.Error()
}
return strings.Join(msgs, "; ")
}
// sourceImporter type-checks imported packages from source with the target
// architecture's sizes. go/importer's "source" importer pins the host
// GOARCH, which would lay out imported types (internal/cpu, internal/abi)
// for the wrong target on a cross-architecture header, so the recursion is
// carried here with one build context and one sizes instance per
// architecture.
type sourceImporter struct {
fset *token.FileSet
ctxt *build.Context
sizes types.Sizes
pkgs map[string]*types.Package
}
// newSourceImporter returns the importer for one target architecture.
// Cgo is disabled so the file set is deterministic and independent of the
// host's C toolchain: cgo-tagged files drop out of the build exactly as
// they do from a CGO_ENABLED=0 build, whose assembly is what gasm targets.
func newSourceImporter(goarch string) *sourceImporter {
ctxt := new(build.Context)
*ctxt = build.Default
ctxt.GOARCH = goarch
ctxt.CgoEnabled = false
return &sourceImporter{
fset: token.NewFileSet(),
ctxt: ctxt,
sizes: types.SizesFor("gc", goarch),
pkgs: map[string]*types.Package{},
}
}
// Import type-checks one imported package and memoises it. "unsafe" must
// resolve to go/types' own package, never to the source in GOROOT/src/unsafe:
// the source declares Sizeof and Offsetof as ordinary functions over
// ArbitraryType, and checking against that signature rejects half the
// unsafe arithmetic the gc compiler accepts, which is exactly the divergence
// srcimporter guards against the same way.
func (im *sourceImporter) Import(path string) (*types.Package, error) {
if path == "unsafe" {
return types.Unsafe, nil
}
if p, ok := im.pkgs[path]; ok {
return p, nil
}
bp, err := im.ctxt.Import(path, "", 0)
if err != nil {
return nil, err
}
files, errs := im.parse(bp)
if len(errs) > 0 {
return nil, errors.New(errorList(errs))
}
pkg, _, _ := im.checkPackage(bp, files)
im.pkgs[path] = pkg
return pkg, nil
}
// parse reads the build package's Go files. Import-level failures (no Go
// files for the target, unreadable files) come back as errors, and the
// type-check decides the rest.
func (im *sourceImporter) parse(bp *build.Package) ([]*ast.File, []error) {
if len(bp.GoFiles) == 0 {
return nil, []error{fmt.Errorf("no Go source files for GOOS=%s GOARCH=%s", im.ctxt.GOOS, im.ctxt.GOARCH)}
}
var (
files []*ast.File
errs []error
)
for _, name := range bp.GoFiles {
f, err := parser.ParseFile(im.fset, filepath.Join(bp.Dir, name), nil, parser.SkipObjectResolution)
if err != nil {
errs = append(errs, err)
continue
}
files = append(files, f)
}
return files, errs
}
// checkPackage type-checks one package's files with the importer's sizes,
// recording every error: a header from a package that does not type-check
// could silently mis-state an offset, so the caller refuses the header
// rather than trusting it. The returned Defs map backs the root package's
// emission walk; imports only need the checked package itself.
func (im *sourceImporter) checkPackage(bp *build.Package, files []*ast.File) (*types.Package, *types.Info, []error) {
var errs []error
conf := &types.Config{
Importer: im,
Sizes: im.sizes,
Error: func(err error) { errs = append(errs, err) },
}
info := &types.Info{Defs: map[*ast.Ident]types.Object{}}
pkg, _ := conf.Check(bp.ImportPath, im.fset, files, info)
return pkg, info, errs
}
// asmhdrCache generates one go_asm.h per package directory and target
// architecture under one temp root, for callers that assemble many files
// (the corpus audit). Failures are cached too: a package that does not
// type-check must not be re-checked once per file.
type asmhdrCache struct {
root string
dirs map[string]string // "pkgDir\x00goarch" -> directory holding go_asm.h
errs map[string]error
}
func newAsmhdrCache() (*asmhdrCache, error) {
root, err := os.MkdirTemp("", "gasm-asmhdr")
if err != nil {
return nil, err
}
return &asmhdrCache{root: root, dirs: map[string]string{}, errs: map[string]error{}}, nil
}
// dirFor returns the directory holding the generated go_asm.h for pkgDir
// and goarch, generating it on first use.
func (c *asmhdrCache) dirFor(pkgDir, goarch string) (string, error) {
key := pkgDir + "\x00" + goarch
if dir, ok := c.dirs[key]; ok {
return dir, nil
}
if err, ok := c.errs[key]; ok {
return "", err
}
dir := filepath.Join(c.root, fmt.Sprintf("h%d_%s", len(c.dirs), goarch))
if _, err := generateGoAsmHeader(pkgDir, goarch, dir); err != nil {
c.errs[key] = err
return "", err
}
c.dirs[key] = dir
return dir, nil
}
// close removes the temp root.
func (c *asmhdrCache) close() { os.RemoveAll(c.root) }
// ensureGoAsmHeader prepares the include directory a file that includes
// go_asm.h needs: the generated header for the package in path's directory,
// for the file's target architecture. It reports a usage error when the
// architecture cannot be determined, and passes through the generator's
// diagnostics, which name the package.
func ensureGoAsmHeader(path string, target arch.Arch, cache *asmhdrCache) (string, func(), error) {
if path == "-" {
return "", nil, errors.New("cannot generate go_asm.h for standard input (no package directory)")
}
if target == arch.Unknown {
return "", nil, errors.New("a file that includes go_asm.h needs a target architecture: name the file _<arch>.s or pass -GOARCH")
}
if cache != nil {
dir, err := cache.dirFor(filepath.Dir(path), goarchName(target))
return dir, func() {}, err
}
root, err := os.MkdirTemp("", "gasm-asmhdr")
if err != nil {
return "", nil, err
}
dir, err := generateGoAsmHeader(filepath.Dir(path), goarchName(target), root)
if err != nil {
os.RemoveAll(root)
return "", nil, err
}
return dir, func() { os.RemoveAll(root) }, nil
}
+311
View File
@@ -0,0 +1,311 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// writePkg lays out a minimal Go package in a temp directory.
func writePkg(t *testing.T, files map[string]string) string {
t.Helper()
dir := t.TempDir()
for name, src := range files {
if err := os.WriteFile(filepath.Join(dir, name), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
}
return dir
}
// generateFor generates the header for dir and returns its text.
func generateFor(t *testing.T, dir, goarch string) string {
t.Helper()
hdrDir, err := generateGoAsmHeader(dir, goarch, t.TempDir())
if err != nil {
t.Fatalf("generateGoAsmHeader(%q, %s): %v", dir, goarch, err)
}
b, err := os.ReadFile(filepath.Join(hdrDir, "go_asm.h"))
if err != nil {
t.Fatal(err)
}
return string(b)
}
func TestGenerateGoAsmHeaderShape(t *testing.T) {
dir := writePkg(t, map[string]string{"sample.go": `package sample
const bufSize = 1024
const (
a = iota * 8
b
c
)
const (
strConst = "hello"
boolConst = true
floatConst = 1.5
_ = "the blank identifier is skipped"
)
const shift = 1 << 20
type reader struct {
r int64
w int64
_ [4]byte
name string
}
type scalar int
type aliased struct {
k uint32
v uint32
}
type alias = aliased
`})
hdr := generateFor(t, dir, "amd64")
want := []string{
"#define const_bufSize 1024",
// iota resolves through go/types, one define per name.
"#define const_a 0",
"#define const_b 8",
"#define const_c 16",
`#define const_strConst "hello"`,
"#define const_boolConst true",
// Floats are the toolchain's own skip, as are blank names.
"#define const_shift 1048576",
// The blank field still occupies its bytes: the pad after w runs to
// the string's 8-byte alignment.
"#define reader__size 40",
"#define reader_r 0",
"#define reader_w 8",
"#define reader_name 24",
// Non-struct named types carry no defines; aliases to structs do.
"#define aliased__size 8",
"#define aliased_k 0",
"#define aliased_v 4",
"#define alias__size 8",
"#define alias_k 0",
"#define alias_v 4",
}
for _, w := range want {
if !strings.Contains(hdr, w+"\n") {
t.Errorf("header misses %q\ngot:\n%s", w, hdr)
}
}
for _, banned := range []string{"#define const_floatConst", "#define _ ", "#define scalar"} {
if strings.Contains(hdr, banned) {
t.Errorf("header must not carry %s\ngot:\n%s", banned, hdr)
}
}
}
func TestGenerateGoAsmHeaderPerArch(t *testing.T) {
dir := writePkg(t, map[string]string{
"common.go": `package perarch
type layout struct {
a int32
p uintptr
}
`,
// The build-tagged file set is part of the contract: a per-arch
// package is exactly how internal/cpu declares its layouts.
"const_amd64.go": `//go:build amd64
package perarch
const flavour = 1
`,
"const_arm64.go": `//go:build arm64
package perarch
const flavour = 2
`,
})
amd64 := generateFor(t, dir, "amd64")
arm64 := generateFor(t, dir, "arm64")
if !strings.Contains(amd64, "#define const_flavour 1\n") {
t.Errorf("amd64 header misses const_flavour 1:\n%s", amd64)
}
if !strings.Contains(arm64, "#define const_flavour 2\n") {
t.Errorf("arm64 header misses const_flavour 2:\n%s", arm64)
}
if strings.Contains(arm64, "#define const_flavour 1\n") {
t.Errorf("arm64 header must not carry the amd64 file's value")
}
// SizesFor makes the layout the target's: uintptr is 4 bytes wide on
// 386 and 8 on amd64, which must move p and grow the struct.
if !strings.Contains(amd64, "#define layout__size 16\n") || !strings.Contains(amd64, "#define layout_p 8\n") {
t.Errorf("amd64 layout wrong:\n%s", amd64)
}
w386 := generateFor(t, dir, "386")
if !strings.Contains(w386, "#define layout__size 8\n") || !strings.Contains(w386, "#define layout_p 4\n") {
t.Errorf("386 layout wrong:\n%s", w386)
}
}
func TestGenerateGoAsmHeaderErrors(t *testing.T) {
t.Run("type error", func(t *testing.T) {
dir := writePkg(t, map[string]string{"bad.go": `package bad
const x = undefinedIdent
`})
_, err := generateGoAsmHeader(dir, "amd64", t.TempDir())
if err == nil {
t.Fatal("generation must fail for a package that does not type-check")
}
if !strings.Contains(err.Error(), dir) {
t.Errorf("error must name the package directory: %v", err)
}
if !strings.Contains(err.Error(), "type-check") {
t.Errorf("error must say the package does not type-check: %v", err)
}
})
t.Run("no go files", func(t *testing.T) {
dir := t.TempDir()
_, err := generateGoAsmHeader(dir, "amd64", t.TempDir())
if err == nil {
t.Fatal("generation must fail without Go files")
}
if !strings.Contains(err.Error(), dir) {
t.Errorf("error must name the package directory: %v", err)
}
})
}
func TestNeedsGoAsmHeader(t *testing.T) {
yes := "#include \"go_asm.h\"\n#include \"textflag.h\"\n"
no := "#include \"textflag.h\"\n#include \"funcdata.h\"\n"
if !needsGoAsmHeader(yes) {
t.Error("needsGoAsmHeader(missing on a go_asm.h include)")
}
if needsGoAsmHeader(no) {
t.Error("needsGoAsmHeader claims other headers need generation")
}
}
func TestGoAsmHeaderResolved(t *testing.T) {
dir := t.TempDir()
if goAsmHeaderResolved(dir, nil) {
t.Error("resolved with no header anywhere")
}
other := t.TempDir()
if goAsmHeaderResolved(dir, []string{other}) {
t.Error("resolved with an empty -I directory")
}
if err := os.WriteFile(filepath.Join(dir, "go_asm.h"), nil, 0o644); err != nil {
t.Fatal(err)
}
if !goAsmHeaderResolved(dir, nil) {
t.Error("not resolved with the header in the package directory")
}
}
func TestOtherGOOSFile(t *testing.T) {
for path, want := range map[string]bool{
"/x/sys_windows_amd64.s": true,
"/x/rt0_js_wasm.s": true,
"/x/sys_darwin_arm64.s": true,
"/x/sys_linux_amd64.s": false,
"/x/time_linux_amd64.s": false,
"/x/memmove_amd64.s": false,
"/x/generic.s": false,
} {
if got := otherGOOSFile(path); got != want {
t.Errorf("otherGOOSFile(%q) = %v, want %v", path, got, want)
}
}
}
// TestRunCorpusAuditGoAsm covers the audit wiring end to end: a package
// beside its kernel, the kernel living off the generated defines, and the
// histogram recording a generation failure as its own reason.
func TestRunCorpusAuditGoAsm(t *testing.T) {
dir := t.TempDir()
write := func(name, src string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
}
write("pkg.go", `package corpus
const pageSize = 4096
type header struct {
magic uint64
flags uint64
}
`)
write("kern_amd64.s", "#include \"go_asm.h\"\nTEXT \xc2\xb7f(SB), NOSPLIT, $0-16\n\tMOVQ\t$const_pageSize, AX\n\tMOVQ\t$header__size, BX\n\tRET\n")
// The defines live in the file's own package; a kernel in a directory
// without Go files has no package to generate from.
if err := os.MkdirAll(filepath.Join(dir, "sub"), 0o755); err != nil {
t.Fatal(err)
}
write(filepath.Join("sub", "lonely_arm64.s"), "#include \"go_asm.h\"\nTEXT \xc2\xb7g(SB), NOSPLIT, $0-0\n\tRET\n")
stats, err := runCorpusAudit(dir, nil)
if err != nil {
t.Fatalf("runCorpusAudit: %v", err)
}
get := func(name string) *corpusTally {
for i, tg := range stats.targets {
if tg.name == name {
return stats.tallies[i]
}
}
t.Fatalf("no tally for %s", name)
return nil
}
if a := get("amd64"); a.attempted != 1 || a.assembled != 1 {
t.Errorf("amd64 = %d/%d, want 1/1", a.assembled, a.attempted)
}
// lonely_arm64.s is an arm64 file whose package cannot be generated.
if a := get("arm64"); a.attempted != 1 || a.assembled != 0 {
t.Errorf("arm64 = %d/%d, want 0/1", a.assembled, a.attempted)
}
if r := get("arm64").reasons["go_asm.h generation failed"]; r != 1 {
t.Errorf("arm64 go_asm.h failure count = %d, want 1", r)
}
}
// TestGenerateGoAsmHeaderRuntime pins the generator against the real thing:
// the runtime package, whose header the toolchain's own -asmhdr output was
// sampled from. Skipped in short mode: it type-checks the whole package.
func TestGenerateGoAsmHeaderRuntime(t *testing.T) {
if testing.Short() {
t.Skip("type-checks the whole runtime package")
}
dir, err := generateGoAsmHeader("/usr/local/go/src/runtime", "amd64", t.TempDir())
if err != nil {
t.Fatalf("generateGoAsmHeader(runtime): %v", err)
}
b, err := os.ReadFile(dir + "/go_asm.h")
if err != nil {
t.Fatal(err)
}
hdr := string(b)
for _, want := range []string{
"#define const_hashSize 8\n",
"#define const_avxSupported 1\n",
"#define const_pageSize 8192\n",
"#define g_stackguard0 16\n",
"#define m__size ",
} {
if !strings.Contains(hdr, want) {
t.Errorf("runtime header misses %q", want)
}
}
}
+98 -8
View File
@@ -418,7 +418,9 @@ func cmdAuditCorpus(args []string, dirs includeDirs) error {
// The toolchain's shipped headers (funcdata.h and friends) define the // The toolchain's shipped headers (funcdata.h and friends) define the
// macros GOROOT files include; a corpus audit measures those files, so // macros GOROOT files include; a corpus audit measures those files, so
// the header directory joins the search path automatically. go_asm.h // the header directory joins the search path automatically. go_asm.h
// is compiler-generated per package and stays unresolvable on purpose. // is compiler-generated per package, so it is not resolved from here:
// files that include it get one generated per target architecture,
// which runCorpusAudit arranges.
if out, err := exec.Command("go", "env", "GOROOT").Output(); err == nil { if out, err := exec.Command("go", "env", "GOROOT").Output(); err == nil {
pkgInclude := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include") pkgInclude := filepath.Join(strings.TrimSpace(string(out)), "pkg", "include")
if fi, err := os.Stat(pkgInclude); err == nil && fi.IsDir() { if fi, err := os.Stat(pkgInclude); err == nil && fi.IsDir() {
@@ -462,9 +464,15 @@ var goPortSuffixes = []string{
"s390x", "wasm", "s390x", "wasm",
} }
// otherPortFile reports whether the file's name carries a Go-architecture // otherPortFile reports whether the file belongs to a build no supported
// suffix gasm does not support. // target ever compiles: either its name carries a Go-architecture suffix
// gasm does not support, or, for a file with no architecture suffix at all,
// it names another GOOS, which go/build drops from the file set
// (rt0_js_wasm.s is a javascript build, not a generic one).
func otherPortFile(path string) bool { func otherPortFile(path string) bool {
if otherGOOSFile(path) {
return true
}
base := path base := path
if i := strings.LastIndexByte(base, '/'); i >= 0 { if i := strings.LastIndexByte(base, '/'); i >= 0 {
base = base[i+1:] base = base[i+1:]
@@ -477,6 +485,29 @@ func otherPortFile(path string) bool {
return false return false
} }
// goOSNames are the GOOS values go/build recognises in file names.
var goOSNames = map[string]bool{
"aix": true, "darwin": true, "dragonfly": true, "freebsd": true,
"ios": true, "js": true, "linux": true, "netbsd": true,
"openbsd": true, "plan9": true, "solaris": true, "wasip1": true,
"windows": true,
}
// otherGOOSFile reports whether the file's name names a GOOS other than the
// host's, by go/build's file-name rules.
func otherGOOSFile(path string) bool {
base := path
if i := strings.LastIndexByte(base, '/'); i >= 0 {
base = base[i+1:]
}
for seg := range strings.SplitSeq(strings.TrimSuffix(base, ".s"), "_") {
if goOSNames[seg] && seg != runtime.GOOS {
return true
}
}
return false
}
func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) { func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
files, err := asmFiles(root) files, err := asmFiles(root)
if err != nil { if err != nil {
@@ -497,12 +528,20 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
// its name allows assembles it. // its name allows assembles it.
full, generic, otherPort := 0, 0, 0 full, generic, otherPort := 0, 0, 0
// Header generation is created on first use, so a corpus with no
// go_asm.h includes never pays for a temp directory.
var hdr *asmhdrCache
defer func() {
if hdr != nil {
hdr.close()
}
}()
for _, path := range files { for _, path := range files {
src, err := readSource(path) src, err := readSource(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
var wanted []int // indexes into targets var wanted []int // indexes into targets
if a := arch.FromFilename(path); a != arch.Unknown { if a := arch.FromFilename(path); a != arch.Unknown {
@@ -513,10 +552,11 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
} }
} else if otherPortFile(path) { } else if otherPortFile(path) {
// A file named for a Go port gasm does not support (arm, // A file named for a Go port gasm does not support (arm,
// 386, s390x, ...) is compiled by no supported-arch build, // 386, s390x, ...) or for another GOOS is compiled by no
// so it is neither generic nor a per-arch attempt: counting // supported-arch build, so it is neither generic nor a
// it as generic would make the headline unreachably low // per-arch attempt: counting it as generic would make the
// for reasons no supported target can fix. // headline unreachably low for reasons no supported target
// can fix.
otherPort++ otherPort++
} else { } else {
generic++ generic++
@@ -525,6 +565,54 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
} }
} }
// A file that includes go_asm.h parses against a per-target header:
// the defines differ per architecture (internal/cpu's layout, for
// one), so the parse cannot be shared the way a header-free file's
// can. A generation failure is a failure for every target, named
// for the package rather than a bare "include not found". A header
// already resolvable in the package directory or the -I list is
// left alone.
if len(wanted) > 0 && needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) {
if hdr == nil {
if hdr, err = newAsmhdrCache(); err != nil {
return nil, err
}
}
pkgDir := filepath.Dir(path)
ok := true
for _, i := range wanted {
tg, t := targets[i], tallies[i]
t.attempted++
hdrDir, err := hdr.dirFor(pkgDir, goarchName(tg.a))
if err != nil {
ok = false
t.fail(path, corpusReason(err))
continue
}
f, errs := parser.ParseWithOptions(path, src, parser.Options{
Expand: true,
IncludeDirs: append(slices.Clone(dirs), hdrDir),
})
if len(errs) > 0 {
ok = false
t.fail(path, corpusReason(errs[0]))
continue
}
if _, err := assembleFile(tg.a, f); err != nil {
ok = false
t.fail(path, corpusReason(err))
continue
}
t.assembled++
}
if ok && len(wanted) > 0 {
full++
}
continue
}
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
ok := true ok := true
for _, i := range wanted { for _, i := range wanted {
tg, t := targets[i], tallies[i] tg, t := targets[i], tallies[i]
@@ -581,6 +669,8 @@ func printCorpusStats(s *corpusStats) {
func corpusReason(err error) string { func corpusReason(err error) string {
msg := err.Error() msg := err.Error()
switch { switch {
case strings.Contains(msg, "go_asm.h for GOARCH"):
return "go_asm.h generation failed"
case strings.Contains(msg, "unsupported"), strings.Contains(msg, "cannot encode"): case strings.Contains(msg, "unsupported"), strings.Contains(msg, "cannot encode"):
return "instruction not encodable" return "instruction not encodable"
case strings.Contains(msg, "undefined label"): case strings.Contains(msg, "undefined label"):
+23
View File
@@ -503,6 +503,12 @@ system toolchain; goobj emits the Go toolchain's own object format, which
cmd/link consumes directly (it requires -p, the package path, and the cmd/link consumes directly (it requires -p, the package path, and the
installed Go toolchain: the object preamble is captured from go tool asm installed Go toolchain: the object preamble is captured from go tool asm
and the format version from go version). and the format version from go version).
A file that includes go_asm.h gets that header generated automatically from
the package it lives in (the .go files beside it, type-checked for the
target architecture, the toolchain's own defines), so GOROOT assembly
assembles without a compiler. A package that has no Go files for the
target or does not type-check is a hard error naming the package.
`) `)
out := fs.String("o", "", "write the output to this file") out := fs.String("o", "", "write the output to this file")
format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)") format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)")
@@ -538,6 +544,23 @@ and the format version from go version).
fmt.Fprintln(os.Stderr, "gasm:", err) fmt.Fprintln(os.Stderr, "gasm:", err)
return 1 return 1
} }
// A file that includes go_asm.h cannot assemble without the package's
// defines, and without a compiler nothing else has generated them, so
// gasm produces the equivalent itself: automatic, because the compiler
// behaves the same way and a flag would only ever be forgotten. A
// generation failure is fatal and names the package: assembling against
// a missing header would fail later with a bare "undefined" instead.
// A go_asm.h that already resolves (placed by hand, or passed with -I)
// is left alone.
if needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) {
hdrDir, cleanup, err := ensureGoAsmHeader(path, targetArch, nil)
if err != nil {
fmt.Fprintln(os.Stderr, "gasm asm:", err)
return 1
}
defer cleanup()
dirs = append(dirs, hdrDir)
}
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs}) f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
for _, e := range errs { for _, e := range errs {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e) fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
+16 -6
View File
@@ -254,11 +254,12 @@ func renderPreproc(line []token.Token) string {
line[2].Kind == token.String { line[2].Kind == token.String {
return "#include " + line[2].Text return "#include " + line[2].Text
} }
parts := make([]string, 0, len(line)-1) // The body of a directive, a macro definition included, is an ordinary
for _, t := range line[1:] { // token run: rendering it through renderOps applies the same punctuation
parts = append(parts, t.Text) // rules as everywhere else, so a macro body keeps its canonical spelling
} // ($v, (a, b), the ';' separators between statements) instead of being
return "#" + strings.Join(parts, " ") // spread with a space between every token.
return "#" + renderOps(line[1:])
} }
// renderOps re-spaces a run of operand tokens into canonical form. It never // renderOps re-spaces a run of operand tokens into canonical form. It never
@@ -348,6 +349,13 @@ func spaceBetween(prev, cur token.Token) bool {
return false return false
case token.Comma: case token.Comma:
return false return false
case token.Semicolon:
// A ';' is a statement separator on the assembly path, not an
// operand: dropping it would fuse two statements into a line the
// assembler rejects, so it must survive as punctuation. It glues
// to the statement it ends and the next statement takes one space,
// matching the toolchain's listing style.
return false
case token.Star, token.Plus, token.Minus, token.Slash, token.Pipe: case token.Star, token.Plus, token.Minus, token.Slash, token.Pipe:
return false return false
case token.LShift, token.RShift, token.Arrow, token.At: case token.LShift, token.RShift, token.Arrow, token.At:
@@ -376,7 +384,9 @@ func spaceBetween(prev, cur token.Token) bool {
return false return false
case token.LAngle, token.RAngle: case token.LAngle, token.RAngle:
return false return false
case token.Comma: case token.Comma, token.Semicolon:
// The statement after a ';' separator takes its own space, exactly
// like the operand after a comma.
return true return true
} }
return true return true
+181
View File
@@ -5,9 +5,11 @@ package format
import ( import (
"os" "os"
"slices"
"strings" "strings"
"testing" "testing"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
"sourcedock.dev/petrbalvin/gasm-devkit/lexer" "sourcedock.dev/petrbalvin/gasm-devkit/lexer"
"sourcedock.dev/petrbalvin/gasm-devkit/parser" "sourcedock.dev/petrbalvin/gasm-devkit/parser"
"sourcedock.dev/petrbalvin/gasm-devkit/token" "sourcedock.dev/petrbalvin/gasm-devkit/token"
@@ -298,6 +300,185 @@ func TestCRLFInputIsNormalisedToLF(t *testing.T) {
} }
} }
// TestSemicolonSeparators pins the treatment of ';' statement separators.
// The separator is load-bearing on the assembly path, where the parser reads
// semicolon-separated statements: a formatter that drops it fuses two
// statements into a line the assembler rejects, which is data corruption.
// Each row pins the canonical spelling, one space after the ';', tight
// before it, the way the toolchain's own sources and listings write it.
func TestSemicolonSeparators(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{
name: "between instructions, tight",
in: "TEXT ·f(SB), $0\nBYTE $0x48;BYTE $0xc7\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $0x48; BYTE $0xc7\n\tRET\n",
},
{
name: "between instructions, spaced",
in: "TEXT ·f(SB), $0\nBYTE $0x48 ; BYTE $0xc7\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $0x48; BYTE $0xc7\n\tRET\n",
},
{
name: "after a label",
in: "TEXT ·f(SB), $0\nlabel: BYTE $1; BYTE $2\nRET\n",
want: "TEXT ·f(SB), $0\nlabel:\n\tBYTE $1; BYTE $2\n\tRET\n",
},
{
// The continuation-spliced macro shape of the runtime sources:
// the lexer makes one logical line of the backslash continuations.
name: "inside a macro body, continued",
in: "#define MOVLTOREG(v, off) \\\n\tMOVL $v, AX; \\\n\tMOVL AX, ret+off(FP)\n",
want: "#define MOVLTOREG(v, off) MOVL $v, AX; MOVL AX, ret+off(FP)\n",
},
{
name: "inside a macro body, one line",
in: "#define PEAS BYTE $0x0a; BYTE $0x0b\n",
want: "#define PEAS BYTE $0x0a; BYTE $0x0b\n",
},
{
name: "several separators in one line",
in: "TEXT ·f(SB), $0\nBYTE $1; BYTE $2; BYTE $3\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $1; BYTE $2; BYTE $3\n\tRET\n",
},
{
name: "two separators back to back",
in: "TEXT ·f(SB), $0\nBYTE $1;; BYTE $2\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $1;; BYTE $2\n\tRET\n",
},
{
name: "inside a line comment, untouched",
in: "TEXT ·f(SB), $0\n// keep; the; separators\nBYTE $1\nRET\n",
want: "TEXT ·f(SB), $0\n\t// keep; the; separators\n\tBYTE $1\n\tRET\n",
},
{
name: "after a statement, before a comment",
in: "TEXT ·f(SB), $0\nMOVQ AX, BX; // tail\nRET\n",
want: "TEXT ·f(SB), $0\n\tMOVQ AX, BX; // tail\n\tRET\n",
},
{
name: "last character on a line",
in: "TEXT ·f(SB), $0\nBYTE $1;\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $1;\n\tRET\n",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Source(tc.in)
if got != tc.want {
t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, tc.want)
}
if again := Source(got); again != got {
t.Fatalf("not idempotent:\n%q", again)
}
if in, out := strings.Count(tc.in, ";"), strings.Count(got, ";"); in != out {
t.Fatalf("semicolon count changed: %d -> %d\n%s", in, out, got)
}
if _, errs := parser.Parse("in.s", got); len(errs) > 0 {
t.Fatalf("formatted output no longer parses: %v", errs)
}
})
}
}
// TestSemicolonStatementRoundTrip proves the formatter's contract on the
// path where ';' separates statements: parse the source the way the
// assembler does, format it, re-parse the formatted text and compare the
// statement sequence. Raw operand texts are token-joined, so they are
// insensitive to the whitespace a format pass chooses, and the comparison
// can only fail when a token is lost: dropping a ';' fuses two statements
// into one, exactly the corruption the released formatter committed.
func TestSemicolonStatementRoundTrip(t *testing.T) {
src := "#define MOVLTOREG(v, off) \\\n" +
"\tMOVL $v, AX; \\\n" +
"\tMOVL AX, ret+off(FP)\n" +
"\n" +
"TEXT ·f(SB), NOSPLIT, $0\n" +
"BYTE $0x48; BYTE $0xc7\n" +
"first: BYTE $1; BYTE $2\n" +
"MOVLTOREG($42, 0)\n" +
"RET\n"
before, errs := parser.ParseWithOptions("in.s", src, parser.Options{Expand: true})
if len(errs) > 0 {
t.Fatalf("source does not parse: %v", errs)
}
formatted := Source(src)
after, errs := parser.ParseWithOptions("in.s", formatted, parser.Options{Expand: true})
if len(errs) > 0 {
t.Fatalf("formatted source does not parse: %v", errs)
}
want, got := stmtSignature(before), stmtSignature(after)
if !slices.Equal(got, want) {
t.Fatalf("statement sequence changed:\n--- before ---\n%q\n--- after ---\n%q", want, got)
}
if again := Source(formatted); again != formatted {
t.Fatalf("not idempotent:\n%q", again)
}
// The two BYTE statements on the first line must stay two: one fused
// statement here is the exact defect this package once shipped.
var bytes []string
for _, stmt := range stmtSignature(after) {
if rest, ok := strings.CutPrefix(stmt, "instr BYTE "); ok {
bytes = append(bytes, rest)
}
}
if want := []string{"$ 0x48", "$ 0xc7", "$ 1", "$ 2"}; !slices.Equal(bytes, want) {
t.Fatalf("BYTE statements after expansion = %q, want %q", bytes, want)
}
}
// stmtSignature flattens a parsed file into one string per declaration and
// statement, in source order. Every component is token-derived, so the
// signature is stable across format passes and moves only when a token is
// lost or gained.
func stmtSignature(f *ast.File) []string {
var out []string
for _, d := range f.Decls {
switch d := d.(type) {
case *ast.Text:
out = append(out, "text "+d.Name.Raw)
for _, s := range d.Body {
out = append(out, stmtText(s))
}
case *ast.Globl:
out = append(out, "globl "+d.Name.Raw)
case *ast.Data:
out = append(out, "data "+d.Name.Raw)
case *ast.Include:
out = append(out, "include "+d.Header.Text)
case *ast.Preproc:
out = append(out, "preproc "+d.Raw)
}
}
for _, s := range f.Orphans {
out = append(out, stmtText(s))
}
return out
}
// stmtText renders one statement for stmtSignature.
func stmtText(s ast.Stmt) string {
switch s := s.(type) {
case *ast.Label:
return "label " + s.Name.Text
case *ast.Instr:
parts := make([]string, 0, len(s.Operands)+1)
parts = append(parts, s.Mnemonic.Text)
for _, op := range s.Operands {
parts = append(parts, op.Raw)
}
return "instr " + strings.Join(parts, " ")
default:
return "stmt"
}
}
// lexOperands lexes a single operand string and drops the EOF token. // lexOperands lexes a single operand string and drops the EOF token.
func lexOperands(s string) []token.Token { func lexOperands(s string) []token.Token {
toks := lexer.Tokenize(s) toks := lexer.Tokenize(s)
+11 -3
View File
@@ -16,8 +16,16 @@ import (
"sourcedock.dev/petrbalvin/gasm-devkit/token" "sourcedock.dev/petrbalvin/gasm-devkit/token"
) )
// middleDot is the Plan 9 symbol separator (U+00B7), used in ·funcName(SB). const (
const middleDot = '\u00B7' // middleDot is the Plan 9 symbol separator (U+00B7), used in
// ·funcName(SB): it stands for the period between package path and name.
middleDot = '\u00B7'
// divisionSlash is the Plan 9 path separator (U+2215), used inside the
// package path of a symbol: internal∕runtime∕atomic·Xchg. Like the
// middle dot it is an identifier character, so a package path containing
// it lexes as one name; the ordinary slash (U+002F) stays punctuation.
divisionSlash = '\u2215'
)
// Lexer scans a source string one token at a time. // Lexer scans a source string one token at a time.
type Lexer struct { type Lexer struct {
@@ -460,7 +468,7 @@ func isHexDigit(r rune) bool {
} }
func isIdentStart(r rune) bool { func isIdentStart(r rune) bool {
return r == '_' || r == middleDot || unicode.IsLetter(r) return r == '_' || r == middleDot || r == divisionSlash || unicode.IsLetter(r)
} }
func isIdentChar(r rune) bool { func isIdentChar(r rune) bool {
+13
View File
@@ -182,6 +182,19 @@ func TestNulIsIllegal(t *testing.T) {
eq(t, texts("MOVQ \x00 AX"), []string{"MOVQ", "\x00", "AX"}) eq(t, texts("MOVQ \x00 AX"), []string{"MOVQ", "\x00", "AX"})
} }
func TestDivisionSlashInIdentifiers(t *testing.T) {
// U+2215 DIVISION SLASH is an identifier character, the way the
// toolchain's tokenizer treats it: the package path of a symbol is
// written with it (internal∕runtime∕atomic·Xchg) and must lex as one
// name. The ordinary slash (U+002F) stays punctuation.
eq(t, texts("CALL internal∕runtime∕atomic·Xchg(SB)"),
[]string{"CALL", "internal∕runtime∕atomic·Xchg", "(", "SB", ")"})
eq(t, texts("MOVQ sync∕atomic·Align(SB), AX"),
[]string{"MOVQ", "sync∕atomic·Align", "(", "SB", ")", ",", "AX"})
// It may also begin a name, like any letter of the toolchain's rule.
eq(t, kinds("∕x"), []token.Kind{token.Ident})
}
// TestOffsetsAroundInvalidByte pins Position.Offset against the original // TestOffsetsAroundInvalidByte pins Position.Offset against the original
// bytes: an invalid UTF-8 byte decodes to RuneError but advances the offset // bytes: an invalid UTF-8 byte decodes to RuneError but advances the offset
// table by exactly one byte, so every later position stays a true byte // table by exactly one byte, so every later position stays a true byte
+41 -2
View File
@@ -32,9 +32,8 @@ func (e Error) Error() string {
// returned file is usable even when errors is non-empty. // returned file is usable even when errors is non-empty.
func Parse(path, src string) (*ast.File, []error) { func Parse(path, src string) (*ast.File, []error) {
tokens := lexer.Tokenize(src) tokens := lexer.Tokenize(src)
lines := splitLines(tokens)
p := &state{path: path} p := &state{path: path}
p.parse(lines) p.parse(statementLines(tokens))
return p.file, p.errs return p.file, p.errs
} }
@@ -73,6 +72,46 @@ func splitLines(tokens []token.Token) [][]token.Token {
return lines return lines
} }
// statementLines turns the token stream into the logical lines the parser
// reads: physical lines split at the ';' statement separators, exactly the
// way the expansion path treats the expanded bodies. The runtime writes
// "ROLQ $3, DI; ROLQ $13, DI" and "REP; MOVSB" in plain files, and the
// separator carries no meaning beyond the break. Comments are statement
// text, not structure: the lexer delivers a whole comment as one token, so
// a ';' inside a comment is never a separator; a comment after a statement
// stays on that statement's line; and a comment that sits between
// statements (the runtime's "NO_LOCAL_POINTERS; /* … */" style) stands as
// its own logical line, like a whole-line comment.
func statementLines(tokens []token.Token) [][]token.Token {
var out [][]token.Token
var cur []token.Token
flush := func() {
if len(cur) > 0 {
out = append(out, cur)
cur = nil
}
}
for _, t := range tokens {
switch t.Kind {
case token.EOF:
// The stream's terminator is not statement content.
case token.Newline, token.Semicolon:
flush()
case token.Comment:
if len(cur) > 0 {
cur = append(cur, t)
} else {
out = append(out, []token.Token{t})
}
flush()
default:
cur = append(cur, t)
}
}
flush()
return out
}
func (p *state) parse(lines [][]token.Token) { func (p *state) parse(lines [][]token.Token) {
p.file = &ast.File{Path: p.path, Macros: map[string]bool{}} p.file = &ast.File{Path: p.path, Macros: map[string]bool{}}
for _, line := range lines { for _, line := range lines {
+96
View File
@@ -401,3 +401,99 @@ func TestInt64MinimumImmediate(t *testing.T) {
t.Errorf("imm.Float = %q, want empty", imm.Float) t.Errorf("imm.Float = %q, want empty", imm.Float)
} }
} }
// TestDivisionSlashPackagePath covers the runtime's package-path spelling:
// U+2215 DIVISION SLASH separates the elements of an import path inside a
// symbol (internal∕runtime∕atomic·Xchg), and the middle dot still separates
// the package from the name. The whole spelling must reach the symbol, not
// stop at the first slash.
func TestDivisionSlashPackagePath(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\n\tCALL internal∕runtime∕atomic·Xchg(SB)\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
instr := txt.Body[0].(*ast.Instr)
sym := instr.Operands[0].Addr.Sym
if sym == nil {
t.Fatal("operand carries no symbol")
}
if sym.Pkg != "internal∕runtime∕atomic" {
t.Errorf("pkg = %q, want internal∕runtime∕atomic", sym.Pkg)
}
if sym.Name != "Xchg" {
t.Errorf("name = %q, want Xchg", sym.Name)
}
if sym.Raw != "internal∕runtime∕atomic·Xchg(SB)" {
t.Errorf("raw = %q", sym.Raw)
}
}
// TestSemicolonStatements covers the plain parse path: ';' separates
// statements on one line exactly as it does inside macro expansion, and a
// ';' inside a comment is comment text.
func TestSemicolonStatements(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\n\tROLQ $3, DI; ROLQ $13, DI\n\tMOVQ AX, BX // note; still comment\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
if len(txt.Body) != 4 {
t.Fatalf("body = %d statements, want 4", len(txt.Body))
}
first := txt.Body[0].(*ast.Instr)
if first.Mnemonic.Text != "ROLQ" || len(first.Operands) != 2 {
t.Errorf("first statement = %+v, want ROLQ with two operands", first.Mnemonic)
}
second := txt.Body[1].(*ast.Instr)
if second.Mnemonic.Text != "ROLQ" || len(second.Operands) != 2 {
t.Errorf("second statement = %s, want ROLQ with two operands", second.Mnemonic.Text)
}
// The trailing comment belongs to the second MOVQ, semicolon included.
third := txt.Body[2].(*ast.Instr)
if third.Mnemonic.Text != "MOVQ" || third.Comment != "note; still comment" {
t.Errorf("third = %s, comment %q", third.Mnemonic.Text, third.Comment)
}
}
// TestSemicolonAfterLabel covers a label sharing its line with two
// statements.
func TestSemicolonAfterLabel(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\nloop: NOP; NOP\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
if len(txt.Body) != 4 {
t.Fatalf("body = %d statements, want 4 (label, two instructions, RET)", len(txt.Body))
}
if _, ok := txt.Body[0].(*ast.Label); !ok {
t.Errorf("first statement = %T, want *ast.Label", txt.Body[0])
}
for i, want := range []string{"NOP", "NOP", "RET"} {
in, ok := txt.Body[i+1].(*ast.Instr)
if !ok || in.Mnemonic.Text != want {
t.Errorf("statement %d = %v, want %s", i+1, txt.Body[i+1], want)
}
}
}
// TestParseEqualsZeroOptions pins the contract that ParseWithOptions with
// the zero Options reproduces Parse, here for the semicolon split.
func TestParseEqualsZeroOptions(t *testing.T) {
src := "TEXT \u00b7f(SB), $0\n\tNOP; NOP\n\tRET\n"
a, errsA := Parse("t.s", src)
b, errsB := ParseWithOptions("t.s", src, Options{})
if len(errsA) > 0 || len(errsB) > 0 {
t.Fatalf("errors: %v / %v", errsA, errsB)
}
ta, tb := texts(a), texts(b)
if len(ta) != len(tb) {
t.Fatalf("decl counts differ: %d vs %d", len(ta), len(tb))
}
for i := range ta {
if len(ta[i].Body) != len(tb[i].Body) {
t.Fatalf("TEXT %d: body lengths differ: %d vs %d", i, len(ta[i].Body), len(tb[i].Body))
}
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ func ParseWithOptions(path, src string, opts Options) (*ast.File, []error) {
lines = pp.fileLines(path, tokens, token.Position{}) lines = pp.fileLines(path, tokens, token.Position{})
errs = pp.errs errs = pp.errs
} else { } else {
lines = splitLines(tokens) lines = statementLines(tokens)
} }
p := &state{path: path} p := &state{path: path}
p.parse(lines) p.parse(lines)
+4 -2
View File
@@ -462,7 +462,9 @@ TEXT ·f(SB), NOSPLIT, $0
func TestParseUnchangedWithoutExpand(t *testing.T) { func TestParseUnchangedWithoutExpand(t *testing.T) {
// Without Expand the preprocessor must not exist: a macro invocation // Without Expand the preprocessor must not exist: a macro invocation
// stays an unexpanded instruction line and ';' keeps the old parse. // stays an unexpanded instruction line. The ';' statement separator is
// not part of the preprocessor: the plain parse path splits on it the
// same way the expansion path does, so both spellings agree.
f, errs := Parse("t_amd64.s", ` f, errs := Parse("t_amd64.s", `
#define TWICE ADDQ AX, AX #define TWICE ADDQ AX, AX
TEXT ·f(SB), NOSPLIT, $0 TEXT ·f(SB), NOSPLIT, $0
@@ -480,7 +482,7 @@ TEXT ·f(SB), NOSPLIT, $0
mnemonics = append(mnemonics, in.Mnemonic.Text) mnemonics = append(mnemonics, in.Mnemonic.Text)
} }
} }
if strings.Join(mnemonics, " ") != "TWICE BYTE RET" { if strings.Join(mnemonics, " ") != "TWICE BYTE BYTE RET" {
t.Errorf("non-expanding parse changed: %v", mnemonics) t.Errorf("non-expanding parse changed: %v", mnemonics)
} }
} }
+44
View File
@@ -0,0 +1,44 @@
// Differential kernel: the _dbar (acquire/release) atomic exchange
// variants against the Go toolchain's loong64enc1.s rows.
#include "textflag.h"
TEXT ·AMXORDBW(SB), NOSPLIT, $0
AMXORDBW R14, (R13), R12
RET
TEXT ·AMXORDBV(SB), NOSPLIT, $0
AMXORDBV R14, (R13), R12
RET
TEXT ·AMMAXDBW(SB), NOSPLIT, $0
AMMAXDBW R14, (R13), R12
RET
TEXT ·AMMAXDBV(SB), NOSPLIT, $0
AMMAXDBV R14, (R13), R12
RET
TEXT ·AMMINDBW(SB), NOSPLIT, $0
AMMINDBW R14, (R13), R12
RET
TEXT ·AMMINDBV(SB), NOSPLIT, $0
AMMINDBV R14, (R13), R12
RET
TEXT ·AMMAXDBWU(SB), NOSPLIT, $0
AMMAXDBWU R14, (R13), R12
RET
TEXT ·AMMAXDBVU(SB), NOSPLIT, $0
AMMAXDBVU R14, (R13), R12
RET
TEXT ·AMMINDBWU(SB), NOSPLIT, $0
AMMINDBWU R14, (R13), R12
RET
TEXT ·AMMINDBVU(SB), NOSPLIT, $0
AMMINDBVU R14, (R13), R12
RET
+25
View File
@@ -0,0 +1,25 @@
#include "textflag.h"
// The kernel exercises the symbol-valued DATA spelling the runtime's rt0
// files use: a data word holding the address of a symbol, resolved by the
// linker through a relocation at the field.
// func lookup() ptr
TEXT ·lookup(SB), NOSPLIT, $0-8
MOVQ handlers+8(SB), AX
MOVQ AX, ret+0(FP)
RET
// func handler() int64
TEXT ·handler(SB), NOSPLIT, $0-8
MOVQ $42, AX
MOVQ AX, ret+0(FP)
RET
GLOBL handlers(SB), NOPTR, $24
DATA handlers+0(SB)/8, $·handler(SB)
DATA handlers+8(SB)/8, $table(SB)
DATA handlers+16(SB)/8, $·handler+5(SB)
GLOBL table(SB), RODATA, $8
DATA table+0(SB)/8, $0x123456789abcdef0
+25
View File
@@ -0,0 +1,25 @@
#include "textflag.h"
// The kernel exercises the symbol-valued DATA spelling the runtime's rt0
// files use: a data word holding the address of a symbol, resolved by the
// linker through a relocation at the field.
// func lookup() ptr
TEXT ·lookup(SB), NOSPLIT, $0-8
MOVD handlers+8(SB), R4
MOVD R4, ret+0(FP)
RET
// func handler() int64
TEXT ·handler(SB), NOSPLIT, $0-8
MOVZ $42, R4
MOVD R4, ret+0(FP)
RET
GLOBL handlers(SB), NOPTR, $24
DATA handlers+0(SB)/8, $·handler(SB)
DATA handlers+8(SB)/8, $table(SB)
DATA handlers+16(SB)/8, $extentry(SB)
GLOBL table(SB), RODATA, $8
DATA table+0(SB)/8, $0x123456789abcdef0
+19
View File
@@ -0,0 +1,19 @@
#include "textflag.h"
// The kernel exercises the U+2215 DIVISION SLASH inside a symbol's package
// path: internal∕runtime∕atomic·Xchg, the spelling sync/atomic/asm.s uses.
// The middle dot (U+00B7) still separates the package path from the name.
// func swap(a, b int64) int64
TEXT ·swap(SB), NOSPLIT, $0-24
MOVQ a+0(FP), DI
MOVQ b+8(FP), SI
CALL internal∕runtime∕atomic·Xchg(SB)
MOVQ AX, ret+16(FP)
RET
// func note() int64
TEXT ·note(SB), NOSPLIT, $0-8
CALL runtime∕debug·SetGCPercent(SB)
MOVQ AX, ret+0(FP)
RET
+19
View File
@@ -0,0 +1,19 @@
#include "textflag.h"
// The kernel exercises the U+2215 DIVISION SLASH inside a symbol's package
// path: internal∕runtime∕atomic·Xchg, the spelling sync/atomic/asm.s uses.
// The middle dot (U+00B7) still separates the package path from the name.
// func swap(a, b int64) int64
TEXT ·swap(SB), NOSPLIT, $0-24
MOVD a+0(FP), R4
MOVD b+8(FP), R5
CALL internal∕runtime∕atomic·Xchg(SB)
MOVD R4, ret+16(FP)
RET
// func note() int64
TEXT ·note(SB), NOSPLIT, $0-8
CALL runtime∕debug·SetGCPercent(SB)
MOVD R0, ret+0(FP)
RET
+31
View File
@@ -0,0 +1,31 @@
// Differential kernel: the bookkeeping statements the assembler accepts and
// encodes to nothing (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·end(SB), NOSPLIT, $0
END
RET
TEXT ·funcdata(SB), NOSPLIT, $0
FUNCDATA $0, ref(SB)
RET
TEXT ·pcdata(SB), NOSPLIT, $0
PCDATA $0, $1
PCDATA $1, $-2
RET
TEXT ·getcallerpc(SB), NOSPLIT, $0
GETCALLERPC R4
RET
TEXT ·mixed(SB), NOSPLIT, $0
PCDATA $0, $1
ADDV R4, R5, R6
FUNCDATA $1, ref(SB)
GETCALLERPC R7
RET
ref:
RET
+21
View File
@@ -0,0 +1,21 @@
#include "textflag.h"
// The kernel exercises the ';' statement separator in a plain file, the way
// the runtime writes it ("ROLQ $3, DI; ROLQ $13, DI", "REP; MOVSQ"). Each
// statement assembles exactly as it would on a line of its own.
// func rol(x int64) int64
TEXT ·rol(SB), NOSPLIT, $0-16
ROLQ $3, DI; ROLQ $13, DI
MOVQ DI, ret+0(FP)
RET
// func move(dst, src unsafe.Pointer)
TEXT ·move(SB), NOSPLIT, $0-16
REP ; MOVSQ
RET
TEXT ·paired(SB), NOSPLIT, $0-8
XORQ AX, AX; XORQ CX, CX
MOVQ AX, ret+0(FP)
RET
+209
View File
@@ -0,0 +1,209 @@
// Differential kernel: the arith_add vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VADDB(SB), NOSPLIT, $0
VADDB V1, V2, V3
RET
TEXT ·VADDH(SB), NOSPLIT, $0
VADDH V1, V2, V3
RET
TEXT ·VADDQ(SB), NOSPLIT, $0
VADDQ V1, V2, V3
RET
TEXT ·XVADDB(SB), NOSPLIT, $0
XVADDB X3, X2, X1
RET
TEXT ·XVADDH(SB), NOSPLIT, $0
XVADDH X3, X2, X1
RET
TEXT ·XVADDW(SB), NOSPLIT, $0
XVADDW X3, X2, X1
RET
TEXT ·XVADDQ(SB), NOSPLIT, $0
XVADDQ X3, X2, X1
RET
TEXT ·VADDBU(SB), NOSPLIT, $0
VADDBU $1, V2
VADDBU $1, V2, V1
RET
TEXT ·VADDHU(SB), NOSPLIT, $0
VADDHU $2, V2, V1
RET
TEXT ·VADDWU(SB), NOSPLIT, $0
VADDWU $3, V2, V1
RET
TEXT ·VADDVU(SB), NOSPLIT, $0
VADDVU $4, V2, V1
RET
TEXT ·XVADDBU(SB), NOSPLIT, $0
XVADDBU $9, X1, X2
RET
TEXT ·XVADDHU(SB), NOSPLIT, $0
XVADDHU $10, X1, X2
RET
TEXT ·XVADDWU(SB), NOSPLIT, $0
XVADDWU $11, X1, X2
RET
TEXT ·XVADDVU(SB), NOSPLIT, $0
XVADDVU $12, X1, X2
RET
TEXT ·VADDWEVHB(SB), NOSPLIT, $0
VADDWEVHB V1, V2, V3
RET
TEXT ·VADDWEVWH(SB), NOSPLIT, $0
VADDWEVWH V1, V2, V3
RET
TEXT ·VADDWEVVW(SB), NOSPLIT, $0
VADDWEVVW V1, V2, V3
RET
TEXT ·VADDWEVQV(SB), NOSPLIT, $0
VADDWEVQV V1, V2, V3
RET
TEXT ·VADDWODHB(SB), NOSPLIT, $0
VADDWODHB V1, V2, V3
RET
TEXT ·VADDWODWH(SB), NOSPLIT, $0
VADDWODWH V1, V2, V3
RET
TEXT ·VADDWODVW(SB), NOSPLIT, $0
VADDWODVW V1, V2, V3
RET
TEXT ·VADDWODQV(SB), NOSPLIT, $0
VADDWODQV V1, V2, V3
RET
TEXT ·XVADDWEVHB(SB), NOSPLIT, $0
XVADDWEVHB X1, X2, X3
RET
TEXT ·XVADDWEVWH(SB), NOSPLIT, $0
XVADDWEVWH X1, X2, X3
RET
TEXT ·XVADDWEVVW(SB), NOSPLIT, $0
XVADDWEVVW X1, X2, X3
RET
TEXT ·XVADDWEVQV(SB), NOSPLIT, $0
XVADDWEVQV X1, X2, X3
RET
TEXT ·XVADDWODHB(SB), NOSPLIT, $0
XVADDWODHB X1, X2, X3
RET
TEXT ·XVADDWODWH(SB), NOSPLIT, $0
XVADDWODWH X1, X2, X3
RET
TEXT ·XVADDWODVW(SB), NOSPLIT, $0
XVADDWODVW X1, X2, X3
RET
TEXT ·XVADDWODQV(SB), NOSPLIT, $0
XVADDWODQV X1, X2, X3
RET
TEXT ·VADDWEVHBU(SB), NOSPLIT, $0
VADDWEVHBU V1, V2, V3
RET
TEXT ·VADDWEVWHU(SB), NOSPLIT, $0
VADDWEVWHU V1, V2, V3
RET
TEXT ·VADDWEVVWU(SB), NOSPLIT, $0
VADDWEVVWU V1, V2, V3
RET
TEXT ·VADDWEVQVU(SB), NOSPLIT, $0
VADDWEVQVU V1, V2, V3
RET
TEXT ·VADDWODHBU(SB), NOSPLIT, $0
VADDWODHBU V1, V2, V3
RET
TEXT ·VADDWODWHU(SB), NOSPLIT, $0
VADDWODWHU V1, V2, V3
RET
TEXT ·VADDWODVWU(SB), NOSPLIT, $0
VADDWODVWU V1, V2, V3
RET
TEXT ·VADDWODQVU(SB), NOSPLIT, $0
VADDWODQVU V1, V2, V3
RET
TEXT ·XVADDWEVHBU(SB), NOSPLIT, $0
XVADDWEVHBU X1, X2, X3
RET
TEXT ·XVADDWEVWHU(SB), NOSPLIT, $0
XVADDWEVWHU X1, X2, X3
RET
TEXT ·XVADDWEVVWU(SB), NOSPLIT, $0
XVADDWEVVWU X1, X2, X3
RET
TEXT ·XVADDWEVQVU(SB), NOSPLIT, $0
XVADDWEVQVU X1, X2, X3
RET
TEXT ·XVADDWODHBU(SB), NOSPLIT, $0
XVADDWODHBU X1, X2, X3
RET
TEXT ·XVADDWODWHU(SB), NOSPLIT, $0
XVADDWODWHU X1, X2, X3
RET
TEXT ·XVADDWODVWU(SB), NOSPLIT, $0
XVADDWODVWU X1, X2, X3
RET
TEXT ·XVADDWODQVU(SB), NOSPLIT, $0
XVADDWODQVU X1, X2, X3
RET
TEXT ·VADDF(SB), NOSPLIT, $0
VADDF V1, V2, V3
RET
TEXT ·VADDD(SB), NOSPLIT, $0
VADDD V1, V2, V3
RET
TEXT ·XVADDF(SB), NOSPLIT, $0
XVADDF X1, X2, X3
RET
TEXT ·XVADDD(SB), NOSPLIT, $0
XVADDD X1, X2, X3
RET
+132
View File
@@ -0,0 +1,132 @@
// Differential kernel: the arith_sat vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VSADDB(SB), NOSPLIT, $0
VSADDB V1, V2, V3
RET
TEXT ·VSADDH(SB), NOSPLIT, $0
VSADDH V1, V2, V3
RET
TEXT ·VSADDW(SB), NOSPLIT, $0
VSADDW V1, V2, V3
RET
TEXT ·VSADDV(SB), NOSPLIT, $0
VSADDV V1, V2, V3
RET
TEXT ·VSSUBB(SB), NOSPLIT, $0
VSSUBB V1, V2, V3
RET
TEXT ·VSSUBH(SB), NOSPLIT, $0
VSSUBH V1, V2, V3
RET
TEXT ·VSSUBW(SB), NOSPLIT, $0
VSSUBW V1, V2, V3
RET
TEXT ·VSSUBV(SB), NOSPLIT, $0
VSSUBV V1, V2, V3
RET
TEXT ·XVSADDB(SB), NOSPLIT, $0
XVSADDB X3, X2, X1
RET
TEXT ·XVSADDH(SB), NOSPLIT, $0
XVSADDH X3, X2, X1
RET
TEXT ·XVSADDW(SB), NOSPLIT, $0
XVSADDW X3, X2, X1
RET
TEXT ·XVSADDV(SB), NOSPLIT, $0
XVSADDV X3, X2, X1
RET
TEXT ·XVSSUBB(SB), NOSPLIT, $0
XVSSUBB X3, X2, X1
RET
TEXT ·XVSSUBH(SB), NOSPLIT, $0
XVSSUBH X3, X2, X1
RET
TEXT ·XVSSUBW(SB), NOSPLIT, $0
XVSSUBW X3, X2, X1
RET
TEXT ·XVSSUBV(SB), NOSPLIT, $0
XVSSUBV X3, X2, X1
RET
TEXT ·VSADDBU(SB), NOSPLIT, $0
VSADDBU V1, V2, V3
RET
TEXT ·VSADDHU(SB), NOSPLIT, $0
VSADDHU V1, V2, V3
RET
TEXT ·VSADDWU(SB), NOSPLIT, $0
VSADDWU V1, V2, V3
RET
TEXT ·VSADDVU(SB), NOSPLIT, $0
VSADDVU V1, V2, V3
RET
TEXT ·VSSUBBU(SB), NOSPLIT, $0
VSSUBBU V1, V2, V3
RET
TEXT ·VSSUBHU(SB), NOSPLIT, $0
VSSUBHU V1, V2, V3
RET
TEXT ·VSSUBWU(SB), NOSPLIT, $0
VSSUBWU V1, V2, V3
RET
TEXT ·VSSUBVU(SB), NOSPLIT, $0
VSSUBVU V1, V2, V3
RET
TEXT ·XVSADDBU(SB), NOSPLIT, $0
XVSADDBU X1, X2, X3
RET
TEXT ·XVSADDHU(SB), NOSPLIT, $0
XVSADDHU X1, X2, X3
RET
TEXT ·XVSADDWU(SB), NOSPLIT, $0
XVSADDWU X1, X2, X3
RET
TEXT ·XVSADDVU(SB), NOSPLIT, $0
XVSADDVU X1, X2, X3
RET
TEXT ·XVSSUBBU(SB), NOSPLIT, $0
XVSSUBBU X1, X2, X3
RET
TEXT ·XVSSUBHU(SB), NOSPLIT, $0
XVSSUBHU X1, X2, X3
RET
TEXT ·XVSSUBWU(SB), NOSPLIT, $0
XVSSUBWU X1, X2, X3
RET
TEXT ·XVSSUBVU(SB), NOSPLIT, $0
XVSSUBVU X1, X2, X3
RET
+220
View File
@@ -0,0 +1,220 @@
// Differential kernel: the arith_sub vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VSUBB(SB), NOSPLIT, $0
VSUBB V1, V2, V3
RET
TEXT ·VSUBH(SB), NOSPLIT, $0
VSUBH V1, V2, V3
RET
TEXT ·VSUBW(SB), NOSPLIT, $0
VSUBW V1, V2, V3
RET
TEXT ·VSUBV(SB), NOSPLIT, $0
VSUBV V1, V2, V3
RET
TEXT ·VSUBQ(SB), NOSPLIT, $0
VSUBQ V1, V2, V3
RET
TEXT ·XVSUBB(SB), NOSPLIT, $0
XVSUBB X3, X2, X1
RET
TEXT ·XVSUBH(SB), NOSPLIT, $0
XVSUBH X3, X2, X1
RET
TEXT ·XVSUBW(SB), NOSPLIT, $0
XVSUBW X3, X2, X1
RET
TEXT ·XVSUBV(SB), NOSPLIT, $0
XVSUBV X3, X2, X1
RET
TEXT ·XVSUBQ(SB), NOSPLIT, $0
XVSUBQ X3, X2, X1
RET
TEXT ·VSUBBU(SB), NOSPLIT, $0
VSUBBU $5, V2, V1
RET
TEXT ·VSUBHU(SB), NOSPLIT, $0
VSUBHU $6, V2, V1
RET
TEXT ·VSUBWU(SB), NOSPLIT, $0
VSUBWU $7, V2, V1
RET
TEXT ·VSUBVU(SB), NOSPLIT, $0
VSUBVU $8, V2, V1
RET
TEXT ·XVSUBBU(SB), NOSPLIT, $0
XVSUBBU $13, X1, X2
RET
TEXT ·XVSUBHU(SB), NOSPLIT, $0
XVSUBHU $14, X1, X2
RET
TEXT ·XVSUBWU(SB), NOSPLIT, $0
XVSUBWU $15, X1, X2
RET
TEXT ·XVSUBVU(SB), NOSPLIT, $0
XVSUBVU $16, X1, X2
RET
TEXT ·VSUBWEVHB(SB), NOSPLIT, $0
VSUBWEVHB V1, V2, V3
RET
TEXT ·VSUBWEVWH(SB), NOSPLIT, $0
VSUBWEVWH V1, V2, V3
RET
TEXT ·VSUBWEVVW(SB), NOSPLIT, $0
VSUBWEVVW V1, V2, V3
RET
TEXT ·VSUBWEVQV(SB), NOSPLIT, $0
VSUBWEVQV V1, V2, V3
RET
TEXT ·VSUBWODHB(SB), NOSPLIT, $0
VSUBWODHB V1, V2, V3
RET
TEXT ·VSUBWODWH(SB), NOSPLIT, $0
VSUBWODWH V1, V2, V3
RET
TEXT ·VSUBWODVW(SB), NOSPLIT, $0
VSUBWODVW V1, V2, V3
RET
TEXT ·VSUBWODQV(SB), NOSPLIT, $0
VSUBWODQV V1, V2, V3
RET
TEXT ·XVSUBWEVHB(SB), NOSPLIT, $0
XVSUBWEVHB X1, X2, X3
RET
TEXT ·XVSUBWEVWH(SB), NOSPLIT, $0
XVSUBWEVWH X1, X2, X3
RET
TEXT ·XVSUBWEVVW(SB), NOSPLIT, $0
XVSUBWEVVW X1, X2, X3
RET
TEXT ·XVSUBWEVQV(SB), NOSPLIT, $0
XVSUBWEVQV X1, X2, X3
RET
TEXT ·XVSUBWODHB(SB), NOSPLIT, $0
XVSUBWODHB X1, X2, X3
RET
TEXT ·XVSUBWODWH(SB), NOSPLIT, $0
XVSUBWODWH X1, X2, X3
RET
TEXT ·XVSUBWODVW(SB), NOSPLIT, $0
XVSUBWODVW X1, X2, X3
RET
TEXT ·XVSUBWODQV(SB), NOSPLIT, $0
XVSUBWODQV X1, X2, X3
RET
TEXT ·VSUBWEVHBU(SB), NOSPLIT, $0
VSUBWEVHBU V1, V2, V3
RET
TEXT ·VSUBWEVWHU(SB), NOSPLIT, $0
VSUBWEVWHU V1, V2, V3
RET
TEXT ·VSUBWEVVWU(SB), NOSPLIT, $0
VSUBWEVVWU V1, V2, V3
RET
TEXT ·VSUBWEVQVU(SB), NOSPLIT, $0
VSUBWEVQVU V1, V2, V3
RET
TEXT ·VSUBWODHBU(SB), NOSPLIT, $0
VSUBWODHBU V1, V2, V3
RET
TEXT ·VSUBWODWHU(SB), NOSPLIT, $0
VSUBWODWHU V1, V2, V3
RET
TEXT ·VSUBWODVWU(SB), NOSPLIT, $0
VSUBWODVWU V1, V2, V3
RET
TEXT ·VSUBWODQVU(SB), NOSPLIT, $0
VSUBWODQVU V1, V2, V3
RET
TEXT ·XVSUBWEVHBU(SB), NOSPLIT, $0
XVSUBWEVHBU X1, X2, X3
RET
TEXT ·XVSUBWEVWHU(SB), NOSPLIT, $0
XVSUBWEVWHU X1, X2, X3
RET
TEXT ·XVSUBWEVVWU(SB), NOSPLIT, $0
XVSUBWEVVWU X1, X2, X3
RET
TEXT ·XVSUBWEVQVU(SB), NOSPLIT, $0
XVSUBWEVQVU X1, X2, X3
RET
TEXT ·XVSUBWODHBU(SB), NOSPLIT, $0
XVSUBWODHBU X1, X2, X3
RET
TEXT ·XVSUBWODWHU(SB), NOSPLIT, $0
XVSUBWODWHU X1, X2, X3
RET
TEXT ·XVSUBWODVWU(SB), NOSPLIT, $0
XVSUBWODVWU X1, X2, X3
RET
TEXT ·XVSUBWODQVU(SB), NOSPLIT, $0
XVSUBWODQVU X1, X2, X3
RET
TEXT ·VSUBF(SB), NOSPLIT, $0
VSUBF V1, V2, V3
RET
TEXT ·VSUBD(SB), NOSPLIT, $0
VSUBD V1, V2, V3
RET
TEXT ·XVSUBF(SB), NOSPLIT, $0
XVSUBF X1, X2, X3
RET
TEXT ·XVSUBD(SB), NOSPLIT, $0
XVSUBD X1, X2, X3
RET
+124
View File
@@ -0,0 +1,124 @@
// Differential kernel: the bitops vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VBITCLRB(SB), NOSPLIT, $0
VBITCLRB V1, V2, V3
VBITCLRB $7, V2, V3
RET
TEXT ·VBITCLRH(SB), NOSPLIT, $0
VBITCLRH V1, V2, V3
VBITCLRH $15, V2, V3
RET
TEXT ·VBITCLRW(SB), NOSPLIT, $0
VBITCLRW V1, V2, V3
VBITCLRW $31, V2, V3
RET
TEXT ·VBITCLRV(SB), NOSPLIT, $0
VBITCLRV V1, V2, V3
VBITCLRV $63, V2, V3
RET
TEXT ·VBITSETB(SB), NOSPLIT, $0
VBITSETB V1, V2, V3
VBITSETB $7, V2, V3
RET
TEXT ·VBITSETH(SB), NOSPLIT, $0
VBITSETH V1, V2, V3
VBITSETH $15, V2, V3
RET
TEXT ·VBITSETW(SB), NOSPLIT, $0
VBITSETW V1, V2, V3
VBITSETW $31, V2, V3
RET
TEXT ·VBITSETV(SB), NOSPLIT, $0
VBITSETV V1, V2, V3
VBITSETV $63, V2, V3
RET
TEXT ·VBITREVB(SB), NOSPLIT, $0
VBITREVB V1, V2, V3
VBITREVB $7, V2, V3
RET
TEXT ·VBITREVH(SB), NOSPLIT, $0
VBITREVH V1, V2, V3
VBITREVH $15, V2, V3
RET
TEXT ·VBITREVW(SB), NOSPLIT, $0
VBITREVW V1, V2, V3
VBITREVW $31, V2, V3
RET
TEXT ·VBITREVV(SB), NOSPLIT, $0
VBITREVV V1, V2, V3
VBITREVV $63, V2, V3
RET
TEXT ·XVBITCLRB(SB), NOSPLIT, $0
XVBITCLRB X3, X2, X1
XVBITCLRB $7, X2, X1
RET
TEXT ·XVBITCLRH(SB), NOSPLIT, $0
XVBITCLRH X3, X2, X1
XVBITCLRH $15, X2, X1
RET
TEXT ·XVBITCLRW(SB), NOSPLIT, $0
XVBITCLRW X3, X2, X1
XVBITCLRW $31, X2, X1
RET
TEXT ·XVBITCLRV(SB), NOSPLIT, $0
XVBITCLRV X3, X2, X1
XVBITCLRV $63, X2, X1
RET
TEXT ·XVBITSETB(SB), NOSPLIT, $0
XVBITSETB X3, X2, X1
XVBITSETB $7, X2, X1
RET
TEXT ·XVBITSETH(SB), NOSPLIT, $0
XVBITSETH X3, X2, X1
XVBITSETH $15, X2, X1
RET
TEXT ·XVBITSETW(SB), NOSPLIT, $0
XVBITSETW X3, X2, X1
XVBITSETW $31, X2, X1
RET
TEXT ·XVBITSETV(SB), NOSPLIT, $0
XVBITSETV X3, X2, X1
XVBITSETV $63, X2, X1
RET
TEXT ·XVBITREVB(SB), NOSPLIT, $0
XVBITREVB X3, X2, X1
XVBITREVB $7, X2, X1
RET
TEXT ·XVBITREVH(SB), NOSPLIT, $0
XVBITREVH X3, X2, X1
XVBITREVH $15, X2, X1
RET
TEXT ·XVBITREVW(SB), NOSPLIT, $0
XVBITREVW X3, X2, X1
XVBITREVW $31, X2, X1
RET
TEXT ·XVBITREVV(SB), NOSPLIT, $0
XVBITREVV X3, X2, X1
XVBITREVV $63, X2, X1
RET
+419
View File
@@ -0,0 +1,419 @@
// Differential kernel: immediate range boundaries for the vector
// immediate forms (min, mid and max of each accepted window).
#include "textflag.h"
TEXT ·VSEQBbounds(SB), NOSPLIT, $0
VSEQB $-16, V2, V3
VSEQB $15, V2, V3
VSEQB $0, V2, V3
RET
TEXT ·VSEQHbounds(SB), NOSPLIT, $0
VSEQH $-16, V2, V3
VSEQH $15, V2, V3
VSEQH $0, V2, V3
RET
TEXT ·VSEQWbounds(SB), NOSPLIT, $0
VSEQW $-16, V2, V3
VSEQW $15, V2, V3
VSEQW $0, V2, V3
RET
TEXT ·VSEQVbounds(SB), NOSPLIT, $0
VSEQV $-16, V2, V3
VSEQV $15, V2, V3
VSEQV $0, V2, V3
RET
TEXT ·VSLTBbounds(SB), NOSPLIT, $0
VSLTB $-16, V2, V3
VSLTB $15, V2, V3
VSLTB $0, V2, V3
RET
TEXT ·VSLTHbounds(SB), NOSPLIT, $0
VSLTH $-16, V2, V3
VSLTH $15, V2, V3
VSLTH $0, V2, V3
RET
TEXT ·VSLTWbounds(SB), NOSPLIT, $0
VSLTW $-16, V2, V3
VSLTW $15, V2, V3
VSLTW $0, V2, V3
RET
TEXT ·VSLTVbounds(SB), NOSPLIT, $0
VSLTV $-16, V2, V3
VSLTV $15, V2, V3
VSLTV $0, V2, V3
RET
TEXT ·VSLTBUbounds(SB), NOSPLIT, $0
VSLTBU $0, V2, V3
VSLTBU $31, V2, V3
VSLTBU $15, V2, V3
RET
TEXT ·VSLTHUbounds(SB), NOSPLIT, $0
VSLTHU $0, V2, V3
VSLTHU $31, V2, V3
VSLTHU $15, V2, V3
RET
TEXT ·VSLTWUbounds(SB), NOSPLIT, $0
VSLTWU $0, V2, V3
VSLTWU $31, V2, V3
VSLTWU $15, V2, V3
RET
TEXT ·VSLTVUbounds(SB), NOSPLIT, $0
VSLTVU $0, V2, V3
VSLTVU $31, V2, V3
VSLTVU $15, V2, V3
RET
TEXT ·VADDBUbounds(SB), NOSPLIT, $0
VADDBU $0, V2, V3
VADDBU $31, V2, V3
VADDBU $15, V2, V3
VADDBU $15, V2
RET
TEXT ·VADDHUbounds(SB), NOSPLIT, $0
VADDHU $0, V2, V3
VADDHU $31, V2, V3
VADDHU $15, V2, V3
VADDHU $15, V2
RET
TEXT ·VADDWUbounds(SB), NOSPLIT, $0
VADDWU $0, V2, V3
VADDWU $31, V2, V3
VADDWU $15, V2, V3
VADDWU $15, V2
RET
TEXT ·VADDVUbounds(SB), NOSPLIT, $0
VADDVU $0, V2, V3
VADDVU $31, V2, V3
VADDVU $15, V2, V3
VADDVU $15, V2
RET
TEXT ·VSUBBUbounds(SB), NOSPLIT, $0
VSUBBU $0, V2, V3
VSUBBU $31, V2, V3
VSUBBU $15, V2, V3
VSUBBU $15, V2
RET
TEXT ·VSUBHUbounds(SB), NOSPLIT, $0
VSUBHU $0, V2, V3
VSUBHU $31, V2, V3
VSUBHU $15, V2, V3
VSUBHU $15, V2
RET
TEXT ·VSUBWUbounds(SB), NOSPLIT, $0
VSUBWU $0, V2, V3
VSUBWU $31, V2, V3
VSUBWU $15, V2, V3
VSUBWU $15, V2
RET
TEXT ·VSUBVUbounds(SB), NOSPLIT, $0
VSUBVU $0, V2, V3
VSUBVU $31, V2, V3
VSUBVU $15, V2, V3
VSUBVU $15, V2
RET
TEXT ·VANDBbounds(SB), NOSPLIT, $0
VANDB $0, V2, V3
VANDB $255, V2, V3
VANDB $127, V2, V3
VANDB $127, V2
RET
TEXT ·VBITCLRBbounds(SB), NOSPLIT, $0
VBITCLRB $0, V2, V3
VBITCLRB $7, V2, V3
VBITCLRB $3, V2, V3
VBITCLRB $3, V2
RET
TEXT ·VBITCLRHbounds(SB), NOSPLIT, $0
VBITCLRH $0, V2, V3
VBITCLRH $15, V2, V3
VBITCLRH $7, V2, V3
VBITCLRH $7, V2
RET
TEXT ·VBITCLRVbounds(SB), NOSPLIT, $0
VBITCLRV $0, V2, V3
VBITCLRV $63, V2, V3
VBITCLRV $31, V2, V3
VBITCLRV $31, V2
RET
TEXT ·VBITCLRWbounds(SB), NOSPLIT, $0
VBITCLRW $0, V2, V3
VBITCLRW $31, V2, V3
VBITCLRW $15, V2, V3
VBITCLRW $15, V2
RET
TEXT ·VBITREVBbounds(SB), NOSPLIT, $0
VBITREVB $0, V2, V3
VBITREVB $7, V2, V3
VBITREVB $3, V2, V3
VBITREVB $3, V2
RET
TEXT ·VBITREVHbounds(SB), NOSPLIT, $0
VBITREVH $0, V2, V3
VBITREVH $15, V2, V3
VBITREVH $7, V2, V3
VBITREVH $7, V2
RET
TEXT ·VBITREVVbounds(SB), NOSPLIT, $0
VBITREVV $0, V2, V3
VBITREVV $63, V2, V3
VBITREVV $31, V2, V3
VBITREVV $31, V2
RET
TEXT ·VBITREVWbounds(SB), NOSPLIT, $0
VBITREVW $0, V2, V3
VBITREVW $31, V2, V3
VBITREVW $15, V2, V3
VBITREVW $15, V2
RET
TEXT ·VBITSETBbounds(SB), NOSPLIT, $0
VBITSETB $0, V2, V3
VBITSETB $7, V2, V3
VBITSETB $3, V2, V3
VBITSETB $3, V2
RET
TEXT ·VBITSETHbounds(SB), NOSPLIT, $0
VBITSETH $0, V2, V3
VBITSETH $15, V2, V3
VBITSETH $7, V2, V3
VBITSETH $7, V2
RET
TEXT ·VBITSETVbounds(SB), NOSPLIT, $0
VBITSETV $0, V2, V3
VBITSETV $63, V2, V3
VBITSETV $31, V2, V3
VBITSETV $31, V2
RET
TEXT ·VBITSETWbounds(SB), NOSPLIT, $0
VBITSETW $0, V2, V3
VBITSETW $31, V2, V3
VBITSETW $15, V2, V3
VBITSETW $15, V2
RET
TEXT ·VEXTRINSBbounds(SB), NOSPLIT, $0
VEXTRINSB $0, V2, V3
VEXTRINSB $255, V2, V3
VEXTRINSB $127, V2, V3
VEXTRINSB $127, V2
RET
TEXT ·VEXTRINSHbounds(SB), NOSPLIT, $0
VEXTRINSH $0, V2, V3
VEXTRINSH $255, V2, V3
VEXTRINSH $127, V2, V3
VEXTRINSH $127, V2
RET
TEXT ·VEXTRINSVbounds(SB), NOSPLIT, $0
VEXTRINSV $0, V2, V3
VEXTRINSV $255, V2, V3
VEXTRINSV $127, V2, V3
VEXTRINSV $127, V2
RET
TEXT ·VEXTRINSWbounds(SB), NOSPLIT, $0
VEXTRINSW $0, V2, V3
VEXTRINSW $255, V2, V3
VEXTRINSW $127, V2, V3
VEXTRINSW $127, V2
RET
TEXT ·VNORBbounds(SB), NOSPLIT, $0
VNORB $0, V2, V3
VNORB $255, V2, V3
VNORB $127, V2, V3
VNORB $127, V2
RET
TEXT ·VORBbounds(SB), NOSPLIT, $0
VORB $0, V2, V3
VORB $255, V2, V3
VORB $127, V2, V3
VORB $127, V2
RET
TEXT ·VPERMIWbounds(SB), NOSPLIT, $0
VPERMIW $0, V2, V3
VPERMIW $255, V2, V3
VPERMIW $127, V2, V3
VPERMIW $127, V2
RET
TEXT ·VROTRBbounds(SB), NOSPLIT, $0
VROTRB $0, V2, V3
VROTRB $7, V2, V3
VROTRB $3, V2, V3
VROTRB $3, V2
RET
TEXT ·VROTRHbounds(SB), NOSPLIT, $0
VROTRH $0, V2, V3
VROTRH $15, V2, V3
VROTRH $7, V2, V3
VROTRH $7, V2
RET
TEXT ·VROTRVbounds(SB), NOSPLIT, $0
VROTRV $0, V2, V3
VROTRV $63, V2, V3
VROTRV $31, V2, V3
VROTRV $31, V2
RET
TEXT ·VROTRWbounds(SB), NOSPLIT, $0
VROTRW $0, V2, V3
VROTRW $31, V2, V3
VROTRW $15, V2, V3
VROTRW $15, V2
RET
TEXT ·VSHUF4IBbounds(SB), NOSPLIT, $0
VSHUF4IB $0, V2, V3
VSHUF4IB $255, V2, V3
VSHUF4IB $127, V2, V3
VSHUF4IB $127, V2
RET
TEXT ·VSHUF4IHbounds(SB), NOSPLIT, $0
VSHUF4IH $0, V2, V3
VSHUF4IH $255, V2, V3
VSHUF4IH $127, V2, V3
VSHUF4IH $127, V2
RET
TEXT ·VSHUF4IVbounds(SB), NOSPLIT, $0
VSHUF4IV $0, V2, V3
VSHUF4IV $15, V2, V3
VSHUF4IV $7, V2, V3
VSHUF4IV $7, V2
RET
TEXT ·VSHUF4IWbounds(SB), NOSPLIT, $0
VSHUF4IW $0, V2, V3
VSHUF4IW $255, V2, V3
VSHUF4IW $127, V2, V3
VSHUF4IW $127, V2
RET
TEXT ·VSLLBbounds(SB), NOSPLIT, $0
VSLLB $0, V2, V3
VSLLB $7, V2, V3
VSLLB $3, V2, V3
VSLLB $3, V2
RET
TEXT ·VSLLHbounds(SB), NOSPLIT, $0
VSLLH $0, V2, V3
VSLLH $15, V2, V3
VSLLH $7, V2, V3
VSLLH $7, V2
RET
TEXT ·VSLLVbounds(SB), NOSPLIT, $0
VSLLV $0, V2, V3
VSLLV $63, V2, V3
VSLLV $31, V2, V3
VSLLV $31, V2
RET
TEXT ·VSLLWbounds(SB), NOSPLIT, $0
VSLLW $0, V2, V3
VSLLW $31, V2, V3
VSLLW $15, V2, V3
VSLLW $15, V2
RET
TEXT ·VSRABbounds(SB), NOSPLIT, $0
VSRAB $0, V2, V3
VSRAB $7, V2, V3
VSRAB $3, V2, V3
VSRAB $3, V2
RET
TEXT ·VSRAHbounds(SB), NOSPLIT, $0
VSRAH $0, V2, V3
VSRAH $15, V2, V3
VSRAH $7, V2, V3
VSRAH $7, V2
RET
TEXT ·VSRAVbounds(SB), NOSPLIT, $0
VSRAV $0, V2, V3
VSRAV $63, V2, V3
VSRAV $31, V2, V3
VSRAV $31, V2
RET
TEXT ·VSRAWbounds(SB), NOSPLIT, $0
VSRAW $0, V2, V3
VSRAW $31, V2, V3
VSRAW $15, V2, V3
VSRAW $15, V2
RET
TEXT ·VSRLBbounds(SB), NOSPLIT, $0
VSRLB $0, V2, V3
VSRLB $7, V2, V3
VSRLB $3, V2, V3
VSRLB $3, V2
RET
TEXT ·VSRLHbounds(SB), NOSPLIT, $0
VSRLH $0, V2, V3
VSRLH $15, V2, V3
VSRLH $7, V2, V3
VSRLH $7, V2
RET
TEXT ·VSRLVbounds(SB), NOSPLIT, $0
VSRLV $0, V2, V3
VSRLV $63, V2, V3
VSRLV $31, V2, V3
VSRLV $31, V2
RET
TEXT ·VSRLWbounds(SB), NOSPLIT, $0
VSRLW $0, V2, V3
VSRLW $31, V2, V3
VSRLW $15, V2, V3
VSRLW $15, V2
RET
TEXT ·VXORBbounds(SB), NOSPLIT, $0
VXORB $0, V2, V3
VXORB $255, V2, V3
VXORB $127, V2, V3
VXORB $127, V2
RET
+148
View File
@@ -0,0 +1,148 @@
// Differential kernel: the divmod vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VDIVB(SB), NOSPLIT, $0
VDIVB V1, V2, V3
RET
TEXT ·VDIVH(SB), NOSPLIT, $0
VDIVH V1, V2, V3
RET
TEXT ·VDIVW(SB), NOSPLIT, $0
VDIVW V1, V2, V3
RET
TEXT ·VDIVV(SB), NOSPLIT, $0
VDIVV V1, V2, V3
RET
TEXT ·VDIVBU(SB), NOSPLIT, $0
VDIVBU V1, V2, V3
RET
TEXT ·VDIVHU(SB), NOSPLIT, $0
VDIVHU V1, V2, V3
RET
TEXT ·VDIVWU(SB), NOSPLIT, $0
VDIVWU V1, V2, V3
RET
TEXT ·VDIVVU(SB), NOSPLIT, $0
VDIVVU V1, V2, V3
RET
TEXT ·VMODB(SB), NOSPLIT, $0
VMODB V1, V2, V3
RET
TEXT ·VMODH(SB), NOSPLIT, $0
VMODH V1, V2, V3
RET
TEXT ·VMODW(SB), NOSPLIT, $0
VMODW V1, V2, V3
RET
TEXT ·VMODV(SB), NOSPLIT, $0
VMODV V1, V2, V3
RET
TEXT ·VMODBU(SB), NOSPLIT, $0
VMODBU V1, V2, V3
RET
TEXT ·VMODHU(SB), NOSPLIT, $0
VMODHU V1, V2, V3
RET
TEXT ·VMODWU(SB), NOSPLIT, $0
VMODWU V1, V2, V3
RET
TEXT ·VMODVU(SB), NOSPLIT, $0
VMODVU V1, V2, V3
RET
TEXT ·XVDIVB(SB), NOSPLIT, $0
XVDIVB X3, X2, X1
RET
TEXT ·XVDIVH(SB), NOSPLIT, $0
XVDIVH X3, X2, X1
RET
TEXT ·XVDIVW(SB), NOSPLIT, $0
XVDIVW X3, X2, X1
RET
TEXT ·XVDIVV(SB), NOSPLIT, $0
XVDIVV X3, X2, X1
RET
TEXT ·XVDIVBU(SB), NOSPLIT, $0
XVDIVBU X3, X2, X1
RET
TEXT ·XVDIVHU(SB), NOSPLIT, $0
XVDIVHU X3, X2, X1
RET
TEXT ·XVDIVWU(SB), NOSPLIT, $0
XVDIVWU X3, X2, X1
RET
TEXT ·XVDIVVU(SB), NOSPLIT, $0
XVDIVVU X3, X2, X1
RET
TEXT ·XVMODB(SB), NOSPLIT, $0
XVMODB X3, X2, X1
RET
TEXT ·XVMODH(SB), NOSPLIT, $0
XVMODH X3, X2, X1
RET
TEXT ·XVMODW(SB), NOSPLIT, $0
XVMODW X3, X2, X1
RET
TEXT ·XVMODV(SB), NOSPLIT, $0
XVMODV X3, X2, X1
RET
TEXT ·XVMODBU(SB), NOSPLIT, $0
XVMODBU X3, X2, X1
RET
TEXT ·XVMODHU(SB), NOSPLIT, $0
XVMODHU X3, X2, X1
RET
TEXT ·XVMODWU(SB), NOSPLIT, $0
XVMODWU X3, X2, X1
RET
TEXT ·XVMODVU(SB), NOSPLIT, $0
XVMODVU X3, X2, X1
RET
TEXT ·VDIVF(SB), NOSPLIT, $0
VDIVF V1, V2, V3
RET
TEXT ·VDIVD(SB), NOSPLIT, $0
VDIVD V1, V2, V3
RET
TEXT ·XVDIVF(SB), NOSPLIT, $0
XVDIVF X1, X2, X3
RET
TEXT ·XVDIVD(SB), NOSPLIT, $0
XVDIVD X1, X2, X3
RET
+176
View File
@@ -0,0 +1,176 @@
// Differential kernel: the fp vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·FFINTFW(SB), NOSPLIT, $0
FFINTFW F0, F1
RET
TEXT ·FFINTFV(SB), NOSPLIT, $0
FFINTFV F0, F1
RET
TEXT ·FFINTDW(SB), NOSPLIT, $0
FFINTDW F0, F1
RET
TEXT ·FTINTWF(SB), NOSPLIT, $0
FTINTWF F0, F1
RET
TEXT ·FTINTWD(SB), NOSPLIT, $0
FTINTWD F0, F1
RET
TEXT ·FTINTVF(SB), NOSPLIT, $0
FTINTVF F0, F1
RET
TEXT ·FTINTVD(SB), NOSPLIT, $0
FTINTVD F0, F1
RET
TEXT ·VFSQRTF(SB), NOSPLIT, $0
VFSQRTF V1, V2
RET
TEXT ·VFSQRTD(SB), NOSPLIT, $0
VFSQRTD V1, V2
RET
TEXT ·VFRECIPF(SB), NOSPLIT, $0
VFRECIPF V1, V2
RET
TEXT ·VFRECIPD(SB), NOSPLIT, $0
VFRECIPD V1, V2
RET
TEXT ·VFRSQRTF(SB), NOSPLIT, $0
VFRSQRTF V1, V2
RET
TEXT ·VFRSQRTD(SB), NOSPLIT, $0
VFRSQRTD V1, V2
RET
TEXT ·XVFSQRTF(SB), NOSPLIT, $0
XVFSQRTF X2, X1
RET
TEXT ·XVFSQRTD(SB), NOSPLIT, $0
XVFSQRTD X2, X1
RET
TEXT ·XVFRECIPF(SB), NOSPLIT, $0
XVFRECIPF X2, X1
RET
TEXT ·XVFRECIPD(SB), NOSPLIT, $0
XVFRECIPD X2, X1
RET
TEXT ·XVFRSQRTF(SB), NOSPLIT, $0
XVFRSQRTF X2, X1
RET
TEXT ·XVFRSQRTD(SB), NOSPLIT, $0
XVFRSQRTD X2, X1
RET
TEXT ·VFRINTRNEF(SB), NOSPLIT, $0
VFRINTRNEF V1, V2
RET
TEXT ·VFRINTRNED(SB), NOSPLIT, $0
VFRINTRNED V1, V2
RET
TEXT ·VFRINTRZF(SB), NOSPLIT, $0
VFRINTRZF V1, V2
RET
TEXT ·VFRINTRZD(SB), NOSPLIT, $0
VFRINTRZD V1, V2
RET
TEXT ·VFRINTRPF(SB), NOSPLIT, $0
VFRINTRPF V1, V2
RET
TEXT ·VFRINTRPD(SB), NOSPLIT, $0
VFRINTRPD V1, V2
RET
TEXT ·VFRINTRMF(SB), NOSPLIT, $0
VFRINTRMF V1, V2
RET
TEXT ·VFRINTRMD(SB), NOSPLIT, $0
VFRINTRMD V1, V2
RET
TEXT ·VFRINTF(SB), NOSPLIT, $0
VFRINTF V1, V2
RET
TEXT ·VFRINTD(SB), NOSPLIT, $0
VFRINTD V1, V2
RET
TEXT ·XVFRINTRNEF(SB), NOSPLIT, $0
XVFRINTRNEF X1, X2
RET
TEXT ·XVFRINTRNED(SB), NOSPLIT, $0
XVFRINTRNED X1, X2
RET
TEXT ·XVFRINTRZF(SB), NOSPLIT, $0
XVFRINTRZF X1, X2
RET
TEXT ·XVFRINTRZD(SB), NOSPLIT, $0
XVFRINTRZD X1, X2
RET
TEXT ·XVFRINTRPF(SB), NOSPLIT, $0
XVFRINTRPF X1, X2
RET
TEXT ·XVFRINTRPD(SB), NOSPLIT, $0
XVFRINTRPD X1, X2
RET
TEXT ·XVFRINTRMF(SB), NOSPLIT, $0
XVFRINTRMF X1, X2
RET
TEXT ·XVFRINTRMD(SB), NOSPLIT, $0
XVFRINTRMD X1, X2
RET
TEXT ·XVFRINTF(SB), NOSPLIT, $0
XVFRINTF X1, X2
RET
TEXT ·XVFRINTD(SB), NOSPLIT, $0
XVFRINTD X1, X2
RET
TEXT ·VFCLASSF(SB), NOSPLIT, $0
VFCLASSF V1, V2
RET
TEXT ·VFCLASSD(SB), NOSPLIT, $0
VFCLASSD V1, V2
RET
TEXT ·XVFCLASSF(SB), NOSPLIT, $0
XVFCLASSF X1, X2
RET
TEXT ·XVFCLASSD(SB), NOSPLIT, $0
XVFCLASSD X1, X2
RET
+68
View File
@@ -0,0 +1,68 @@
// Differential kernel: the interleave vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VILVLB(SB), NOSPLIT, $0
VILVLB V1, V2, V3
RET
TEXT ·VILVLH(SB), NOSPLIT, $0
VILVLH V1, V2, V3
RET
TEXT ·VILVLW(SB), NOSPLIT, $0
VILVLW V1, V2, V3
RET
TEXT ·VILVLV(SB), NOSPLIT, $0
VILVLV V1, V2, V3
RET
TEXT ·VILVHB(SB), NOSPLIT, $0
VILVHB V1, V2, V3
RET
TEXT ·VILVHH(SB), NOSPLIT, $0
VILVHH V1, V2, V3
RET
TEXT ·VILVHW(SB), NOSPLIT, $0
VILVHW V1, V2, V3
RET
TEXT ·VILVHV(SB), NOSPLIT, $0
VILVHV V1, V2, V3
RET
TEXT ·XVILVLB(SB), NOSPLIT, $0
XVILVLB X3, X2, X1
RET
TEXT ·XVILVLH(SB), NOSPLIT, $0
XVILVLH X3, X2, X1
RET
TEXT ·XVILVLW(SB), NOSPLIT, $0
XVILVLW X3, X2, X1
RET
TEXT ·XVILVLV(SB), NOSPLIT, $0
XVILVLV X3, X2, X1
RET
TEXT ·XVILVHB(SB), NOSPLIT, $0
XVILVHB X3, X2, X1
RET
TEXT ·XVILVHH(SB), NOSPLIT, $0
XVILVHH X3, X2, X1
RET
TEXT ·XVILVHW(SB), NOSPLIT, $0
XVILVHW X3, X2, X1
RET
TEXT ·XVILVHV(SB), NOSPLIT, $0
XVILVHV X3, X2, X1
RET
+174
View File
@@ -0,0 +1,174 @@
// Differential kernel: the logic vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VSEQH(SB), NOSPLIT, $0
VSEQH V1, V2, V3
VSEQH $1, V2, V3
RET
TEXT ·VSEQW(SB), NOSPLIT, $0
VSEQW V1, V2, V3
VSEQW $8, V2, V3
RET
TEXT ·XVSEQH(SB), NOSPLIT, $0
XVSEQH X3, X2, X4
XVSEQH $3, X2, X4
RET
TEXT ·XVSEQW(SB), NOSPLIT, $0
XVSEQW X3, X2, X4
XVSEQW $12, X2, X4
RET
TEXT ·VSLTB(SB), NOSPLIT, $0
VSLTB V1, V2, V3
VSLTB $1, V2, V3
RET
TEXT ·VSLTH(SB), NOSPLIT, $0
VSLTH V1, V2, V3
VSLTH $-16, V2, V3
RET
TEXT ·VSLTW(SB), NOSPLIT, $0
VSLTW V1, V2, V3
VSLTW $-16, V2, V3
RET
TEXT ·VSLTV(SB), NOSPLIT, $0
VSLTV V1, V2, V3
VSLTV $-15, V2, V3
RET
TEXT ·XVSLTB(SB), NOSPLIT, $0
XVSLTB X1, X2, X3
XVSLTB $1, X2, X3
RET
TEXT ·XVSLTH(SB), NOSPLIT, $0
XVSLTH X1, X2, X3
XVSLTH $-16, X2, X3
RET
TEXT ·XVSLTW(SB), NOSPLIT, $0
XVSLTW X1, X2, X3
XVSLTW $-16, X2, X3
RET
TEXT ·XVSLTV(SB), NOSPLIT, $0
XVSLTV X1, X2, X3
XVSLTV $-16, X2, X3
RET
TEXT ·VSLTBU(SB), NOSPLIT, $0
VSLTBU V1, V2, V3
VSLTBU $0, V2, V3
RET
TEXT ·VSLTHU(SB), NOSPLIT, $0
VSLTHU V1, V2, V3
VSLTHU $31, V2, V3
RET
TEXT ·VSLTWU(SB), NOSPLIT, $0
VSLTWU V1, V2, V3
VSLTWU $16, V2, V3
RET
TEXT ·VSLTVU(SB), NOSPLIT, $0
VSLTVU V1, V2, V3
VSLTVU $1, V2, V3
RET
TEXT ·XVSLTBU(SB), NOSPLIT, $0
XVSLTBU X1, X2, X3
XVSLTBU $0, X2, X3
RET
TEXT ·XVSLTHU(SB), NOSPLIT, $0
XVSLTHU X1, X2, X3
XVSLTHU $31, X2, X3
RET
TEXT ·XVSLTWU(SB), NOSPLIT, $0
XVSLTWU X1, X2, X3
XVSLTWU $8, X2, X3
RET
TEXT ·XVSLTVU(SB), NOSPLIT, $0
XVSLTVU X1, X2, X3
XVSLTVU $0, X2, X3
RET
TEXT ·VORV(SB), NOSPLIT, $0
VORV V1, V2, V3
VORV V1, V2
RET
TEXT ·VNORV(SB), NOSPLIT, $0
VNORV V1, V2, V3
VNORV V1, V2
RET
TEXT ·VANDNV(SB), NOSPLIT, $0
VANDNV V1, V2, V3
VANDNV V1, V2
RET
TEXT ·VORNV(SB), NOSPLIT, $0
VORNV V1, V2, V3
VORNV V1, V2
RET
TEXT ·VORB(SB), NOSPLIT, $0
VORB $64, V2, V3
VORB $64, V2
RET
TEXT ·VXORB(SB), NOSPLIT, $0
VXORB $128, V2, V3
VXORB $128, V2
RET
TEXT ·VNORB(SB), NOSPLIT, $0
VNORB $255, V2, V3
VNORB $255, V2
RET
TEXT ·XVORV(SB), NOSPLIT, $0
XVORV X1, X2, X3
XVORV X1, X2
RET
TEXT ·XVNORV(SB), NOSPLIT, $0
XVNORV X1, X2, X3
XVNORV X1, X2
RET
TEXT ·XVANDNV(SB), NOSPLIT, $0
XVANDNV X1, X2, X3
XVANDNV X1, X2
RET
TEXT ·XVORNV(SB), NOSPLIT, $0
XVORNV X1, X2, X3
XVORNV X1, X2
RET
TEXT ·XVORB(SB), NOSPLIT, $0
XVORB $1, X2, X3
XVORB $1, X2
RET
TEXT ·XVXORB(SB), NOSPLIT, $0
XVXORB $127, X2, X3
XVXORB $127, X2
RET
TEXT ·XVNORB(SB), NOSPLIT, $0
XVNORB $255, X2, X3
XVNORB $255, X2
RET
+28
View File
@@ -0,0 +1,28 @@
// Differential kernel: the madd vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VMADDB(SB), NOSPLIT, $0
VMADDB V1, V2, V3
RET
TEXT ·VMADDH(SB), NOSPLIT, $0
VMADDH V1, V2, V3
RET
TEXT ·VMADDV(SB), NOSPLIT, $0
VMADDV V1, V2, V3
RET
TEXT ·XVMADDB(SB), NOSPLIT, $0
XVMADDB X1, X2, X3
RET
TEXT ·XVMADDH(SB), NOSPLIT, $0
XVMADDH X1, X2, X3
RET
TEXT ·XVMADDV(SB), NOSPLIT, $0
XVMADDV X1, X2, X3
RET
+204
View File
@@ -0,0 +1,204 @@
// Differential kernel: the maddwiden vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VMADDW(SB), NOSPLIT, $0
VMADDW V1, V2, V3
RET
TEXT ·XVMADDW(SB), NOSPLIT, $0
XVMADDW X1, X2, X3
RET
TEXT ·VMADDWEVHB(SB), NOSPLIT, $0
VMADDWEVHB V1, V2, V3
RET
TEXT ·VMADDWEVWH(SB), NOSPLIT, $0
VMADDWEVWH V1, V2, V3
RET
TEXT ·VMADDWEVVW(SB), NOSPLIT, $0
VMADDWEVVW V1, V2, V3
RET
TEXT ·VMADDWEVQV(SB), NOSPLIT, $0
VMADDWEVQV V1, V2, V3
RET
TEXT ·VMADDWODHB(SB), NOSPLIT, $0
VMADDWODHB V1, V2, V3
RET
TEXT ·VMADDWODWH(SB), NOSPLIT, $0
VMADDWODWH V1, V2, V3
RET
TEXT ·VMADDWODVW(SB), NOSPLIT, $0
VMADDWODVW V1, V2, V3
RET
TEXT ·VMADDWODQV(SB), NOSPLIT, $0
VMADDWODQV V1, V2, V3
RET
TEXT ·XVMADDWEVHB(SB), NOSPLIT, $0
XVMADDWEVHB X1, X2, X3
RET
TEXT ·XVMADDWEVWH(SB), NOSPLIT, $0
XVMADDWEVWH X1, X2, X3
RET
TEXT ·XVMADDWEVVW(SB), NOSPLIT, $0
XVMADDWEVVW X1, X2, X3
RET
TEXT ·XVMADDWEVQV(SB), NOSPLIT, $0
XVMADDWEVQV X1, X2, X3
RET
TEXT ·XVMADDWODHB(SB), NOSPLIT, $0
XVMADDWODHB X1, X2, X3
RET
TEXT ·XVMADDWODWH(SB), NOSPLIT, $0
XVMADDWODWH X1, X2, X3
RET
TEXT ·XVMADDWODVW(SB), NOSPLIT, $0
XVMADDWODVW X1, X2, X3
RET
TEXT ·XVMADDWODQV(SB), NOSPLIT, $0
XVMADDWODQV X1, X2, X3
RET
TEXT ·VMADDWEVHBU(SB), NOSPLIT, $0
VMADDWEVHBU V1, V2, V3
RET
TEXT ·VMADDWEVWHU(SB), NOSPLIT, $0
VMADDWEVWHU V1, V2, V3
RET
TEXT ·VMADDWEVVWU(SB), NOSPLIT, $0
VMADDWEVVWU V1, V2, V3
RET
TEXT ·VMADDWEVQVU(SB), NOSPLIT, $0
VMADDWEVQVU V1, V2, V3
RET
TEXT ·VMADDWODHBU(SB), NOSPLIT, $0
VMADDWODHBU V1, V2, V3
RET
TEXT ·VMADDWODWHU(SB), NOSPLIT, $0
VMADDWODWHU V1, V2, V3
RET
TEXT ·VMADDWODVWU(SB), NOSPLIT, $0
VMADDWODVWU V1, V2, V3
RET
TEXT ·VMADDWODQVU(SB), NOSPLIT, $0
VMADDWODQVU V1, V2, V3
RET
TEXT ·XVMADDWEVHBU(SB), NOSPLIT, $0
XVMADDWEVHBU X1, X2, X3
RET
TEXT ·XVMADDWEVWHU(SB), NOSPLIT, $0
XVMADDWEVWHU X1, X2, X3
RET
TEXT ·XVMADDWEVVWU(SB), NOSPLIT, $0
XVMADDWEVVWU X1, X2, X3
RET
TEXT ·XVMADDWEVQVU(SB), NOSPLIT, $0
XVMADDWEVQVU X1, X2, X3
RET
TEXT ·XVMADDWODHBU(SB), NOSPLIT, $0
XVMADDWODHBU X1, X2, X3
RET
TEXT ·XVMADDWODWHU(SB), NOSPLIT, $0
XVMADDWODWHU X1, X2, X3
RET
TEXT ·XVMADDWODVWU(SB), NOSPLIT, $0
XVMADDWODVWU X1, X2, X3
RET
TEXT ·XVMADDWODQVU(SB), NOSPLIT, $0
XVMADDWODQVU X1, X2, X3
RET
TEXT ·VMADDWEVHBUB(SB), NOSPLIT, $0
VMADDWEVHBUB V1, V2, V3
RET
TEXT ·VMADDWEVWHUH(SB), NOSPLIT, $0
VMADDWEVWHUH V1, V2, V3
RET
TEXT ·VMADDWEVVWUW(SB), NOSPLIT, $0
VMADDWEVVWUW V1, V2, V3
RET
TEXT ·VMADDWEVQVUV(SB), NOSPLIT, $0
VMADDWEVQVUV V1, V2, V3
RET
TEXT ·VMADDWODHBUB(SB), NOSPLIT, $0
VMADDWODHBUB V1, V2, V3
RET
TEXT ·VMADDWODWHUH(SB), NOSPLIT, $0
VMADDWODWHUH V1, V2, V3
RET
TEXT ·VMADDWODVWUW(SB), NOSPLIT, $0
VMADDWODVWUW V1, V2, V3
RET
TEXT ·VMADDWODQVUV(SB), NOSPLIT, $0
VMADDWODQVUV V1, V2, V3
RET
TEXT ·XVMADDWEVHBUB(SB), NOSPLIT, $0
XVMADDWEVHBUB X1, X2, X3
RET
TEXT ·XVMADDWEVWHUH(SB), NOSPLIT, $0
XVMADDWEVWHUH X1, X2, X3
RET
TEXT ·XVMADDWEVVWUW(SB), NOSPLIT, $0
XVMADDWEVVWUW X1, X2, X3
RET
TEXT ·XVMADDWEVQVUV(SB), NOSPLIT, $0
XVMADDWEVQVUV X1, X2, X3
RET
TEXT ·XVMADDWODHBUB(SB), NOSPLIT, $0
XVMADDWODHBUB X1, X2, X3
RET
TEXT ·XVMADDWODWHUH(SB), NOSPLIT, $0
XVMADDWODWHUH X1, X2, X3
RET
TEXT ·XVMADDWODVWUW(SB), NOSPLIT, $0
XVMADDWODVWUW X1, X2, X3
RET
TEXT ·XVMADDWODQVUV(SB), NOSPLIT, $0
XVMADDWODQVUV X1, X2, X3
RET
+36
View File
@@ -0,0 +1,36 @@
// Differential kernel: the msub vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VMSUBB(SB), NOSPLIT, $0
VMSUBB V1, V2, V3
RET
TEXT ·VMSUBH(SB), NOSPLIT, $0
VMSUBH V1, V2, V3
RET
TEXT ·VMSUBW(SB), NOSPLIT, $0
VMSUBW V1, V2, V3
RET
TEXT ·VMSUBV(SB), NOSPLIT, $0
VMSUBV V1, V2, V3
RET
TEXT ·XVMSUBB(SB), NOSPLIT, $0
XVMSUBB X1, X2, X3
RET
TEXT ·XVMSUBH(SB), NOSPLIT, $0
XVMSUBH X1, X2, X3
RET
TEXT ·XVMSUBW(SB), NOSPLIT, $0
XVMSUBW X1, X2, X3
RET
TEXT ·XVMSUBV(SB), NOSPLIT, $0
XVMSUBV X1, X2, X3
RET
+108
View File
@@ -0,0 +1,108 @@
// Differential kernel: the mul vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VMULB(SB), NOSPLIT, $0
VMULB V1, V2, V3
RET
TEXT ·VMULH(SB), NOSPLIT, $0
VMULH V1, V2, V3
RET
TEXT ·VMULV(SB), NOSPLIT, $0
VMULV V1, V2, V3
RET
TEXT ·VMUHB(SB), NOSPLIT, $0
VMUHB V1, V2, V3
RET
TEXT ·VMUHH(SB), NOSPLIT, $0
VMUHH V1, V2, V3
RET
TEXT ·VMUHW(SB), NOSPLIT, $0
VMUHW V1, V2, V3
RET
TEXT ·VMUHV(SB), NOSPLIT, $0
VMUHV V1, V2, V3
RET
TEXT ·VMUHBU(SB), NOSPLIT, $0
VMUHBU V1, V2, V3
RET
TEXT ·VMUHHU(SB), NOSPLIT, $0
VMUHHU V1, V2, V3
RET
TEXT ·VMUHWU(SB), NOSPLIT, $0
VMUHWU V1, V2, V3
RET
TEXT ·VMUHVU(SB), NOSPLIT, $0
VMUHVU V1, V2, V3
RET
TEXT ·XVMULB(SB), NOSPLIT, $0
XVMULB X3, X2, X1
RET
TEXT ·XVMULH(SB), NOSPLIT, $0
XVMULH X3, X2, X1
RET
TEXT ·XVMULV(SB), NOSPLIT, $0
XVMULV X3, X2, X1
RET
TEXT ·XVMUHB(SB), NOSPLIT, $0
XVMUHB X3, X2, X1
RET
TEXT ·XVMUHH(SB), NOSPLIT, $0
XVMUHH X3, X2, X1
RET
TEXT ·XVMUHW(SB), NOSPLIT, $0
XVMUHW X3, X2, X1
RET
TEXT ·XVMUHV(SB), NOSPLIT, $0
XVMUHV X3, X2, X1
RET
TEXT ·XVMUHBU(SB), NOSPLIT, $0
XVMUHBU X3, X2, X1
RET
TEXT ·XVMUHHU(SB), NOSPLIT, $0
XVMUHHU X3, X2, X1
RET
TEXT ·XVMUHWU(SB), NOSPLIT, $0
XVMUHWU X3, X2, X1
RET
TEXT ·XVMUHVU(SB), NOSPLIT, $0
XVMUHVU X3, X2, X1
RET
TEXT ·VMULF(SB), NOSPLIT, $0
VMULF V1, V2, V3
RET
TEXT ·VMULD(SB), NOSPLIT, $0
VMULD V1, V2, V3
RET
TEXT ·XVMULF(SB), NOSPLIT, $0
XVMULF X1, X2, X3
RET
TEXT ·XVMULD(SB), NOSPLIT, $0
XVMULD X1, X2, X3
RET
+204
View File
@@ -0,0 +1,204 @@
// Differential kernel: the mulwiden vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VMULW(SB), NOSPLIT, $0
VMULW V1, V2, V3
RET
TEXT ·XVMULW(SB), NOSPLIT, $0
XVMULW X3, X2, X1
RET
TEXT ·VMULWEVHB(SB), NOSPLIT, $0
VMULWEVHB V1, V2, V3
RET
TEXT ·VMULWEVWH(SB), NOSPLIT, $0
VMULWEVWH V1, V2, V3
RET
TEXT ·VMULWEVVW(SB), NOSPLIT, $0
VMULWEVVW V1, V2, V3
RET
TEXT ·VMULWEVQV(SB), NOSPLIT, $0
VMULWEVQV V1, V2, V3
RET
TEXT ·VMULWODHB(SB), NOSPLIT, $0
VMULWODHB V1, V2, V3
RET
TEXT ·VMULWODWH(SB), NOSPLIT, $0
VMULWODWH V1, V2, V3
RET
TEXT ·VMULWODVW(SB), NOSPLIT, $0
VMULWODVW V1, V2, V3
RET
TEXT ·VMULWODQV(SB), NOSPLIT, $0
VMULWODQV V1, V2, V3
RET
TEXT ·VMULWEVHBU(SB), NOSPLIT, $0
VMULWEVHBU V1, V2, V3
RET
TEXT ·VMULWEVWHU(SB), NOSPLIT, $0
VMULWEVWHU V1, V2, V3
RET
TEXT ·VMULWEVVWU(SB), NOSPLIT, $0
VMULWEVVWU V1, V2, V3
RET
TEXT ·VMULWEVQVU(SB), NOSPLIT, $0
VMULWEVQVU V1, V2, V3
RET
TEXT ·VMULWODHBU(SB), NOSPLIT, $0
VMULWODHBU V1, V2, V3
RET
TEXT ·VMULWODWHU(SB), NOSPLIT, $0
VMULWODWHU V1, V2, V3
RET
TEXT ·VMULWODVWU(SB), NOSPLIT, $0
VMULWODVWU V1, V2, V3
RET
TEXT ·VMULWODQVU(SB), NOSPLIT, $0
VMULWODQVU V1, V2, V3
RET
TEXT ·XVMULWEVHB(SB), NOSPLIT, $0
XVMULWEVHB X1, X2, X3
RET
TEXT ·XVMULWEVWH(SB), NOSPLIT, $0
XVMULWEVWH X1, X2, X3
RET
TEXT ·XVMULWEVVW(SB), NOSPLIT, $0
XVMULWEVVW X1, X2, X3
RET
TEXT ·XVMULWEVQV(SB), NOSPLIT, $0
XVMULWEVQV X1, X2, X3
RET
TEXT ·XVMULWODHB(SB), NOSPLIT, $0
XVMULWODHB X1, X2, X3
RET
TEXT ·XVMULWODWH(SB), NOSPLIT, $0
XVMULWODWH X1, X2, X3
RET
TEXT ·XVMULWODVW(SB), NOSPLIT, $0
XVMULWODVW X1, X2, X3
RET
TEXT ·XVMULWODQV(SB), NOSPLIT, $0
XVMULWODQV X1, X2, X3
RET
TEXT ·XVMULWEVHBU(SB), NOSPLIT, $0
XVMULWEVHBU X1, X2, X3
RET
TEXT ·XVMULWEVWHU(SB), NOSPLIT, $0
XVMULWEVWHU X1, X2, X3
RET
TEXT ·XVMULWEVVWU(SB), NOSPLIT, $0
XVMULWEVVWU X1, X2, X3
RET
TEXT ·XVMULWEVQVU(SB), NOSPLIT, $0
XVMULWEVQVU X1, X2, X3
RET
TEXT ·XVMULWODHBU(SB), NOSPLIT, $0
XVMULWODHBU X1, X2, X3
RET
TEXT ·XVMULWODWHU(SB), NOSPLIT, $0
XVMULWODWHU X1, X2, X3
RET
TEXT ·XVMULWODVWU(SB), NOSPLIT, $0
XVMULWODVWU X1, X2, X3
RET
TEXT ·XVMULWODQVU(SB), NOSPLIT, $0
XVMULWODQVU X1, X2, X3
RET
TEXT ·VMULWEVHBUB(SB), NOSPLIT, $0
VMULWEVHBUB V1, V2, V3
RET
TEXT ·VMULWEVWHUH(SB), NOSPLIT, $0
VMULWEVWHUH V1, V2, V3
RET
TEXT ·VMULWEVVWUW(SB), NOSPLIT, $0
VMULWEVVWUW V1, V2, V3
RET
TEXT ·VMULWEVQVUV(SB), NOSPLIT, $0
VMULWEVQVUV V1, V2, V3
RET
TEXT ·VMULWODHBUB(SB), NOSPLIT, $0
VMULWODHBUB V1, V2, V3
RET
TEXT ·VMULWODWHUH(SB), NOSPLIT, $0
VMULWODWHUH V1, V2, V3
RET
TEXT ·VMULWODVWUW(SB), NOSPLIT, $0
VMULWODVWUW V1, V2, V3
RET
TEXT ·VMULWODQVUV(SB), NOSPLIT, $0
VMULWODQVUV V1, V2, V3
RET
TEXT ·XVMULWEVHBUB(SB), NOSPLIT, $0
XVMULWEVHBUB X1, X2, X3
RET
TEXT ·XVMULWEVWHUH(SB), NOSPLIT, $0
XVMULWEVWHUH X1, X2, X3
RET
TEXT ·XVMULWEVVWUW(SB), NOSPLIT, $0
XVMULWEVVWUW X1, X2, X3
RET
TEXT ·XVMULWEVQVUV(SB), NOSPLIT, $0
XVMULWEVQVUV X1, X2, X3
RET
TEXT ·XVMULWODHBUB(SB), NOSPLIT, $0
XVMULWODHBUB X1, X2, X3
RET
TEXT ·XVMULWODWHUH(SB), NOSPLIT, $0
XVMULWODWHUH X1, X2, X3
RET
TEXT ·XVMULWODVWUW(SB), NOSPLIT, $0
XVMULWODVWUW X1, X2, X3
RET
TEXT ·XVMULWODQVUV(SB), NOSPLIT, $0
XVMULWODQVUV X1, X2, X3
RET
+214
View File
@@ -0,0 +1,214 @@
// Differential kernel: the shift vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VSLLB(SB), NOSPLIT, $0
VSLLB V1, V2, V3
VSLLB $0, V1, V2
VSLLB $7, V1, V2
VSLLB $5, V1
RET
TEXT ·VSLLH(SB), NOSPLIT, $0
VSLLH V1, V2, V3
VSLLH $0, V1, V2
VSLLH $15, V1, V2
VSLLH $10, V1
RET
TEXT ·VSLLW(SB), NOSPLIT, $0
VSLLW V1, V2, V3
VSLLW $0, V1, V2
VSLLW $31, V1, V2
VSLLW $11, V1
RET
TEXT ·VSLLV(SB), NOSPLIT, $0
VSLLV V1, V2, V3
VSLLV $0, V1, V2
VSLLV $63, V1, V2
VSLLV $30, V1
RET
TEXT ·VSRLB(SB), NOSPLIT, $0
VSRLB V1, V2, V3
VSRLB $0, V1, V2
VSRLB $7, V1, V2
VSRLB $4, V1
RET
TEXT ·VSRLH(SB), NOSPLIT, $0
VSRLH V1, V2, V3
VSRLH $0, V1, V2
VSRLH $15, V1, V2
VSRLH $9, V1
RET
TEXT ·VSRLW(SB), NOSPLIT, $0
VSRLW V1, V2, V3
VSRLW $0, V1, V2
VSRLW $31, V1, V2
VSRLW $16, V1
RET
TEXT ·VSRLV(SB), NOSPLIT, $0
VSRLV V1, V2, V3
VSRLV $0, V1, V2
VSRLV $63, V1, V2
VSRLV $40, V1
RET
TEXT ·VSRAH(SB), NOSPLIT, $0
VSRAH V1, V2, V3
VSRAH $0, V1, V2
VSRAH $15, V1, V2
VSRAH $8, V1
RET
TEXT ·VSRAW(SB), NOSPLIT, $0
VSRAW V1, V2, V3
VSRAW $0, V1, V2
VSRAW $31, V1, V2
VSRAW $12, V1
RET
TEXT ·VSRAV(SB), NOSPLIT, $0
VSRAV V1, V2, V3
VSRAV $0, V1, V2
VSRAV $63, V1, V2
VSRAV $50, V1
RET
TEXT ·VROTRB(SB), NOSPLIT, $0
VROTRB V1, V2, V3
VROTRB $0, V1, V2
VROTRB $7, V1, V2
VROTRB $3, V1
RET
TEXT ·VROTRH(SB), NOSPLIT, $0
VROTRH V1, V2, V3
VROTRH $0, V1, V2
VROTRH $15, V1, V2
VROTRH $5, V1
RET
TEXT ·VROTRV(SB), NOSPLIT, $0
VROTRV V1, V2, V3
VROTRV $0, V1, V2
VROTRV $63, V1, V2
VROTRV $52, V1
RET
TEXT ·XVSLLB(SB), NOSPLIT, $0
XVSLLB X3, X2, X1
XVSLLB $0, X2, X1
XVSLLB $7, X2, X1
XVSLLB $4, X2
RET
TEXT ·XVSLLH(SB), NOSPLIT, $0
XVSLLH X3, X2, X1
XVSLLH $0, X2, X1
XVSLLH $15, X2, X1
XVSLLH $8, X2
RET
TEXT ·XVSLLW(SB), NOSPLIT, $0
XVSLLW X3, X2, X1
XVSLLW $0, X2, X1
XVSLLW $31, X2, X1
XVSLLW $13, X2
RET
TEXT ·XVSLLV(SB), NOSPLIT, $0
XVSLLV X3, X2, X1
XVSLLV $0, X2, X1
XVSLLV $63, X2, X1
XVSLLV $36, X2
RET
TEXT ·XVSRLB(SB), NOSPLIT, $0
XVSRLB X3, X2, X1
XVSRLB $0, X2, X1
XVSRLB $7, X2, X1
XVSRLB $5, X2
RET
TEXT ·XVSRLH(SB), NOSPLIT, $0
XVSRLH X3, X2, X1
XVSRLH $0, X2, X1
XVSRLH $15, X2, X1
XVSRLH $9, X2
RET
TEXT ·XVSRLW(SB), NOSPLIT, $0
XVSRLW X3, X2, X1
XVSRLW $0, X2, X1
XVSRLW $31, X2, X1
XVSRLW $14, X2
RET
TEXT ·XVSRLV(SB), NOSPLIT, $0
XVSRLV X3, X2, X1
XVSRLV $0, X2, X1
XVSRLV $63, X2, X1
XVSRLV $45, X2
RET
TEXT ·XVSRAB(SB), NOSPLIT, $0
XVSRAB X3, X2, X1
XVSRAB $0, X2, X1
XVSRAB $7, X2, X1
XVSRAB $6, X2
RET
TEXT ·XVSRAH(SB), NOSPLIT, $0
XVSRAH X3, X2, X1
XVSRAH $0, X2, X1
XVSRAH $15, X2, X1
XVSRAH $10, X2
RET
TEXT ·XVSRAW(SB), NOSPLIT, $0
XVSRAW X3, X2, X1
XVSRAW $0, X2, X1
XVSRAW $31, X2, X1
XVSRAW $16, X2
RET
TEXT ·XVSRAV(SB), NOSPLIT, $0
XVSRAV X3, X2, X1
XVSRAV $0, X2, X1
XVSRAV $63, X2, X1
XVSRAV $48, X2
RET
TEXT ·XVROTRB(SB), NOSPLIT, $0
XVROTRB X3, X2, X1
XVROTRB $0, X2, X1
XVROTRB $7, X2, X1
XVROTRB $3, X2
RET
TEXT ·XVROTRH(SB), NOSPLIT, $0
XVROTRH X3, X2, X1
XVROTRH $0, X2, X1
XVROTRH $15, X2, X1
XVROTRH $13, X2
RET
TEXT ·XVROTRW(SB), NOSPLIT, $0
XVROTRW X3, X2, X1
XVROTRW $0, X2, X1
XVROTRW $31, X2, X1
XVROTRW $24, X2
RET
TEXT ·XVROTRV(SB), NOSPLIT, $0
XVROTRV X3, X2, X1
XVROTRV $0, X2, X1
XVROTRV $63, X2, X1
XVROTRV $52, X2
RET
+132
View File
@@ -0,0 +1,132 @@
// Differential kernel: the shuffle vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VSHUF4IB(SB), NOSPLIT, $0
VSHUF4IB $0, V2, V1
VSHUF4IB $16, V2, V1
VSHUF4IB $255, V2, V1
RET
TEXT ·VSHUF4IH(SB), NOSPLIT, $0
VSHUF4IH $0, V2, V1
VSHUF4IH $128, V2, V1
VSHUF4IH $255, V2, V1
RET
TEXT ·VSHUF4IW(SB), NOSPLIT, $0
VSHUF4IW $0, V2, V1
VSHUF4IW $96, V2, V1
VSHUF4IW $255, V2, V1
RET
TEXT ·VSHUF4IV(SB), NOSPLIT, $0
VSHUF4IV $0, V2, V1
VSHUF4IV $8, V2, V1
VSHUF4IV $15, V2, V1
RET
TEXT ·XVSHUF4IB(SB), NOSPLIT, $0
XVSHUF4IB $0, X1, X2
XVSHUF4IB $16, X1, X2
XVSHUF4IB $255, X1, X2
RET
TEXT ·XVSHUF4IH(SB), NOSPLIT, $0
XVSHUF4IH $0, X1, X2
XVSHUF4IH $128, X1, X2
XVSHUF4IH $255, X1, X2
RET
TEXT ·XVSHUF4IW(SB), NOSPLIT, $0
XVSHUF4IW $0, X1, X2
XVSHUF4IW $96, X1, X2
XVSHUF4IW $255, X1, X2
RET
TEXT ·XVSHUF4IV(SB), NOSPLIT, $0
XVSHUF4IV $0, X1, X2
XVSHUF4IV $8, X1, X2
XVSHUF4IV $15, X1, X2
RET
TEXT ·VSHUFH(SB), NOSPLIT, $0
VSHUFH V1, V2, V3
RET
TEXT ·VSHUFW(SB), NOSPLIT, $0
VSHUFW V1, V2, V3
RET
TEXT ·VSHUFV(SB), NOSPLIT, $0
VSHUFV V1, V2, V3
RET
TEXT ·XVSHUFH(SB), NOSPLIT, $0
XVSHUFH X1, X2, X3
RET
TEXT ·XVSHUFW(SB), NOSPLIT, $0
XVSHUFW X1, X2, X3
RET
TEXT ·XVSHUFV(SB), NOSPLIT, $0
XVSHUFV X1, X2, X3
RET
TEXT ·VSHUFB(SB), NOSPLIT, $0
VSHUFB V1, V2, V3, V4
RET
TEXT ·XVSHUFB(SB), NOSPLIT, $0
XVSHUFB X1, X2, X3, X4
RET
TEXT ·VPERMIW(SB), NOSPLIT, $0
VPERMIW $0x1B, V1, V2
RET
TEXT ·XVPERMIW(SB), NOSPLIT, $0
XVPERMIW $0x2B, X1, X2
RET
TEXT ·XVPERMIV(SB), NOSPLIT, $0
XVPERMIV $0x3B, X1, X2
RET
TEXT ·XVPERMIQ(SB), NOSPLIT, $0
XVPERMIQ $0x4B, X1, X2
RET
TEXT ·VEXTRINSB(SB), NOSPLIT, $0
VEXTRINSB $0x18, V1, V2
RET
TEXT ·VEXTRINSH(SB), NOSPLIT, $0
VEXTRINSH $0x27, V1, V2
RET
TEXT ·VEXTRINSW(SB), NOSPLIT, $0
VEXTRINSW $0x36, V1, V2
RET
TEXT ·VEXTRINSV(SB), NOSPLIT, $0
VEXTRINSV $0x45, V1, V2
RET
TEXT ·XVEXTRINSB(SB), NOSPLIT, $0
XVEXTRINSB $0x54, X1, X2
RET
TEXT ·XVEXTRINSH(SB), NOSPLIT, $0
XVEXTRINSH $0x63, X1, X2
RET
TEXT ·XVEXTRINSW(SB), NOSPLIT, $0
XVEXTRINSW $0x72, X1, X2
RET
TEXT ·XVEXTRINSV(SB), NOSPLIT, $0
XVEXTRINSV $0x81, X1, X2
RET
+108
View File
@@ -0,0 +1,108 @@
// Differential kernel: the unary vector slice against the Go
// toolchain's loong64enc1.s rows (gasm v. go tool asm, byte for byte).
#include "textflag.h"
TEXT ·VPCNTB(SB), NOSPLIT, $0
VPCNTB V1, V2
RET
TEXT ·VPCNTH(SB), NOSPLIT, $0
VPCNTH V1, V2
RET
TEXT ·VPCNTW(SB), NOSPLIT, $0
VPCNTW V1, V2
RET
TEXT ·XVPCNTB(SB), NOSPLIT, $0
XVPCNTB X3, X2
RET
TEXT ·XVPCNTH(SB), NOSPLIT, $0
XVPCNTH X3, X2
RET
TEXT ·XVPCNTW(SB), NOSPLIT, $0
XVPCNTW X3, X2
RET
TEXT ·VNEGB(SB), NOSPLIT, $0
VNEGB V1, V2
RET
TEXT ·VNEGH(SB), NOSPLIT, $0
VNEGH V1, V2
RET
TEXT ·VNEGW(SB), NOSPLIT, $0
VNEGW V1, V2
RET
TEXT ·VNEGV(SB), NOSPLIT, $0
VNEGV V1, V2
RET
TEXT ·XVNEGB(SB), NOSPLIT, $0
XVNEGB X2, X1
RET
TEXT ·XVNEGH(SB), NOSPLIT, $0
XVNEGH X2, X1
RET
TEXT ·XVNEGW(SB), NOSPLIT, $0
XVNEGW X2, X1
RET
TEXT ·XVNEGV(SB), NOSPLIT, $0
XVNEGV X2, X1
RET
TEXT ·VSETEQV(SB), NOSPLIT, $0
VSETEQV V1, FCC0
RET
TEXT ·XVSETEQV(SB), NOSPLIT, $0
XVSETEQV X1, FCC0
RET
TEXT ·VSETANYEQH(SB), NOSPLIT, $0
VSETANYEQH V1, FCC0
RET
TEXT ·VSETANYEQW(SB), NOSPLIT, $0
VSETANYEQW V1, FCC0
RET
TEXT ·VSETALLNEB(SB), NOSPLIT, $0
VSETALLNEB V1, FCC0
RET
TEXT ·VSETALLNEH(SB), NOSPLIT, $0
VSETALLNEH V1, FCC0
RET
TEXT ·VSETALLNEW(SB), NOSPLIT, $0
VSETALLNEW V1, FCC0
RET
TEXT ·XVSETANYEQH(SB), NOSPLIT, $0
XVSETANYEQH X1, FCC0
RET
TEXT ·XVSETANYEQW(SB), NOSPLIT, $0
XVSETANYEQW X1, FCC0
RET
TEXT ·XVSETALLNEB(SB), NOSPLIT, $0
XVSETALLNEB X1, FCC0
RET
TEXT ·XVSETALLNEH(SB), NOSPLIT, $0
XVSETALLNEH X1, FCC0
RET
TEXT ·XVSETALLNEW(SB), NOSPLIT, $0
XVSETALLNEW X1, FCC0
RET
+19
View File
@@ -31,6 +31,25 @@ func TestGroundTruthLOONG64(t *testing.T) {
"../testdata/verify/vector_loong64.s", "../testdata/verify/vector_loong64.s",
"../testdata/verify/pcalign_loong64.s", "../testdata/verify/pcalign_loong64.s",
"../testdata/verify/l64forms_loong64.s", "../testdata/verify/l64forms_loong64.s",
"../testdata/verify/vector_arith_add_loong64.s",
"../testdata/verify/vector_arith_sub_loong64.s",
"../testdata/verify/vector_arith_sat_loong64.s",
"../testdata/verify/vector_logic_loong64.s",
"../testdata/verify/vector_madd_loong64.s",
"../testdata/verify/vector_maddwiden_loong64.s",
"../testdata/verify/vector_mul_loong64.s",
"../testdata/verify/vector_mulwiden_loong64.s",
"../testdata/verify/vector_msub_loong64.s",
"../testdata/verify/vector_divmod_loong64.s",
"../testdata/verify/vector_shift_loong64.s",
"../testdata/verify/vector_bitops_loong64.s",
"../testdata/verify/vector_interleave_loong64.s",
"../testdata/verify/vector_unary_loong64.s",
"../testdata/verify/vector_shuffle_loong64.s",
"../testdata/verify/vector_fp_loong64.s",
"../testdata/verify/vector_bounds_loong64.s",
"../testdata/verify/amdb_loong64.s",
"../testdata/verify/pseudos_loong64.s",
"trampoline_loong64.s", "trampoline_loong64.s",
} { } {
t.Run(path, func(t *testing.T) { t.Run(path, func(t *testing.T) {