// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once #include "Luau/IrBuilder.h" static const char* kUserdataRunTypes[] = {"extra", "color", "vec2", "mat3", nullptr}; constexpr uint8_t kUserdataExtra = 0; constexpr uint8_t kUserdataColor = 1; constexpr uint8_t kUserdataVec2 = 2; constexpr uint8_t kUserdataMat3 = 3; // Userdata tags can be different from userdata bytecode type indices constexpr uint8_t kTagVec2 = 12; struct Vec2 { float x; float y; }; inline bool compareMemberName(const char* member, size_t memberLength, const char* str) { return memberLength == strlen(str) && strcmp(member, str) == 0; } inline uint8_t typeToUserdataIndex(uint8_t type) { // Underflow will push the type into a value that is not comparable to any kUserdata* constants return type - LBC_TYPE_TAGGED_USERDATA_BASE; } inline uint8_t userdataIndexToType(uint8_t userdataIndex) { return LBC_TYPE_TAGGED_USERDATA_BASE + userdataIndex; } inline uint8_t vectorAccessBytecodeType(const char* member, size_t memberLength) { using namespace Luau::CodeGen; if (compareMemberName(member, memberLength, "Magnitude")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Unit")) return LBC_TYPE_VECTOR; return LBC_TYPE_ANY; } inline bool vectorAccess(Luau::CodeGen::IrBuilder& build, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos) { using namespace Luau::CodeGen; if (compareMemberName(member, memberLength, "Magnitude")) { IrOp x = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(0)); IrOp y = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(4)); IrOp z = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(8)); IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z); IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(resultReg), mag); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TNUMBER)); return true; } if (compareMemberName(member, memberLength, "Unit")) { IrOp x = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(0)); IrOp y = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(4)); IrOp z = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(8)); IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z); IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag); IrOp xr = build.inst(IrCmd::MUL_NUM, x, inv); IrOp yr = build.inst(IrCmd::MUL_NUM, y, inv); IrOp zr = build.inst(IrCmd::MUL_NUM, z, inv); build.inst(IrCmd::STORE_VECTOR, build.vmReg(resultReg), xr, yr, zr); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TVECTOR)); return true; } return false; } inline uint8_t vectorNamecallBytecodeType(const char* member, size_t memberLength) { if (compareMemberName(member, memberLength, "Dot")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Cross")) return LBC_TYPE_VECTOR; return LBC_TYPE_ANY; } inline bool vectorNamecall( Luau::CodeGen::IrBuilder& build, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos) { using namespace Luau::CodeGen; if (compareMemberName(member, memberLength, "Dot") && params == 2 && results <= 1) { build.loadAndCheckTag(build.vmReg(argResReg + 2), LUA_TVECTOR, build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(0)); IrOp x2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(0)); IrOp xx = build.inst(IrCmd::MUL_NUM, x1, x2); IrOp y1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(4)); IrOp y2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(4)); IrOp yy = build.inst(IrCmd::MUL_NUM, y1, y2); IrOp z1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(8)); IrOp z2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(8)); IrOp zz = build.inst(IrCmd::MUL_NUM, z1, z2); IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, xx, yy), zz); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(argResReg), sum); build.inst(IrCmd::STORE_TAG, build.vmReg(argResReg), build.constTag(LUA_TNUMBER)); // If the function is called in multi-return context, stack has to be adjusted if (results == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(argResReg), build.constInt(1)); return true; } if (compareMemberName(member, memberLength, "Cross") && params == 2 && results <= 1) { build.loadAndCheckTag(build.vmReg(argResReg + 2), LUA_TVECTOR, build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(0)); IrOp x2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(0)); IrOp y1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(4)); IrOp y2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(4)); IrOp z1 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(sourceReg), build.constInt(8)); IrOp z2 = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(argResReg + 2), build.constInt(8)); IrOp y1z2 = build.inst(IrCmd::MUL_NUM, y1, z2); IrOp z1y2 = build.inst(IrCmd::MUL_NUM, z1, y2); IrOp xr = build.inst(IrCmd::SUB_NUM, y1z2, z1y2); IrOp z1x2 = build.inst(IrCmd::MUL_NUM, z1, x2); IrOp x1z2 = build.inst(IrCmd::MUL_NUM, x1, z2); IrOp yr = build.inst(IrCmd::SUB_NUM, z1x2, x1z2); IrOp x1y2 = build.inst(IrCmd::MUL_NUM, x1, y2); IrOp y1x2 = build.inst(IrCmd::MUL_NUM, y1, x2); IrOp zr = build.inst(IrCmd::SUB_NUM, x1y2, y1x2); build.inst(IrCmd::STORE_VECTOR, build.vmReg(argResReg), xr, yr, zr); build.inst(IrCmd::STORE_TAG, build.vmReg(argResReg), build.constTag(LUA_TVECTOR)); // If the function is called in multi-return context, stack has to be adjusted if (results == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(argResReg), build.constInt(1)); return true; } return false; } inline uint8_t userdataAccessBytecodeType(uint8_t type, const char* member, size_t memberLength) { switch (typeToUserdataIndex(type)) { case kUserdataColor: if (compareMemberName(member, memberLength, "R")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "G")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "B")) return LBC_TYPE_NUMBER; break; case kUserdataVec2: if (compareMemberName(member, memberLength, "X")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Y")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Magnitude")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Unit")) return userdataIndexToType(kUserdataVec2); break; case kUserdataMat3: if (compareMemberName(member, memberLength, "Row1")) return LBC_TYPE_VECTOR; if (compareMemberName(member, memberLength, "Row2")) return LBC_TYPE_VECTOR; if (compareMemberName(member, memberLength, "Row3")) return LBC_TYPE_VECTOR; break; } return LBC_TYPE_ANY; } inline bool userdataAccess( Luau::CodeGen::IrBuilder& build, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos) { using namespace Luau::CodeGen; switch (typeToUserdataIndex(type)) { case kUserdataColor: break; case kUserdataVec2: if (compareMemberName(member, memberLength, "X")) { IrOp udata = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp value = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(resultReg), value); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TNUMBER)); return true; } if (compareMemberName(member, memberLength, "Y")) { IrOp udata = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp value = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(resultReg), value); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TNUMBER)); return true; } if (compareMemberName(member, memberLength, "Magnitude")) { IrOp udata = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp y = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); IrOp sum = build.inst(IrCmd::ADD_NUM, x2, y2); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(resultReg), mag); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TNUMBER)); return true; } if (compareMemberName(member, memberLength, "Unit")) { IrOp udata = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp y = build.inst(IrCmd::BUFFER_READF32, udata, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); IrOp sum = build.inst(IrCmd::ADD_NUM, x2, y2); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag); IrOp xr = build.inst(IrCmd::MUL_NUM, x, inv); IrOp yr = build.inst(IrCmd::MUL_NUM, y, inv); build.inst(IrCmd::CHECK_GC); IrOp udatar = build.inst(IrCmd::NEW_USERDATA, build.constInt(sizeof(Vec2)), build.constInt(kTagVec2)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, x)), xr, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, y)), yr, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_POINTER, build.vmReg(resultReg), udatar); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TUSERDATA)); return true; } break; case kUserdataMat3: break; } return false; } inline uint8_t userdataMetamethodBytecodeType(uint8_t lhsTy, uint8_t rhsTy, Luau::CodeGen::HostMetamethod method) { switch (method) { case Luau::CodeGen::HostMetamethod::Add: case Luau::CodeGen::HostMetamethod::Sub: case Luau::CodeGen::HostMetamethod::Mul: case Luau::CodeGen::HostMetamethod::Div: if (typeToUserdataIndex(lhsTy) == kUserdataVec2 || typeToUserdataIndex(rhsTy) == kUserdataVec2) return userdataIndexToType(kUserdataVec2); break; case Luau::CodeGen::HostMetamethod::Minus: if (typeToUserdataIndex(lhsTy) == kUserdataVec2) return userdataIndexToType(kUserdataVec2); break; default: break; } return LBC_TYPE_ANY; } inline bool userdataMetamethod(Luau::CodeGen::IrBuilder& build, uint8_t lhsTy, uint8_t rhsTy, int resultReg, Luau::CodeGen::IrOp lhs, Luau::CodeGen::IrOp rhs, Luau::CodeGen::HostMetamethod method, int pcpos) { using namespace Luau::CodeGen; switch (method) { case Luau::CodeGen::HostMetamethod::Add: if (typeToUserdataIndex(lhsTy) == kUserdataVec2 && typeToUserdataIndex(rhsTy) == kUserdataVec2) { build.loadAndCheckTag(lhs, LUA_TUSERDATA, build.vmExit(pcpos)); build.loadAndCheckTag(rhs, LUA_TUSERDATA, build.vmExit(pcpos)); IrOp udata1 = build.inst(IrCmd::LOAD_POINTER, lhs); build.inst(IrCmd::CHECK_USERDATA_TAG, udata1, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp udata2 = build.inst(IrCmd::LOAD_POINTER, rhs); build.inst(IrCmd::CHECK_USERDATA_TAG, udata2, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp mx = build.inst(IrCmd::ADD_NUM, x1, x2); IrOp y1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp y2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp my = build.inst(IrCmd::ADD_NUM, y1, y2); build.inst(IrCmd::CHECK_GC); IrOp udatar = build.inst(IrCmd::NEW_USERDATA, build.constInt(sizeof(Vec2)), build.constInt(kTagVec2)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, x)), mx, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, y)), my, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_POINTER, build.vmReg(resultReg), udatar); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TUSERDATA)); return true; } break; case Luau::CodeGen::HostMetamethod::Mul: if (typeToUserdataIndex(lhsTy) == kUserdataVec2 && typeToUserdataIndex(rhsTy) == kUserdataVec2) { build.loadAndCheckTag(lhs, LUA_TUSERDATA, build.vmExit(pcpos)); build.loadAndCheckTag(rhs, LUA_TUSERDATA, build.vmExit(pcpos)); IrOp udata1 = build.inst(IrCmd::LOAD_POINTER, lhs); build.inst(IrCmd::CHECK_USERDATA_TAG, udata1, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp udata2 = build.inst(IrCmd::LOAD_POINTER, rhs); build.inst(IrCmd::CHECK_USERDATA_TAG, udata2, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp mx = build.inst(IrCmd::MUL_NUM, x1, x2); IrOp y1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp y2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp my = build.inst(IrCmd::MUL_NUM, y1, y2); build.inst(IrCmd::CHECK_GC); IrOp udatar = build.inst(IrCmd::NEW_USERDATA, build.constInt(sizeof(Vec2)), build.constInt(kTagVec2)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, x)), mx, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, y)), my, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_POINTER, build.vmReg(resultReg), udatar); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TUSERDATA)); return true; } break; case Luau::CodeGen::HostMetamethod::Minus: if (typeToUserdataIndex(lhsTy) == kUserdataVec2) { build.loadAndCheckTag(lhs, LUA_TUSERDATA, build.vmExit(pcpos)); IrOp udata1 = build.inst(IrCmd::LOAD_POINTER, lhs); build.inst(IrCmd::CHECK_USERDATA_TAG, udata1, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp y = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp mx = build.inst(IrCmd::UNM_NUM, x); IrOp my = build.inst(IrCmd::UNM_NUM, y); build.inst(IrCmd::CHECK_GC); IrOp udatar = build.inst(IrCmd::NEW_USERDATA, build.constInt(sizeof(Vec2)), build.constInt(kTagVec2)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, x)), mx, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, y)), my, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_POINTER, build.vmReg(resultReg), udatar); build.inst(IrCmd::STORE_TAG, build.vmReg(resultReg), build.constTag(LUA_TUSERDATA)); return true; } break; default: break; } return false; } inline uint8_t userdataNamecallBytecodeType(uint8_t type, const char* member, size_t memberLength) { switch (typeToUserdataIndex(type)) { case kUserdataColor: break; case kUserdataVec2: if (compareMemberName(member, memberLength, "Dot")) return LBC_TYPE_NUMBER; if (compareMemberName(member, memberLength, "Min")) return userdataIndexToType(kUserdataVec2); break; case kUserdataMat3: break; } return LBC_TYPE_ANY; } inline bool userdataNamecall(Luau::CodeGen::IrBuilder& build, uint8_t type, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos) { using namespace Luau::CodeGen; switch (typeToUserdataIndex(type)) { case kUserdataColor: break; case kUserdataVec2: if (compareMemberName(member, memberLength, "Dot")) { IrOp udata1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata1, build.constInt(kTagVec2), build.vmExit(pcpos)); build.loadAndCheckTag(build.vmReg(argResReg + 2), LUA_TUSERDATA, build.vmExit(pcpos)); IrOp udata2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(argResReg + 2)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata2, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp xx = build.inst(IrCmd::MUL_NUM, x1, x2); IrOp y1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp y2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp yy = build.inst(IrCmd::MUL_NUM, y1, y2); IrOp sum = build.inst(IrCmd::ADD_NUM, xx, yy); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(argResReg), sum); build.inst(IrCmd::STORE_TAG, build.vmReg(argResReg), build.constTag(LUA_TNUMBER)); // If the function is called in multi-return context, stack has to be adjusted if (results == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(argResReg), build.constInt(1)); return true; } if (compareMemberName(member, memberLength, "Min")) { IrOp udata1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(sourceReg)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata1, build.constInt(kTagVec2), build.vmExit(pcpos)); build.loadAndCheckTag(build.vmReg(argResReg + 2), LUA_TUSERDATA, build.vmExit(pcpos)); IrOp udata2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(argResReg + 2)); build.inst(IrCmd::CHECK_USERDATA_TAG, udata2, build.constInt(kTagVec2), build.vmExit(pcpos)); IrOp x1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp x2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, x)), build.constTag(LUA_TUSERDATA)); IrOp mx = build.inst(IrCmd::MIN_NUM, x1, x2); IrOp y1 = build.inst(IrCmd::BUFFER_READF32, udata1, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp y2 = build.inst(IrCmd::BUFFER_READF32, udata2, build.constInt(offsetof(Vec2, y)), build.constTag(LUA_TUSERDATA)); IrOp my = build.inst(IrCmd::MIN_NUM, y1, y2); build.inst(IrCmd::CHECK_GC); IrOp udatar = build.inst(IrCmd::NEW_USERDATA, build.constInt(sizeof(Vec2)), build.constInt(kTagVec2)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, x)), mx, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::BUFFER_WRITEF32, udatar, build.constInt(offsetof(Vec2, y)), my, build.constTag(LUA_TUSERDATA)); build.inst(IrCmd::STORE_POINTER, build.vmReg(argResReg), udatar); build.inst(IrCmd::STORE_TAG, build.vmReg(argResReg), build.constTag(LUA_TUSERDATA)); // If the function is called in multi-return context, stack has to be adjusted if (results == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(argResReg), build.constInt(1)); return true; } break; case kUserdataMat3: break; } return false; }