- backend update from GZDoom.

Mainly quaternion math and sound system cleanup.
This commit is contained in:
Christoph Oelckers 2022-11-24 16:56:46 +01:00
parent f1bfaac301
commit 2ffdf3d0e1
25 changed files with 919 additions and 259 deletions

View file

@ -1299,7 +1299,7 @@ FxExpression *FxStringCast::Resolve(FCompileContext &ctx)
if (basex->isConstant())
{
ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
FxExpression *x = new FxConstant(soundEngine->GetSoundName(constval.GetInt()), ScriptPosition);
FxExpression *x = new FxConstant(soundEngine->GetSoundName(FSoundID::fromInt(constval.GetInt())), ScriptPosition);
delete this;
return x;
}
@ -1475,7 +1475,7 @@ FxExpression *FxSoundCast::Resolve(FCompileContext &ctx)
if (basex->isConstant())
{
ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
FxExpression *x = new FxConstant(FSoundID(constval.GetString()), ScriptPosition);
FxExpression *x = new FxConstant(S_FindSound(constval.GetString()), ScriptPosition);
delete this;
return x;
}
@ -3093,7 +3093,14 @@ FxExpression *FxMulDiv::Resolve(FCompileContext& ctx)
[[fallthrough]];
case '*':
if ((left->IsVector() || left->IsQuaternion()) && right->IsNumeric())
if (Operator == '*' && left->IsQuaternion() && (right->IsVector3() || right->IsQuaternion()))
{
// quat * vec3
// quat * quat
ValueType = right->ValueType;
break;
}
else if ((left->IsVector() || left->IsQuaternion()) && right->IsNumeric())
{
if (right->IsInteger())
{
@ -3208,10 +3215,26 @@ ExpEmit FxMulDiv::Emit(VMFunctionBuilder *build)
ExpEmit op1 = left->Emit(build);
ExpEmit op2 = right->Emit(build);
if (IsVector() || IsQuaternion())
if (Operator == '*' && left->IsQuaternion() && right->IsQuaternion())
{
op1.Free(build);
op2.Free(build);
ExpEmit to(build, ValueType->GetRegType(), ValueType->GetRegCount());
build->Emit(OP_MULQQ_RR, to.RegNum, op1.RegNum, op2.RegNum);
return to;
}
else if (Operator == '*' && left->IsQuaternion() && right->IsVector3())
{
op1.Free(build);
op2.Free(build);
ExpEmit to(build, ValueType->GetRegType(), ValueType->GetRegCount());
build->Emit(OP_MULQV3_RR, to.RegNum, op1.RegNum, op2.RegNum);
return to;
}
else if (IsVector() || IsQuaternion())
{
assert(Operator != '%');
if (right->IsVector())
if (left->IsFloat())
{
std::swap(op1, op2);
}
@ -4378,7 +4401,7 @@ ExpEmit FxConcat::Emit(VMFunctionBuilder *build)
build->Emit(op2.RegType == REGT_INT ? OP_LK : op2.RegType == REGT_FLOAT ? OP_LKF : OP_LKP, nonconst.RegNum, op2.RegNum);
op2 = nonconst;
}
if (op1.RegType == REGT_FLOAT) cast = op1.RegCount == 1 ? CAST_F2S : op1.RegCount == 2 ? CAST_V22S : op1.RegCount == 3 ? CAST_V32S : CAST_V42S;
if (op2.RegType == REGT_FLOAT) cast = op2.RegCount == 1 ? CAST_F2S : op2.RegCount == 2 ? CAST_V22S : op2.RegCount == 3 ? CAST_V32S : CAST_V42S;
else if (right->ValueType == TypeUInt32) cast = CAST_U2S;
else if (right->ValueType == TypeName) cast = CAST_N2S;
else if (right->ValueType == TypeSound) cast = CAST_So2S;
@ -8206,6 +8229,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
// [ZZ] substitute ccls for String internal type.
if (id == NAME_String) ccls = TypeStringStruct;
else if (id == NAME_Quat || id == NAME_FQuat) ccls = TypeQuaternionStruct;
else ccls = FindContainerType(id, ctx);
if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true;
}
@ -8385,9 +8409,9 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
delete this;
return x->Resolve(ctx);
}
Self->ValueType = TypeQuaternionStruct;
}
else if (Self->ValueType == TypeString)
{
if (MethodName == NAME_Length) // This is an intrinsic because a dedicated opcode exists for it.

View file

@ -344,7 +344,7 @@ public:
bool IsVector2() const { return ValueType == TypeVector2 || ValueType == TypeFVector2; };
bool IsVector3() const { return ValueType == TypeVector3 || ValueType == TypeFVector3; };
bool IsVector4() const { return ValueType == TypeVector4 || ValueType == TypeFVector4; };
bool IsQuaternion() const { return ValueType == TypeQuaternion || ValueType == TypeFQuaternion; };
bool IsQuaternion() const { return ValueType == TypeQuaternion || ValueType == TypeFQuaternion || ValueType == TypeQuaternionStruct; };
bool IsBoolCompat() const { return ValueType->isScalar(); }
bool IsObject() const { return ValueType->isObjectPointer(); }
bool IsArray() const { return ValueType->isArray() || (ValueType->isPointer() && ValueType->toPointer()->PointedType->isArray()); }
@ -450,7 +450,7 @@ public:
FxConstant(FSoundID val, const FScriptPosition &pos) : FxExpression(EFX_Constant, pos)
{
ValueType = value.Type = TypeSound;
value.Int = val;
value.Int = val.index();
isresolved = true;
}

View file

@ -69,6 +69,7 @@ PStruct* TypeFVector4;
PStruct* TypeFQuaternion;
PStruct *TypeColorStruct;
PStruct *TypeStringStruct;
PStruct* TypeQuaternionStruct;
PPointer *TypeNullPtr;
PPointer *TypeVoidPtr;
@ -316,6 +317,7 @@ void PType::StaticInit()
TypeVoidPtr = NewPointer(TypeVoid, false);
TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value.
TypeStringStruct = NewStruct("Stringstruct", nullptr, true);
TypeQuaternionStruct = NewStruct("QuatStruct", nullptr, true);
TypeFont = NewPointer(NewStruct("Font", nullptr, true));
#ifdef __BIG_ENDIAN__
TypeColorStruct->AddField(NAME_a, TypeUInt8);
@ -1349,7 +1351,7 @@ bool PSound::ReadValue(FSerializer &ar, const char *key, void *addr) const
}
else
{
*(FSoundID *)addr = FSoundID(cptr);
*(FSoundID *)addr = S_FindSound(cptr);
return true;
}
}

View file

@ -623,6 +623,7 @@ extern PStruct* TypeQuaternion;
extern PStruct* TypeFQuaternion;
extern PStruct *TypeColorStruct;
extern PStruct *TypeStringStruct;
extern PStruct* TypeQuaternionStruct;
extern PStatePointer *TypeState;
extern PPointer *TypeFont;
extern PStateLabel *TypeStateLabel;

View file

@ -1129,3 +1129,136 @@ DEFINE_FIELD(DStatusBarCore, drawClip);
DEFINE_FIELD(DStatusBarCore, fullscreenOffsets);
DEFINE_FIELD(DStatusBarCore, defaultScale);
DEFINE_FIELD(DHUDFont, mFont);
//
// Quaternion
void QuatFromAngles(double yaw, double pitch, double roll, DQuaternion* pquat)
{
*pquat = DQuaternion::FromAngles(DAngle::fromDeg(yaw), DAngle::fromDeg(pitch), DAngle::fromDeg(roll));
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, FromAngles, QuatFromAngles)
{
PARAM_PROLOGUE;
PARAM_FLOAT(yaw);
PARAM_FLOAT(pitch);
PARAM_FLOAT(roll);
DQuaternion quat;
QuatFromAngles(yaw, pitch, roll, &quat);
ACTION_RETURN_QUAT(quat);
}
void QuatAxisAngle(double x, double y, double z, double angleDeg, DQuaternion* pquat)
{
auto axis = DVector3(x, y, z);
auto angle = DAngle::fromDeg(angleDeg);
*pquat = DQuaternion::AxisAngle(axis, angle);
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, AxisAngle, QuatAxisAngle)
{
PARAM_PROLOGUE;
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_FLOAT(z);
PARAM_FLOAT(angle);
DQuaternion quat;
QuatAxisAngle(x, y, z, angle, &quat);
ACTION_RETURN_QUAT(quat);
}
void QuatNLerp(
double ax, double ay, double az, double aw,
double bx, double by, double bz, double bw,
double t,
DQuaternion* pquat
)
{
auto from = DQuaternion { ax, ay, az, aw };
auto to = DQuaternion { bx, by, bz, bw };
*pquat = DQuaternion::NLerp(from, to, t);
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, NLerp, QuatNLerp)
{
PARAM_PROLOGUE;
PARAM_FLOAT(ax);
PARAM_FLOAT(ay);
PARAM_FLOAT(az);
PARAM_FLOAT(aw);
PARAM_FLOAT(bx);
PARAM_FLOAT(by);
PARAM_FLOAT(bz);
PARAM_FLOAT(bw);
PARAM_FLOAT(t);
DQuaternion quat;
QuatNLerp(ax, ay, az, aw, bx, by, bz, bw, t, &quat);
ACTION_RETURN_QUAT(quat);
}
void QuatSLerp(
double ax, double ay, double az, double aw,
double bx, double by, double bz, double bw,
double t,
DQuaternion* pquat
)
{
auto from = DQuaternion { ax, ay, az, aw };
auto to = DQuaternion { bx, by, bz, bw };
*pquat = DQuaternion::SLerp(from, to, t);
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, SLerp, QuatSLerp)
{
PARAM_PROLOGUE;
PARAM_FLOAT(ax);
PARAM_FLOAT(ay);
PARAM_FLOAT(az);
PARAM_FLOAT(aw);
PARAM_FLOAT(bx);
PARAM_FLOAT(by);
PARAM_FLOAT(bz);
PARAM_FLOAT(bw);
PARAM_FLOAT(t);
DQuaternion quat;
QuatSLerp(ax, ay, az, aw, bx, by, bz, bw, t, &quat);
ACTION_RETURN_QUAT(quat);
}
void QuatConjugate(
double x, double y, double z, double w,
DQuaternion* pquat
)
{
*pquat = DQuaternion(x, y, z, w).Conjugate();
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, Conjugate, QuatConjugate)
{
PARAM_SELF_STRUCT_PROLOGUE(DQuaternion);
DQuaternion quat;
QuatConjugate(self->X, self->Y, self->Z, self->W, &quat);
ACTION_RETURN_QUAT(quat);
}
void QuatInverse(
double x, double y, double z, double w,
DQuaternion* pquat
)
{
*pquat = DQuaternion(x, y, z, w).Inverse();
}
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, Inverse, QuatInverse)
{
PARAM_SELF_STRUCT_PROLOGUE(DQuaternion);
DQuaternion quat;
QuatInverse(self->X, self->Y, self->Z, self->W, &quat);
ACTION_RETURN_QUAT(quat);
}

View file

@ -1606,6 +1606,62 @@ void JitCompiler::EmitEQV4_K()
I_Error("EQV4_K is not used.");
}
// Quaternion ops
void FuncMULQQ(void *result, double ax, double ay, double az, double aw, double bx, double by, double bz, double bw)
{
*reinterpret_cast<DQuaternion*>(result) = DQuaternion(ax, ay, az, aw) * DQuaternion(bx, by, bz, bw);
}
void FuncMULQV3(void *result, double ax, double ay, double az, double aw, double bx, double by, double bz)
{
*reinterpret_cast<DVector3*>(result) = DQuaternion(ax, ay, az, aw) * DVector3(bx, by, bz);
}
void JitCompiler::EmitMULQQ_RR()
{
auto stack = GetTemporaryVectorStackStorage();
auto tmp = newTempIntPtr();
cc.lea(tmp, stack);
auto call = CreateCall<void, void*, double, double, double, double, double, double, double, double>(FuncMULQQ);
call->setArg(0, tmp);
call->setArg(1, regF[B + 0]);
call->setArg(2, regF[B + 1]);
call->setArg(3, regF[B + 2]);
call->setArg(4, regF[B + 3]);
call->setArg(5, regF[C + 0]);
call->setArg(6, regF[C + 1]);
call->setArg(7, regF[C + 2]);
call->setArg(8, regF[C + 3]);
cc.movsd(regF[A + 0], asmjit::x86::qword_ptr(tmp, 0));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
cc.movsd(regF[A + 3], asmjit::x86::qword_ptr(tmp, 24));
}
void JitCompiler::EmitMULQV3_RR()
{
auto stack = GetTemporaryVectorStackStorage();
auto tmp = newTempIntPtr();
cc.lea(tmp, stack);
auto call = CreateCall<void, void*, double, double, double, double, double, double, double>(FuncMULQV3);
call->setArg(0, tmp);
call->setArg(1, regF[B + 0]);
call->setArg(2, regF[B + 1]);
call->setArg(3, regF[B + 2]);
call->setArg(4, regF[B + 3]);
call->setArg(5, regF[C + 0]);
call->setArg(6, regF[C + 1]);
call->setArg(7, regF[C + 2]);
cc.movsd(regF[A + 0], asmjit::x86::qword_ptr(tmp, 0));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
}
/////////////////////////////////////////////////////////////////////////////
// Pointer math.

View file

@ -60,8 +60,8 @@ static int CastS2N(FString *b) { return b->Len() == 0 ? NAME_None : FName(*b).Ge
static void CastN2S(FString *a, int b) { FName name = FName(ENamedName(b)); *a = name.IsValidName() ? name.GetChars() : ""; }
static int CastS2Co(FString *b) { return V_GetColor(*b); }
static void CastCo2S(FString *a, int b) { PalEntry c(b); a->Format("%02x %02x %02x", c.r, c.g, c.b); }
static int CastS2So(FString *b) { return FSoundID(*b); }
static void CastSo2S(FString* a, int b) { *a = soundEngine->GetSoundName(b); }
static int CastS2So(FString *b) { return S_FindSound(*b).index(); }
static void CastSo2S(FString* a, int b) { *a = soundEngine->GetSoundName(FSoundID::fromInt(b)); }
static void CastSID2S(FString* a, unsigned int b) { VM_CastSpriteIDToString(a, b); }
static void CastTID2S(FString *a, int b) { auto tex = TexMan.GetGameTexture(*(FTextureID*)&b); *a = (tex == nullptr) ? "(null)" : tex->GetName().GetChars(); }

View file

@ -186,7 +186,13 @@ private:
asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6)>(func))), asmjit::FuncSignature6<RetType, P1, P2, P3, P4, P5, P6>()); }
template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>
asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6, P7)>(func))), asmjit::FuncSignature7<RetType, P1, P2, P3, P4, P5, P6, P7>()); }
asmjit::CCFuncCall* CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6, P7)>(func))), asmjit::FuncSignature7<RetType, P1, P2, P3, P4, P5, P6, P7>()); }
template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7, typename P8>
asmjit::CCFuncCall* CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6, P7, P8)>(func))), asmjit::FuncSignature8<RetType, P1, P2, P3, P4, P5, P6, P7, P8>()); }
template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7, typename P8, typename P9>
asmjit::CCFuncCall* CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6, P7, P8, P9)>(func))), asmjit::FuncSignature9<RetType, P1, P2, P3, P4, P5, P6, P7, P8, P9>()); }
FString regname;
size_t tmpPosInt32, tmpPosInt64, tmpPosIntPtr, tmpPosXmmSd, tmpPosXmmSs, tmpPosXmmPd, resultPosInt32, resultPosIntPtr, resultPosXmmSd;
@ -305,6 +311,19 @@ private:
TArray<OpcodeLabel> labels;
// Get temporary storage enough for DVector4 which is required by operation such as MULQQ and MULQV3
bool vectorStackAllocated = false;
asmjit::X86Mem vectorStack;
asmjit::X86Mem GetTemporaryVectorStackStorage()
{
if (!vectorStackAllocated)
{
vectorStack = cc.newStack(sizeof(DVector4), alignof(DVector4), "tmpDVector4");
vectorStackAllocated = true;
}
return vectorStack;
}
const VMOP *pc;
VM_UBYTE op;
};

View file

@ -39,6 +39,7 @@
#include "autosegs.h"
#include "zstring.h"
#include "vectors.h"
#include "quaternion.h"
#include "cmdlib.h"
#include "engineerrors.h"
#include "memarena.h"
@ -147,6 +148,14 @@ struct VMReturn
((double *)Location)[2] = val[2];
((double *)Location)[3] = val[3];
}
void SetQuaternion(const DQuaternion &val)
{
assert(RegType == (REGT_FLOAT | REGT_MULTIREG4));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
((double *)Location)[3] = val[3];
}
void SetVector(const double val[3])
{
assert(RegType == (REGT_FLOAT|REGT_MULTIREG3));
@ -530,7 +539,7 @@ bool AssertObject(void * ob);
#define PARAM_UINT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); unsigned x = param[p].i;
#define PARAM_BOOL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); bool x = !!param[p].i;
#define PARAM_NAME_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FName x = ENamedName(param[p].i);
#define PARAM_SOUND_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FSoundID x = param[p].i;
#define PARAM_SOUND_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FSoundID x = FSoundID::fromInt(param[p].i);
#define PARAM_COLOR_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); PalEntry x = param[p].i;
#define PARAM_FLOAT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); double x = param[p].f;
#define PARAM_ANGLE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); DAngle x = DAngle::fromDeg(param[p].f);
@ -748,6 +757,8 @@ class AActor;
#define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC2(v) do { DVector2 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector2(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC3(v) do { DVector3 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC4(v) do { DVector4 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector4(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_QUAT(v) do { DQuaternion u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetQuaternion(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v)
#define ACTION_RETURN_STRING(v) do { FString u = v; if (numret > 0) { assert(ret != NULL); ret->SetString(u); return 1; } return 0; } while(0)

View file

@ -1887,7 +1887,22 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
ASSERTF(B+3); ASSERTKF(C+3);
fcp = &konstf[C];
goto Do_EQV4;
OP(MULQV3_RR):
ASSERTF(a + 2); ASSERTF(B + 3); ASSERTF(C + 2);
{
const DQuaternion& q = reinterpret_cast<DQuaternion&>(reg.f[B]);
const DVector3& v = reinterpret_cast<DVector3&>(reg.f[C]);
reinterpret_cast<DVector3&>(reg.f[A]) = q * v;
}
NEXTOP;
OP(MULQQ_RR):
ASSERTF(a + 3); ASSERTF(B + 3); ASSERTF(C + 3);
{
const DQuaternion& q1 = reinterpret_cast<DQuaternion&>(reg.f[B]);
const DQuaternion& q2 = reinterpret_cast<DQuaternion&>(reg.f[C]);
reinterpret_cast<DQuaternion&>(reg.f[A]) = q1 * q2;
}
NEXTOP;
OP(ADDA_RR):
ASSERTA(a); ASSERTA(B); ASSERTD(C);
c = reg.d[C];
@ -2107,12 +2122,12 @@ static void DoCast(const VMRegisters &reg, const VMFrame *f, int a, int b, int c
case CAST_S2So:
ASSERTD(a); ASSERTS(b);
reg.d[a] = FSoundID(reg.s[b]);
reg.d[a] = S_FindSound(reg.s[b]).index();
break;
case CAST_So2S:
ASSERTS(a); ASSERTD(b);
reg.s[a] = soundEngine->GetSoundName(reg.d[b]);
reg.s[a] = soundEngine->GetSoundName(FSoundID::fromInt(reg.d[b]));
break;
case CAST_SID2S:

View file

@ -278,6 +278,10 @@ xx(LENV4, lenv4, RFRV, NOP, 0, 0) // fA = vB.Length
xx(EQV4_R, beqv4, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 33)
xx(EQV4_K, beqv4, CVRK, NOP, 0, 0) // this will never be used.
// Quaternion math
xx(MULQQ_RR, mulqq, RVRVRV, NOP, 0, 0) // qA = qB * qC
xx(MULQV3_RR, mulqv3, RVRVRV, NOP, 0, 0) // qA = qB * vC
// Pointer math.
xx(ADDA_RR, add, RPRPRI, NOP, 0, 0) // pA = pB + dkC
xx(ADDA_RK, add, RPRPKI, ADDA_RR,4, REGT_INT)