blob: 48ad3aeb0b7fea1cc8fd6adc00f044d63ce4495e [file] [edit]
#include <xbyak/xbyak_util.h>
#include <cinttypes>
#include <cstring>
#include <vector>
#include <map>
#ifdef XBYAK32
#error "this sample is for only 64-bit mode"
#endif
using namespace Xbyak::util;
#ifndef DUMP
#ifdef _MSC_VER
#pragma warning(disable : 4459)
#pragma warning(disable : 4996)
#endif
#include <cybozu/test.hpp>
#ifdef XBYAK64_WIN
#include "sf_test_win.h"
#endif
#ifdef XBYAK64_GCC
#include "sf_test_gcc.h"
#endif
struct Code : public Xbyak::CodeGenerator {
void gen1()
{
StackFrame sf(this, 1);
mov(rax, sf.p[0]);
}
void gen2()
{
StackFrame sf(this, 2);
lea(rax, ptr [sf.p[0] + sf.p[1]]);
}
void gen3()
{
StackFrame sf(this, 3);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
}
void gen4()
{
StackFrame sf(this, 4);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
add(rax, sf.p[3]);
}
void gen5()
{
StackFrame sf(this, 4, UseRCX);
xor_(rcx, rcx);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
add(rax, sf.p[3]);
}
void gen6()
{
StackFrame sf(this, 4, UseRCX | UseRDX);
xor_(rcx, rcx);
xor_(rdx, rdx);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
add(rax, sf.p[3]);
}
void gen7()
{
StackFrame sf(this, 3, UseRCX | UseRDX);
xor_(rcx, rcx);
xor_(rdx, rdx);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
}
void gen8()
{
StackFrame sf(this, 3, 3 | UseRCX | UseRDX);
xor_(rcx, rcx);
xor_(rdx, rdx);
mov(sf.t[0], 1);
mov(sf.t[1], 2);
mov(sf.t[2], 3);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
}
void gen9()
{
StackFrame sf(this, 3, 3 | UseRCX | UseRDX, 32);
xor_(rcx, rcx);
xor_(rdx, rdx);
mov(sf.t[0], 1);
mov(sf.t[1], 2);
mov(sf.t[2], 3);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
mov(ptr [rsp + 8 * 0], rax);
mov(ptr [rsp + 8 * 1], rax);
mov(ptr [rsp + 8 * 2], rax);
mov(ptr [rsp + 8 * 3], rax);
}
void gen10()
{
StackFrame sf(this, 4, 8 | UseRCX | UseRDX, 32);
xor_(rcx, rcx);
xor_(rdx, rdx);
for (int i = 0; i < 8; i++) {
mov(sf.t[i], i);
}
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
add(rax, sf.p[3]);
mov(ptr [rsp + 8 * 0], rax);
mov(ptr [rsp + 8 * 1], rax);
mov(ptr [rsp + 8 * 2], rax);
mov(ptr [rsp + 8 * 3], rax);
}
void gen11()
{
StackFrame sf(this, 0, UseRCX);
xor_(rcx, rcx);
mov(rax, 3);
}
void gen12()
{
StackFrame sf(this, 4, UseRDX);
xor_(rdx, rdx);
mov(rax, sf.p[0]);
add(rax, sf.p[1]);
add(rax, sf.p[2]);
add(rax, sf.p[3]);
}
/*
int64_t f(const int64_t a[13]) { return sum-of-a[]; }
*/
void gen13()
{
StackFrame sf(this, 1, 13);
for (int i = 0; i < 13; i++) {
mov(sf.t[i], ptr[sf.p[0] + i * 8]);
}
mov(rax, sf.t[0]);
for (int i = 1; i < 13; i++) {
add(rax, sf.t[i]);
}
}
/*
same as gen13
*/
void gen14()
{
StackFrame sf(this, 1, 11 | UseRCX | UseRDX);
Pack t = sf.t;
t.append(rcx);
t.append(rdx);
for (int i = 0; i < 13; i++) {
mov(t[i], ptr[sf.p[0] + i * 8]);
}
mov(rax, t[0]);
for (int i = 1; i < 13; i++) {
add(rax, t[i]);
}
}
/*
return (1 << 15) - 1;
*/
void gen15()
{
StackFrame sf(this, 0, 14, 8);
Pack t = sf.t;
t.append(rax);
for (int i = 0; i < 15; i++) {
mov(t[i], uint64_t(1) << i);
}
mov(qword[rsp], 0);
for (int i = 0; i < 15; i++) {
add(ptr[rsp], t[i]);
}
mov(rax, ptr[rsp]);
}
};
struct Code2 : Xbyak::CodeGenerator {
Code2()
: Xbyak::CodeGenerator(4096 * 32)
{
}
void gen(int pNum, int tNum, int stackSizeByte)
{
StackFrame sf(this, pNum, tNum, stackSizeByte);
if (tNum & UseRCX) xor_(rcx, rcx);
if (tNum & UseRDX) xor_(rdx, rdx);
for (int i = 0, n = tNum & ~(UseRCX | UseRDX); i < n; i++) {
mov(sf.t[i], 5);
}
for (int i = 0; i < stackSizeByte; i++) {
mov(byte [rsp + i], 0);
}
mov(rax, 1);
for (int i = 0; i < pNum; i++) {
add(rax, sf.p[i]);
}
}
void gen2(int pNum, int tNum, int stackSizeByte)
{
StackFrame sf(this, pNum, tNum, stackSizeByte);
mov(rax, rsp);
}
};
void verify(const uint8_t *_f, int pNum)
{
uint8_t *f = const_cast<uint8_t*>(_f);
switch (pNum) {
case 0:
CYBOZU_TEST_EQUAL(1, reinterpret_cast<int (*)()>(f)());
return;
case 1:
CYBOZU_TEST_EQUAL(11, reinterpret_cast<int (*)(int)>(f)(10));
return;
case 2:
CYBOZU_TEST_EQUAL(111, reinterpret_cast<int (*)(int, int)>(f)(10, 100));
return;
case 3:
CYBOZU_TEST_EQUAL(1111, reinterpret_cast<int (*)(int, int, int)>(f)(10, 100, 1000));
return;
case 4:
CYBOZU_TEST_EQUAL(11111, reinterpret_cast<int (*)(int, int, int, int)>(f)(10, 100, 1000, 10000));
return;
default:
printf("ERR pNum=%d\n", pNum);
exit(1);
}
}
CYBOZU_TEST_AUTO(param)
{
Code2 code;
for (int stackSize = 0; stackSize < 32; stackSize += 7) {
for (int pNum = 0; pNum < 4; pNum++) {
for (int mode = 0; mode < 4; mode++) {
int maxNum = 0;
int opt = 0;
if (mode == 0) {
maxNum = 10;
} else if (mode == 1) {
maxNum = 9;
opt = UseRCX;
} else if (mode == 2) {
maxNum = 9;
opt = UseRDX;
} else {
maxNum = 8;
opt = UseRCX | UseRDX;
}
for (int tNum = 0; tNum < maxNum; tNum++) {
// printf("pNum=%d, tNum=%d, stackSize=%d\n", pNum, tNum | opt, stackSize);
const uint8_t *f = code.getCurr();
code.gen(pNum, tNum | opt, stackSize);
verify(f, pNum);
/*
check rsp is 16-byte aligned if stackSize > 0
*/
if (stackSize > 0) {
Code2 c2;
c2.gen2(pNum, tNum | opt, stackSize);
uint64_t addr = c2.getCode<uint64_t (*)()>()();
CYBOZU_TEST_EQUAL(addr % 16, 0);
}
}
}
}
}
}
CYBOZU_TEST_AUTO(args)
{
Code code;
int (*f1)(int) = code.getCurr<int (*)(int)>();
code.gen1();
CYBOZU_TEST_EQUAL(5, f1(5));
int (*f2)(int, int) = code.getCurr<int (*)(int, int)>();
code.gen2();
CYBOZU_TEST_EQUAL(9, f2(3, 6));
int (*f3)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
code.gen3();
CYBOZU_TEST_EQUAL(14, f3(1, 4, 9));
int (*f4)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
code.gen4();
CYBOZU_TEST_EQUAL(30, f4(1, 4, 9, 16));
int (*f5)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
code.gen5();
CYBOZU_TEST_EQUAL(23, f5(2, 5, 7, 9));
int (*f6)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
code.gen6();
CYBOZU_TEST_EQUAL(18, f6(3, 4, 5, 6));
int (*f7)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
code.gen7();
CYBOZU_TEST_EQUAL(12, f7(3, 4, 5));
int (*f8)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
code.gen8();
CYBOZU_TEST_EQUAL(23, f8(5, 8, 10));
int (*f9)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
code.gen9();
CYBOZU_TEST_EQUAL(60, f9(10, 20, 30));
int (*f10)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
code.gen10();
CYBOZU_TEST_EQUAL(100, f10(10, 20, 30, 40));
int (*f11)() = code.getCurr<int (*)()>();
code.gen11();
CYBOZU_TEST_EQUAL(3, f11());
int (*f12)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
code.gen12();
CYBOZU_TEST_EQUAL(24, f12(3, 5, 7, 9));
{
int64_t tbl[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
int64_t (*f13)(const int64_t*) = code.getCurr<int64_t (*)(const int64_t*)>();
code.gen13();
CYBOZU_TEST_EQUAL(91, f13(tbl));
int64_t (*f14)(const int64_t*) = code.getCurr<int64_t (*)(const int64_t*)>();
code.gen14();
CYBOZU_TEST_EQUAL(91, f14(tbl));
}
int (*f15)() = code.getCurr<int (*)()>();
code.gen15();
CYBOZU_TEST_EQUAL((1 << 15) - 1, f15());
}
void put(const Xbyak::util::Pack& p)
{
for (size_t i = 0, n = p.size(); i < n; i++) {
printf("%s ", p[i].toString());
}
printf("\n");
}
void verifyPack(const Xbyak::util::Pack& p, const int *tbl, size_t tblNum)
{
for (size_t i = 0; i < tblNum; i++) {
CYBOZU_TEST_EQUAL(p[i].getIdx(), tbl[i]);
}
}
CYBOZU_TEST_AUTO(pack)
{
const int N = 10;
Xbyak::Reg64 regTbl[N];
for (int i = 0; i < N; i++) {
regTbl[i] = Xbyak::Reg64(i);
}
Xbyak::util::Pack p(regTbl, N);
const struct {
int pos;
int num;
int tbl[10];
} tbl[] = {
{ 0, 10, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } },
{ 1, 9, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } },
{ 2, 8, { 2, 3, 4, 5, 6, 7, 8, 9 } },
{ 3, 7, { 3, 4, 5, 6, 7, 8, 9 } },
{ 4, 6, { 4, 5, 6, 7, 8, 9 } },
{ 5, 5, { 5, 6, 7, 8, 9 } },
{ 6, 4, { 6, 7, 8, 9 } },
{ 7, 3, { 7, 8, 9 } },
{ 8, 2, { 8, 9 } },
{ 9, 1, { 9 } },
{ 3, 5, { 3, 4, 5, 6, 7 } },
};
for (size_t i = 0; i < sizeof(tbl) / sizeof(*tbl); i++) {
const int pos = tbl[i].pos;
const int num = tbl[i].num;
verifyPack(p.sub(pos, num), tbl[i].tbl, num);
if (pos + num == N) {
verifyPack(p.sub(pos), tbl[i].tbl, num);
}
}
}
struct CloseCode : Xbyak::CodeGenerator {
CloseCode(size_t mode)
{
switch (mode) {
case 0:
{
StackFrame sf(this, 0);
// close() is automatically called.
}
break;
case 1:
{
StackFrame sf(this, 0, 0, 0, false);
sf.close(); // Explicitly call close().
setProtectModeRE(); // Ensure that no writes occur in destructor by setting read-exec
}
break;
case 2:
{
StackFrame sf(this, 0, 0, 0, false);
sf.close(); // Explicitly call close().
sf.close(); // Explicitly call close().
setProtectModeRE(); // Ensure that no writes occur in destructor by setting read-exec
}
break;
default:
CYBOZU_TEST_ASSERT(false);
}
}
};
CYBOZU_TEST_AUTO(close)
{
const size_t expectedTbl[] = {
1, 1, 2,
};
for (size_t i = 0; i < sizeof(expectedTbl)/sizeof(expectedTbl[0]); i++) {
CloseCode c(i);
CYBOZU_TEST_EQUAL(c.getSize(), expectedTbl[i]);
}
}
#endif
struct ParamId {
int pNum;
int tNum;
int useRegs;
int stackSizeByte;
struct encoded {
uint8_t pNum;
uint8_t tNum;
uint16_t useRegsLow;
uint16_t useRegsHigh;
uint8_t stackSizeByte;
uint8_t reserved;
};
uint64_t id() const
{
encoded e;
uint32_t ur = uint32_t(useRegs) >> 5;
e.pNum = uint8_t(pNum);
e.tNum = uint8_t(tNum);
e.useRegsLow = uint16_t(ur);
e.useRegsHigh = uint16_t(ur >> 16);
e.stackSizeByte = uint8_t(stackSizeByte);
e.reserved = 0;
uint64_t v;
memcpy(&v, &e, sizeof(v));
return v;
};
void set_id(uint64_t v)
{
encoded e;
memcpy(&e, &v, sizeof(e));
pNum = e.pNum;
tNum = e.tNum;
useRegs = int(uint32_t(e.useRegsLow) | (uint32_t(e.useRegsHigh) << 16)) << 5;
stackSizeByte = e.stackSizeByte;
}
};
typedef std::vector<uint8_t> Bytes;
#ifndef DUMP
void cmpAndDumpIfFailed(int rhs, int lhs, const Bytes& d)
{
CYBOZU_TEST_EQUAL(rhs, lhs);
if (rhs != lhs) {
FILE *fp = fopen("dump.bin", "wb");
fwrite(d.data(), 1, d.size(), fp);
fclose(fp);
exit(1);
}
}
#endif
struct Data {
ParamId paramId;
Bytes code;
};
typedef std::map<uint64_t, Data> DataMap;
void stackFrameTest()
{
DataMap dataMap;
struct Code : Xbyak::CodeGenerator {
Code(int pNum, int tNum, int useRegs, int stackSizeByte)
{
StackFrame sf(this, pNum, tNum|useRegs, stackSizeByte);
// modify
for (int i = 0; i < tNum; i++) {
mov(sf.t[i], 12345);
}
if (useRegs & UseRCX) {
mov(rcx, 12345);
}
if (useRegs & UseRDX) {
mov(rdx, 12345);
}
if (useRegs & UseRSI) {
mov(rsi, 1000);
}
if (useRegs & UseRDI) {
mov(rdi, 2000);
}
// use rbp if UseRBP and !UseRBPAsFramePointer
if ((useRegs & UseRBPAsFramePointer) == UseRBP) {
mov(rbp, 3000);
}
// eax is sum of all params and (esp & 15) if stackSizeByte > 0
if (stackSizeByte > 0) {
mov(eax, esp);
and_(eax, 15);
} else {
xor_(eax, eax);
}
for (int i = 0; i < pNum; i++) {
add(rax, sf.p[i]);
}
}
};
static const uint8_t stackSizeTbl[] = { 0, 33 };
for (int pNum = 0; pNum <= 4; pNum++) {
for (int tNum = 0; tNum <= 14; tNum++) {
// skip middle middle values of tNum to keep the headers small
if (3 < tNum && tNum < 13) continue;
for (int i = 0; i < (1<<6); i++) {
int totalNum = pNum + tNum;
int useRegs = 0;
if (i & 1) { useRegs |= UseRCX; totalNum++; }
if (i & 2) { useRegs |= UseRDX; totalNum++; }
if (i & 4) { useRegs |= UseRSI; totalNum++; }
if (i & 8) { useRegs |= UseRDI; totalNum++; }
// UseRBP and UseRBPAsFramePointer are mutually exclusive
if (i & 16) { useRegs |= UseRBP; totalNum++; }
if (!(i & 16) && (i & 32)) { useRegs |= UseRBPAsFramePointer; totalNum++; }
if (totalNum > 14) continue;
for (size_t j = 0; j < sizeof(stackSizeTbl)/sizeof(stackSizeTbl[0]); j++) {
int stackSizeByte = stackSizeTbl[j];
//fprintf(stderr, "pNum=%d, tNum=%d, useRegs=0x%X stackSizeByte=%d\n", pNum, tNum, useRegs, stackSizeByte);
Code c(pNum, tNum, useRegs, stackSizeByte);
//fprintf(stderr, "code size = %d\n", int(c.getSize()));
Data d;
d.paramId.pNum = pNum;
d.paramId.tNum = tNum;
d.paramId.useRegs = useRegs;
d.paramId.stackSizeByte = stackSizeByte;
d.code.assign(c.getCode(), c.getCode() + c.getSize());
dataMap[d.paramId.id()] = d;
#ifndef DUMP
switch (pNum) {
case 0:
{
int (*f)() = c.getCode<int (*)()>();
CYBOZU_TEST_EQUAL(0, f());
// cmpAndDumpIfFailed(0, f(), d.code);
break;
}
case 1:
{
int (*f1)(int) = c.getCode<int (*)(int)>();
CYBOZU_TEST_EQUAL(1, f1(1));
break;
}
case 2:
{
int (*f2)(int, int) = c.getCode<int (*)(int, int)>();
CYBOZU_TEST_EQUAL(11, f2(1, 10));
break;
}
case 3:
{
int (*f3)(int, int, int) = c.getCode<int (*)(int, int, int)>();
CYBOZU_TEST_EQUAL(111, f3(1, 10, 100));
break;
}
case 4:
{
int (*f4)(int, int, int, int) = c.getCode<int (*)(int, int, int, int)>();
CYBOZU_TEST_EQUAL(1111, f4(1, 10, 100, 1000));
break;
}
}
#endif
}
}
}
}
// UseSSE(n)/UseAVX(n) : xmm save/restore (Win64), vzeroupper and NoVzeroupper
#ifndef DUMP
const bool hasAvx = Xbyak::util::Cpu().has(Xbyak::util::Cpu::tAVX);
#endif
static const uint8_t vecNumTbl[] = { 0, 1, 6, 7, 8, 15, 16, 32 };
for (int kind = 0; kind < 2; kind++) { // 0: UseSSE, 1: UseAVX
for (size_t vi = 0; vi < sizeof(vecNumTbl)/sizeof(vecNumTbl[0]); vi++) {
const int vecNum = vecNumTbl[vi];
if (kind == 0 && vecNum > 16) continue;
for (int noVz = 0; noVz < (kind == 0 ? 1 : 2); noVz++) {
for (int useRbp = 0; useRbp < 2; useRbp++) { // flip the parity of saveNum_
for (size_t j = 0; j < sizeof(stackSizeTbl)/sizeof(stackSizeTbl[0]); j++) {
const int stackSizeByte = stackSizeTbl[j];
int useRegs = kind == 0 ? UseSSE(vecNum) : UseAVX(vecNum);
if (noVz) useRegs |= NoVzeroupper;
if (useRbp) useRegs |= UseRBP;
Code c(1, 0, useRegs, stackSizeByte);
Data d;
d.paramId.pNum = 1;
d.paramId.tNum = 0;
d.paramId.useRegs = useRegs;
d.paramId.stackSizeByte = stackSizeByte;
d.code.assign(c.getCode(), c.getCode() + c.getSize());
dataMap[d.paramId.id()] = d;
#ifndef DUMP
// vzeroupper/vmovaps require AVX
if (kind == 1 && !hasAvx) continue;
int (*f1)(int) = c.getCode<int (*)(int)>();
CYBOZU_TEST_EQUAL(1, f1(1));
#endif
}
}
}
}
}
#ifdef DUMP
for (DataMap::const_iterator it = dataMap.begin(); it != dataMap.end(); ++it) {
const Data& d = it->second;
printf("static const uint8_t code_%" PRIx64 "[] = {\n", d.paramId.id());
for (size_t j = 0; j < d.code.size(); j++) {
if (j % 16 == 0) {
if (j > 0) printf("\n");
printf("\t");
}
if (j > 0) printf(" ");
printf("0x%02x,", d.code[j]);
}
printf("\n};\n");
}
printf("static const struct {\n");
printf("\tuint64_t paramId;\n");
printf("\tconst uint8_t *code;\n");
printf("\tsize_t codeSize;\n");
printf("} g_dataVec[] = {\n");
for (DataMap::const_iterator it = dataMap.begin(); it != dataMap.end(); ++it) {
const Data& d = it->second;
printf("\t{ 0x%" PRIx64 ", code_%" PRIx64 ", %zu },\n", d.paramId.id(), d.paramId.id(), d.code.size());
}
printf("};\n");
#else
DataMap dataMapExpected;
for (size_t i = 0; i < sizeof(g_dataVec) / sizeof(*g_dataVec); i++) {
const uint64_t id = g_dataVec[i].paramId;
Data d;
d.paramId.set_id(id);
d.code.assign(g_dataVec[i].code, g_dataVec[i].code + g_dataVec[i].codeSize);
dataMapExpected[id] = d;
}
CYBOZU_TEST_EQUAL(dataMap.size(), dataMapExpected.size());
for (DataMap::const_iterator it = dataMapExpected.begin(); it != dataMapExpected.end(); ++it) {
const uint64_t id = it->first;
DataMap::const_iterator it2 = dataMap.find(id);
CYBOZU_TEST_ASSERT(it2 != dataMap.end());
const Data& d = it2->second;
const Data& dExpected = it->second;
CYBOZU_TEST_EQUAL(d.code.size(), dExpected.code.size());
CYBOZU_TEST_EQUAL_ARRAY(d.code.data(), dExpected.code.data(), d.code.size());
}
#endif
}
/*
Test StackFrame APX flag combinations.
Covers UsePUSH2, UsePPX, UsePUSH2|UsePPX, UseR30R31 and combinations.
A small parameter/register subset keeps the golden file manageable.
*/
void apxStackFrameTest()
{
DataMap dataMap;
struct Code : Xbyak::CodeGenerator {
Code(int pNum, int tNum, int useRegs, int stackSizeByte)
{
StackFrame sf(this, pNum, tNum|useRegs, stackSizeByte);
for (int i = 0; i < tNum; i++) {
mov(sf.t[i], 12345);
}
if (useRegs & UseRCX) mov(rcx, 12345);
if (useRegs & UseRDX) mov(rdx, 12345);
if (useRegs & UseRSI) mov(rsi, 1000);
if (useRegs & UseRDI) mov(rdi, 2000);
if ((useRegs & UseRBPAsFramePointer) == UseRBP) mov(rbp, 3000);
if (useRegs & UseR30R31) {
mov(r30, 4321);
mov(r31, 8765);
}
if (stackSizeByte > 0) {
mov(eax, esp);
and_(eax, 15);
} else {
xor_(eax, eax);
}
for (int i = 0; i < pNum; i++) {
add(rax, sf.p[i]);
}
}
};
static const int apxFlagCombos[] = {
UsePUSH2,
UsePPX,
UsePUSH2 | UsePPX,
UseR30R31,
UseR30R31 | UsePUSH2 | UsePPX,
};
static const uint8_t stackSizeTbl[] = { 0, 33 };
for (size_t ai = 0; ai < sizeof(apxFlagCombos)/sizeof(apxFlagCombos[0]); ai++) {
const int apxFlags = apxFlagCombos[ai];
// Limit pNum/tNum/useRegs to a representative subset to keep the golden file small.
for (int pNum = 0; pNum <= 2; pNum++) {
for (int tNum = 0; tNum <= 4; tNum++) {
// First 16 useRegs combos (bits 0..3: UseRCX, UseRDX, UseRSI, UseRDI)
for (int i = 0; i < 16; i++) {
int totalNum = pNum + tNum;
int useRegs = apxFlags;
if (i & 1) { useRegs |= UseRCX; totalNum++; }
if (i & 2) { useRegs |= UseRDX; totalNum++; }
if (i & 4) { useRegs |= UseRSI; totalNum++; }
if (i & 8) { useRegs |= UseRDI; totalNum++; }
// UseR30R31 doesn't consume any of the 14 managed slots (r30/r31 are
// outside StackFrame's allocation table); this accounting is only a
// heuristic to keep the golden file from growing too large.
if (useRegs & UseR30R31) totalNum += 2;
if (totalNum > 14) continue;
for (size_t j = 0; j < sizeof(stackSizeTbl)/sizeof(stackSizeTbl[0]); j++) {
int stackSizeByte = stackSizeTbl[j];
Code c(pNum, tNum, useRegs, stackSizeByte);
Data d;
d.paramId.pNum = pNum;
d.paramId.tNum = tNum;
d.paramId.useRegs = useRegs;
d.paramId.stackSizeByte = stackSizeByte;
d.code.assign(c.getCode(), c.getCode() + c.getSize());
dataMap[d.paramId.id()] = d;
#ifndef DUMP
// APX instructions (push2, push2p, pushp etc.) require APX_F; skip execution otherwise.
static const bool hasApx = Xbyak::util::Cpu().has(Xbyak::util::Cpu::tAPX_F);
if (!hasApx) continue;
switch (pNum) {
case 0:
{
int (*f)() = c.getCode<int (*)()>();
CYBOZU_TEST_EQUAL(0, f());
break;
}
case 1:
{
int (*f1)(int) = c.getCode<int (*)(int)>();
CYBOZU_TEST_EQUAL(1, f1(1));
break;
}
case 2:
{
int (*f2)(int, int) = c.getCode<int (*)(int, int)>();
CYBOZU_TEST_EQUAL(11, f2(1, 10));
break;
}
}
#endif
}
}
}
}
}
#ifdef DUMP
for (DataMap::const_iterator it = dataMap.begin(); it != dataMap.end(); ++it) {
const Data& d = it->second;
printf("static const uint8_t apx_code_%" PRIx64 "[] = {\n", d.paramId.id());
for (size_t j = 0; j < d.code.size(); j++) {
if (j % 16 == 0) {
if (j > 0) printf("\n");
printf("\t");
}
if (j > 0) printf(" ");
printf("0x%02x,", d.code[j]);
}
printf("\n};\n");
}
printf("static const struct {\n");
printf("\tuint64_t paramId;\n");
printf("\tconst uint8_t *code;\n");
printf("\tsize_t codeSize;\n");
printf("} g_apxDataVec[] = {\n");
for (DataMap::const_iterator it = dataMap.begin(); it != dataMap.end(); ++it) {
const Data& d = it->second;
printf("\t{ 0x%" PRIx64 ", apx_code_%" PRIx64 ", %zu },\n", d.paramId.id(), d.paramId.id(), d.code.size());
}
printf("};\n");
#else
DataMap dataMapExpected;
for (size_t i = 0; i < sizeof(g_apxDataVec) / sizeof(*g_apxDataVec); i++) {
const uint64_t id = g_apxDataVec[i].paramId;
Data d;
d.paramId.set_id(id);
d.code.assign(g_apxDataVec[i].code, g_apxDataVec[i].code + g_apxDataVec[i].codeSize);
dataMapExpected[id] = d;
}
CYBOZU_TEST_EQUAL(dataMap.size(), dataMapExpected.size());
for (DataMap::const_iterator it = dataMapExpected.begin(); it != dataMapExpected.end(); ++it) {
const uint64_t id = it->first;
DataMap::const_iterator it2 = dataMap.find(id);
CYBOZU_TEST_ASSERT(it2 != dataMap.end());
const Data& d = it2->second;
const Data& dExpected = it->second;
CYBOZU_TEST_EQUAL(d.code.size(), dExpected.code.size());
CYBOZU_TEST_EQUAL_ARRAY(d.code.data(), dExpected.code.data(), d.code.size());
}
#endif
}
#ifdef DUMP
int main()
{
stackFrameTest();
apxStackFrameTest();
}
#else
CYBOZU_TEST_AUTO(stackFrame)
{
stackFrameTest();
}
CYBOZU_TEST_AUTO(stackFrameApx)
{
apxStackFrameTest();
}
// rbp must be pushed with pushp (not push) when UsePPX is specified
// so that the pushp/popp pair is matched for the PPX hint
CYBOZU_TEST_AUTO(rbpWithPpx)
{
struct Code : Xbyak::CodeGenerator {
Code()
{
StackFrame sf(this, 0, UseRBP|UsePPX);
}
} c;
const uint8_t tbl[] = {
0xd5, 0x08, 0x55, // pushp rbp
0xd5, 0x08, 0x5d, // popp rbp
0xc3, // ret
};
const size_t n = sizeof(tbl);
CYBOZU_TEST_EQUAL(c.getSize(), n);
CYBOZU_TEST_EQUAL_ARRAY(c.getCode(), tbl, n);
}
CYBOZU_TEST_AUTO(vecFlagError)
{
Xbyak::CodeGenerator code;
CYBOZU_TEST_EXCEPTION(StackFrame(&code, 0, UseSSE(1)|UseAVX(1)), Xbyak::Error);
CYBOZU_TEST_EXCEPTION(StackFrame(&code, 0, UseSSE(17)), Xbyak::Error);
// NoVzeroupper requires UseAVX
CYBOZU_TEST_EXCEPTION(StackFrame(&code, 0, UseSSE(3)|NoVzeroupper), Xbyak::Error);
CYBOZU_TEST_EXCEPTION(StackFrame(&code, 0, NoVzeroupper), Xbyak::Error);
CYBOZU_TEST_EXCEPTION(StackFrame(&code, 0, UseAVX(33)), Xbyak::Error);
CYBOZU_TEST_NO_EXCEPTION(StackFrame(&code, 0, UseSSE(16)));
CYBOZU_TEST_NO_EXCEPTION(StackFrame(&code, 0, UseAVX(32)));
CYBOZU_TEST_NO_EXCEPTION(StackFrame(&code, 0, UseAVX(8)|NoVzeroupper));
}
// rsp must be 16-byte aligned whenever the xmm save area exists (Win64)
CYBOZU_TEST_AUTO(vecAlign)
{
struct AlignCode : Xbyak::CodeGenerator {
AlignCode(int useRegs, int stackSizeByte)
{
StackFrame sf(this, 0, useRegs, stackSizeByte);
mov(eax, esp);
and_(eax, 15);
}
};
#ifdef XBYAK64_WIN
const int expected = 0; // the xmm save area forces 16-byte alignment
#else
const int expected = 8; // no save area on SysV; rsp stays as it is at entry
#endif
AlignCode c1(UseSSE(8), 0);
CYBOZU_TEST_EQUAL(expected, c1.getCode<int (*)()>()());
AlignCode c2(UseSSE(8), 33);
CYBOZU_TEST_EQUAL(0, c2.getCode<int (*)()>()());
if (Xbyak::util::Cpu().has(Xbyak::util::Cpu::tAVX)) {
AlignCode c3(UseAVX(16)|NoVzeroupper, 0);
CYBOZU_TEST_EQUAL(expected, c3.getCode<int (*)()>()());
}
}
#ifdef XBYAK64_WIN
// the callee must restore the values of xmm6-15 which the caller loaded
CYBOZU_TEST_AUTO(vecSaveRestore)
{
using namespace Xbyak;
struct Callee : Xbyak::CodeGenerator {
Callee()
{
StackFrame sf(this, 0, UseSSE(16));
for (int i = 0; i < 16; i++) {
pxor(Xmm(i), Xmm(i));
}
}
} callee;
struct Caller : Xbyak::CodeGenerator {
explicit Caller(const void *f)
{
StackFrame sf(this, 2, UseRSI|UseRDI|UseSSE(16));
mov(rsi, sf.p[0]); // src
mov(rdi, sf.p[1]); // dst
for (int i = 0; i < 10; i++) {
movups(Xmm(6 + i), ptr[rsi + i * 16]);
}
mov(rax, size_t(f));
call(rax);
for (int i = 0; i < 10; i++) {
movups(ptr[rdi + i * 16], Xmm(6 + i));
}
}
} caller(callee.getCode<const void*>());
uint32_t src[40], dst[40];
for (int i = 0; i < 40; i++) {
src[i] = uint32_t(i * 0x01010101 + 0x12345678);
dst[i] = 0;
}
caller.getCode<void (*)(const uint32_t*, uint32_t*)>()(src, dst);
CYBOZU_TEST_EQUAL_ARRAY(src, dst, 40);
}
#endif
#endif // DUMP