blob: aef442e485d46cd1d79e525c90b98df563c8ff81 [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ppc64
import (
"bytes"
"fmt"
"internal/testenv"
"math"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"cmd/internal/obj"
"cmd/internal/objabi"
)
var platformEnvs = [][]string{
{"GOOS=aix", "GOARCH=ppc64"},
{"GOOS=linux", "GOARCH=ppc64"},
{"GOOS=linux", "GOARCH=ppc64le"},
}
const invalidPCAlignSrc = `
TEXT test(SB),0,$0-0
ADD $2, R3
PCALIGN $64
RET
`
const validPCAlignSrc = `
TEXT test(SB),0,$0-0
ADD $2, R3
PCALIGN $16
MOVD $8, R16
ADD $8, R4
PCALIGN $32
ADD $8, R3
PCALIGN $8
ADD $4, R8
RET
`
const x64pgm = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
`
const x32pgm = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
`
const x16pgm = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
`
const x0pgm = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
`
const x64pgmA64 = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
`
const x64pgmA32 = `
TEXT test(SB),0,$0-0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
OR R0, R0
PNOP
`
// Test that nops are inserted when crossing 64B boundaries, and
// alignment is adjusted to avoid crossing.
func TestPfxAlign(t *testing.T) {
testenv.MustHaveGoBuild(t)
dir, err := os.MkdirTemp("", "testpfxalign")
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
pgms := []struct {
text []byte
align string
hasNop bool
}{
{[]byte(x0pgm), "align=0x0", false}, // No alignment or nop adjustments needed
{[]byte(x16pgm), "align=0x20", false}, // Increased alignment needed
{[]byte(x32pgm), "align=0x40", false}, // Worst case alignment needed
{[]byte(x64pgm), "align=0x0", true}, // 0 aligned is default (16B) alignment
{[]byte(x64pgmA64), "align=0x40", true}, // extra alignment + nop
{[]byte(x64pgmA32), "align=0x20", true}, // extra alignment + nop
}
for _, pgm := range pgms {
tmpfile := filepath.Join(dir, "x.s")
err = os.WriteFile(tmpfile, pgm.text, 0644)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile)
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=ppc64le")
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("Failed to compile %v: %v\n", pgm, err)
}
if !strings.Contains(string(out), pgm.align) {
t.Errorf(fmt.Sprintf("Fatal, misaligned text with prefixed instructions:\n%s\n", string(out)))
}
hasNop := strings.Contains(string(out), "00 00 00 60")
if hasNop != pgm.hasNop {
t.Errorf(fmt.Sprintf("Fatal, prefixed instruction is missing nop padding:\n%s\n", string(out)))
}
}
}
// TestLarge generates a very large file to verify that large
// program builds successfully, and branches which exceed the
// range of BC are rewritten to reach.
func TestLarge(t *testing.T) {
if testing.Short() {
t.Skip("Skip in short mode")
}
testenv.MustHaveGoBuild(t)
dir, err := os.MkdirTemp("", "testlarge")
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
// A few interesting test cases for long conditional branch fixups
tests := []struct {
jmpinsn string
backpattern []string
fwdpattern []string
}{
// Test the interesting cases of conditional branch rewrites for too-far targets. Simple conditional
// branches can be made to reach with one JMP insertion, compound conditionals require two.
//
// beq <-> bne conversion (insert one jump)
{"BEQ",
[]string{``,
`0x20030 131120\s\(.*\)\tBC\t\$4,\sCR0EQ,\s131128`,
`0x20034 131124\s\(.*\)\tJMP\t0`},
[]string{``,
`0x0000 00000\s\(.*\)\tBC\t\$4,\sCR0EQ,\s8`,
`0x0004 00004\s\(.*\)\tJMP\t131128`},
},
{"BNE",
[]string{``,
`0x20030 131120\s\(.*\)\tBC\t\$12,\sCR0EQ,\s131128`,
`0x20034 131124\s\(.*\)\tJMP\t0`},
[]string{``,
`0x0000 00000\s\(.*\)\tBC\t\$12,\sCR0EQ,\s8`,
`0x0004 00004\s\(.*\)\tJMP\t131128`}},
// bdnz (BC 16,0,tgt) <-> bdz (BC 18,0,+4) conversion (insert one jump)
{"BC 16,0,",
[]string{``,
`0x20030 131120\s\(.*\)\tBC\t\$18,\sCR0LT,\s131128`,
`0x20034 131124\s\(.*\)\tJMP\t0`},
[]string{``,
`0x0000 00000\s\(.*\)\tBC\t\$18,\sCR0LT,\s8`,
`0x0004 00004\s\(.*\)\tJMP\t131128`}},
{"BC 18,0,",
[]string{``,
`0x20030 131120\s\(.*\)\tBC\t\$16,\sCR0LT,\s131128`,
`0x20034 131124\s\(.*\)\tJMP\t0`},
[]string{``,
`0x0000 00000\s\(.*\)\tBC\t\$16,\sCR0LT,\s8`,
`0x0004 00004\s\(.*\)\tJMP\t131128`}},
// bdnzt (BC 8,0,tgt) <-> bdnzt (BC 8,0,+4) conversion (insert two jumps)
{"BC 8,0,",
[]string{``,
`0x20034 131124\s\(.*\)\tBC\t\$8,\sCR0LT,\s131132`,
`0x20038 131128\s\(.*\)\tJMP\t131136`,
`0x2003c 131132\s\(.*\)\tJMP\t0\n`},
[]string{``,
`0x0000 00000\s\(.*\)\tBC\t\$8,\sCR0LT,\s8`,
`0x0004 00004\s\(.*\)\tJMP\t12`,
`0x0008 00008\s\(.*\)\tJMP\t131136\n`}},
}
for _, test := range tests {
// generate a very large function
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
gen(buf, test.jmpinsn)
tmpfile := filepath.Join(dir, "x.s")
err = os.WriteFile(tmpfile, buf.Bytes(), 0644)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
// Test on all supported ppc64 platforms
for _, platenv := range platformEnvs {
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile)
cmd.Env = append(os.Environ(), platenv...)
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("Assemble failed (%v): %v, output: %s", platenv, err, out)
}
matched, err := regexp.MatchString(strings.Join(test.fwdpattern, "\n\t*"), string(out))
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("Failed to detect long forward BC fixup in (%v):%s\n", platenv, out)
}
matched, err = regexp.MatchString(strings.Join(test.backpattern, "\n\t*"), string(out))
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("Failed to detect long backward BC fixup in (%v):%s\n", platenv, out)
}
}
}
}
// gen generates a very large program with a very long forward and backwards conditional branch.
func gen(buf *bytes.Buffer, jmpinsn string) {
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
fmt.Fprintln(buf, "label_start:")
fmt.Fprintln(buf, jmpinsn, "label_end")
for i := 0; i < (1<<15 + 10); i++ {
fmt.Fprintln(buf, "MOVD R0, R1")
}
fmt.Fprintln(buf, jmpinsn, "label_start")
fmt.Fprintln(buf, "label_end:")
fmt.Fprintln(buf, "MOVD R0, R1")
fmt.Fprintln(buf, "RET")
}
// TestPCalign generates two asm files containing the
// PCALIGN directive, to verify correct values are and
// accepted, and incorrect values are flagged in error.
func TestPCalign(t *testing.T) {
var pattern8 = `0x...8\s.*ADD\s..,\sR8`
var pattern16 = `0x...[80]\s.*MOVD\s..,\sR16`
var pattern32 = `0x...0\s.*ADD\s..,\sR3`
testenv.MustHaveGoBuild(t)
dir, err := os.MkdirTemp("", "testpcalign")
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
// generate a test with valid uses of PCALIGN
tmpfile := filepath.Join(dir, "x.s")
err = os.WriteFile(tmpfile, []byte(validPCAlignSrc), 0644)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
// build generated file without errors and assemble it
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux")
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("Build failed: %v, output: %s", err, out)
}
matched, err := regexp.MatchString(pattern8, string(out))
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("The 8 byte alignment is not correct: %t, output:%s\n", matched, out)
}
matched, err = regexp.MatchString(pattern16, string(out))
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("The 16 byte alignment is not correct: %t, output:%s\n", matched, out)
}
matched, err = regexp.MatchString(pattern32, string(out))
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("The 32 byte alignment is not correct: %t, output:%s\n", matched, out)
}
// generate a test with invalid use of PCALIGN
tmpfile = filepath.Join(dir, "xi.s")
err = os.WriteFile(tmpfile, []byte(invalidPCAlignSrc), 0644)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
// build test with errors and check for messages
cmd = exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "xi.o"), "-S", tmpfile)
cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux")
out, err = cmd.CombinedOutput()
if !strings.Contains(string(out), "Unexpected alignment") {
t.Errorf("Invalid alignment not detected for PCALIGN\n")
}
}
// Verify register constants are correctly aligned. Much of the ppc64 assembler assumes masking out significant
// bits will produce a valid register number:
// REG_Rx & 31 == x
// REG_Fx & 31 == x
// REG_Vx & 31 == x
// REG_VSx & 63 == x
// REG_SPRx & 1023 == x
// REG_CRx & 7 == x
//
// VR and FPR disjointly overlap VSR, interpreting as VSR registers should produce the correctly overlapped VSR.
// REG_FPx & 63 == x
// REG_Vx & 63 == x + 32
func TestRegValueAlignment(t *testing.T) {
tstFunc := func(rstart, rend, msk, rout int) {
for i := rstart; i <= rend; i++ {
if i&msk != rout {
t.Errorf("%v is not aligned to 0x%X (expected %d, got %d)\n", rconv(i), msk, rout, rstart&msk)
}
rout++
}
}
var testType = []struct {
rstart int
rend int
msk int
rout int
}{
{REG_VS0, REG_VS63, 63, 0},
{REG_R0, REG_R31, 31, 0},
{REG_F0, REG_F31, 31, 0},
{REG_V0, REG_V31, 31, 0},
{REG_V0, REG_V31, 63, 32},
{REG_F0, REG_F31, 63, 0},
{REG_SPR0, REG_SPR0 + 1023, 1023, 0},
{REG_CR0, REG_CR7, 7, 0},
{REG_CR0LT, REG_CR7SO, 31, 0},
}
for _, t := range testType {
tstFunc(t.rstart, t.rend, t.msk, t.rout)
}
}
// Verify interesting obj.Addr arguments are classified correctly.
func TestAddrClassifier(t *testing.T) {
type cmplx struct {
pic int
pic_dyn int
dyn int
nonpic int
}
tsts := [...]struct {
arg obj.Addr
output interface{}
}{
// Supported register type args
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_R1}, C_REG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_R2}, C_REGP},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_F1}, C_FREG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_F2}, C_FREGP},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_V2}, C_VREG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS1}, C_VSREG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS2}, C_VSREGP},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR}, C_CREG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1}, C_CREG},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1SO}, C_CRBIT},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0}, C_SPR},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 1}, C_XER},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 8}, C_LR},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 9}, C_CTR},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_FPSCR}, C_FPSCR},
{obj.Addr{Type: obj.TYPE_REG, Reg: REG_A1}, C_AREG},
// Memory type arguments.
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_GOTREF}, C_ADDR},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_TOCREF}, C_ADDR},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.STLSBSS}}, cmplx{C_TLS_IE, C_TLS_IE, C_TLS_LE, C_TLS_LE}},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_ADDR},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO}, C_SOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: BIG}, C_LOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM}, C_SOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: BIG}, C_LOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LOREG}, // 33 is FixedFrameSize-1
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE}, C_ZOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Index: REG_R4}, C_XOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: 1}, C_SOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: BIG}, C_LOREG},
{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: -BIG - 33}, C_LOREG},
// Misc (golang initializes -0.0 to 0.0, hence the obfuscation below)
{obj.Addr{Type: obj.TYPE_TEXTSIZE}, C_TEXTSIZE},
{obj.Addr{Type: obj.TYPE_FCONST, Val: 0.0}, C_ZCON},
{obj.Addr{Type: obj.TYPE_FCONST, Val: math.Float64frombits(0x8000000000000000)}, C_S16CON},
// Address type arguments
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1}, C_SACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: BIG}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: -BIG - 1}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1 << 32}, C_DACON},
{obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_STATIC, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: 1}, C_SACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: BIG}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: 1}, C_SACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: BIG}, C_LACON},
{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LACON}, // 33 is FixedFrameSize-1
// Constant type arguments
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0}, C_ZCON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1}, C_U1CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 2}, C_U2CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 4}, C_U3CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 8}, C_U4CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 16}, C_U5CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 32}, C_U8CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 14}, C_U15CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 15}, C_U16CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 16}, C_U3216CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 + 1<<16}, C_U32CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 32}, C_S34CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 33}, C_64CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -1}, C_S16CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -0x10000}, C_S3216CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -0x10001}, C_S32CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 33)}, C_S34CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 34)}, C_64CON},
// Branch like arguments
{obj.Addr{Type: obj.TYPE_BRANCH, Sym: &obj.LSym{Type: objabi.SDATA}}, cmplx{C_SBRA, C_LBRAPIC, C_LBRAPIC, C_SBRA}},
{obj.Addr{Type: obj.TYPE_BRANCH}, C_SBRA},
}
pic_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Arch: &Linkppc64}, autosize: 0}
pic_dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0}
dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0}
nonpic_ctxt9 := ctxt9{ctxt: &obj.Link{Arch: &Linkppc64}, autosize: 0}
ctxts := [...]*ctxt9{&pic_ctxt9, &pic_dyn_ctxt9, &dyn_ctxt9, &nonpic_ctxt9}
name := [...]string{"pic", "pic_dyn", "dyn", "nonpic"}
for _, tst := range tsts {
var expect []int
switch tst.output.(type) {
case cmplx:
v := tst.output.(cmplx)
expect = []int{v.pic, v.pic_dyn, v.dyn, v.nonpic}
case int:
expect = []int{tst.output.(int), tst.output.(int), tst.output.(int), tst.output.(int)}
}
for i, _ := range ctxts {
if output := ctxts[i].aclass(&tst.arg); output != expect[i] {
t.Errorf("%s.aclass(%v) = %v, expected %v\n", name[i], tst.arg, DRconv(output), DRconv(expect[i]))
}
}
}
}