| // Copyright 2024 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "src/tint/lang/spirv/reader/lower/builtins.h" | |
| #include "src/tint/lang/core/ir/builder.h" | |
| #include "src/tint/lang/core/ir/module.h" | |
| #include "src/tint/lang/core/ir/validator.h" | |
| #include "src/tint/lang/core/type/builtin_structs.h" | |
| #include "src/tint/lang/spirv/ir/builtin_call.h" | |
| namespace tint::spirv::reader::lower { | |
| namespace { | |
| using namespace tint::core::fluent_types; // NOLINT | |
| using namespace tint::core::number_suffixes; // NOLINT | |
| /// PIMPL state for the transform. | |
| struct State { | |
| /// The IR module. | |
| core::ir::Module& ir; | |
| /// The IR builder. | |
| core::ir::Builder b{ir}; | |
| /// The type manager. | |
| core::type::Manager& ty{ir.Types()}; | |
| /// Process the module. | |
| void Process() { | |
| Vector<spirv::ir::BuiltinCall*, 4> builtin_worklist; | |
| for (auto* inst : ir.Instructions()) { | |
| if (auto* builtin = inst->As<spirv::ir::BuiltinCall>()) { | |
| builtin_worklist.Push(builtin); | |
| } | |
| } | |
| // Replace the builtins that we found. | |
| for (auto* builtin : builtin_worklist) { | |
| switch (builtin->Func()) { | |
| case spirv::BuiltinFn::kNormalize: | |
| Normalize(builtin); | |
| break; | |
| case spirv::BuiltinFn::kInverse: | |
| Inverse(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSign: | |
| Sign(builtin); | |
| break; | |
| case spirv::BuiltinFn::kAbs: | |
| Abs(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSMax: | |
| SMax(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSMin: | |
| SMin(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSClamp: | |
| SClamp(builtin); | |
| break; | |
| case spirv::BuiltinFn::kUMax: | |
| UMax(builtin); | |
| break; | |
| case spirv::BuiltinFn::kUMin: | |
| UMin(builtin); | |
| break; | |
| case spirv::BuiltinFn::kUClamp: | |
| UClamp(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFindILsb: | |
| FindILsb(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFindSMsb: | |
| FindSMsb(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFindUMsb: | |
| FindUMsb(builtin); | |
| break; | |
| case spirv::BuiltinFn::kRefract: | |
| Refract(builtin); | |
| break; | |
| case spirv::BuiltinFn::kReflect: | |
| Reflect(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFaceForward: | |
| FaceForward(builtin); | |
| break; | |
| case spirv::BuiltinFn::kLdexp: | |
| Ldexp(builtin); | |
| break; | |
| case spirv::BuiltinFn::kModf: | |
| Modf(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFrexp: | |
| Frexp(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitCount: | |
| BitCount(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitFieldInsert: | |
| BitFieldInsert(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitFieldSExtract: | |
| BitFieldSExtract(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitFieldUExtract: | |
| BitFieldUExtract(builtin); | |
| break; | |
| case spirv::BuiltinFn::kAdd: | |
| Add(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSub: | |
| Sub(builtin); | |
| break; | |
| case spirv::BuiltinFn::kMul: | |
| Mul(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSDiv: | |
| SDiv(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSMod: | |
| SMod(builtin); | |
| break; | |
| case spirv::BuiltinFn::kConvertFToS: | |
| ConvertFToS(builtin); | |
| break; | |
| case spirv::BuiltinFn::kConvertSToF: | |
| ConvertSToF(builtin); | |
| break; | |
| case spirv::BuiltinFn::kConvertUToF: | |
| ConvertUToF(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitwiseAnd: | |
| BitwiseAnd(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitwiseOr: | |
| BitwiseOr(builtin); | |
| break; | |
| case spirv::BuiltinFn::kBitwiseXor: | |
| BitwiseXor(builtin); | |
| break; | |
| case spirv::BuiltinFn::kEqual: | |
| Equal(builtin); | |
| break; | |
| case spirv::BuiltinFn::kNotEqual: | |
| NotEqual(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSGreaterThan: | |
| SGreaterThan(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSGreaterThanEqual: | |
| SGreaterThanEqual(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSLessThan: | |
| SLessThan(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSLessThanEqual: | |
| SLessThanEqual(builtin); | |
| break; | |
| case spirv::BuiltinFn::kUGreaterThan: | |
| UGreaterThan(builtin); | |
| break; | |
| case spirv::BuiltinFn::kUGreaterThanEqual: | |
| UGreaterThanEqual(builtin); | |
| break; | |
| case spirv::BuiltinFn::kULessThan: | |
| ULessThan(builtin); | |
| break; | |
| case spirv::BuiltinFn::kULessThanEqual: | |
| ULessThanEqual(builtin); | |
| break; | |
| case spirv::BuiltinFn::kShiftLeftLogical: | |
| ShiftLeftLogical(builtin); | |
| break; | |
| case spirv::BuiltinFn::kShiftRightLogical: | |
| ShiftRightLogical(builtin); | |
| break; | |
| case spirv::BuiltinFn::kShiftRightArithmetic: | |
| ShiftRightArithmetic(builtin); | |
| break; | |
| case spirv::BuiltinFn::kNot: | |
| Not(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSNegate: | |
| SNegate(builtin); | |
| break; | |
| case spirv::BuiltinFn::kFMod: | |
| FMod(builtin); | |
| break; | |
| case spirv::BuiltinFn::kSelect: | |
| Select(builtin); | |
| break; | |
| case spirv::BuiltinFn::kOuterProduct: | |
| OuterProduct(builtin); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformBroadcast: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kSubgroupBroadcast); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformShuffle: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kSubgroupShuffle); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformShuffleXor: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kSubgroupShuffleXor); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformShuffleDown: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kSubgroupShuffleDown); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformShuffleUp: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kSubgroupShuffleUp); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformBroadcastFirst: | |
| GroupNonUniformBroadcastFirst(builtin); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformQuadBroadcast: | |
| GroupNonUniformBuiltin(builtin, core::BuiltinFn::kQuadBroadcast); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformQuadSwap: | |
| GroupNonUniformQuadSwap(builtin); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformSMin: | |
| GroupNonUniformMinMax(builtin, core::BuiltinFn::kSubgroupMin); | |
| break; | |
| case spirv::BuiltinFn::kGroupNonUniformSMax: | |
| GroupNonUniformMinMax(builtin, core::BuiltinFn::kSubgroupMax); | |
| break; | |
| case spirv::BuiltinFn::kAtomicLoad: | |
| case spirv::BuiltinFn::kAtomicStore: | |
| case spirv::BuiltinFn::kAtomicExchange: | |
| case spirv::BuiltinFn::kAtomicCompareExchange: | |
| case spirv::BuiltinFn::kAtomicIAdd: | |
| case spirv::BuiltinFn::kAtomicISub: | |
| case spirv::BuiltinFn::kAtomicSMax: | |
| case spirv::BuiltinFn::kAtomicSMin: | |
| case spirv::BuiltinFn::kAtomicUMax: | |
| case spirv::BuiltinFn::kAtomicUMin: | |
| case spirv::BuiltinFn::kAtomicAnd: | |
| case spirv::BuiltinFn::kAtomicOr: | |
| case spirv::BuiltinFn::kAtomicXor: | |
| case spirv::BuiltinFn::kAtomicIIncrement: | |
| case spirv::BuiltinFn::kAtomicIDecrement: | |
| // Ignore Atomics, they'll be handled by the `Atomics` transform. | |
| break; | |
| case spirv::BuiltinFn::kOpImage: | |
| case spirv::BuiltinFn::kOpSampledImage: | |
| case spirv::BuiltinFn::kImageRead: | |
| case spirv::BuiltinFn::kImageFetch: | |
| case spirv::BuiltinFn::kImageGather: | |
| case spirv::BuiltinFn::kImageDrefGather: | |
| case spirv::BuiltinFn::kImageQueryLevels: | |
| case spirv::BuiltinFn::kImageQuerySamples: | |
| case spirv::BuiltinFn::kImageQuerySize: | |
| case spirv::BuiltinFn::kImageQuerySizeLod: | |
| case spirv::BuiltinFn::kImageSampleExplicitLod: | |
| case spirv::BuiltinFn::kImageSampleImplicitLod: | |
| case spirv::BuiltinFn::kImageSampleProjImplicitLod: | |
| case spirv::BuiltinFn::kImageSampleProjExplicitLod: | |
| case spirv::BuiltinFn::kImageSampleDrefImplicitLod: | |
| case spirv::BuiltinFn::kImageSampleDrefExplicitLod: | |
| case spirv::BuiltinFn::kImageSampleProjDrefImplicitLod: | |
| case spirv::BuiltinFn::kImageSampleProjDrefExplicitLod: | |
| case spirv::BuiltinFn::kImageWrite: | |
| // Ignore image methods, they'll be handled by the `Texture` transform. | |
| break; | |
| default: | |
| TINT_UNREACHABLE() << "unknown spirv builtin: " << builtin->Func(); | |
| } | |
| } | |
| } | |
| void GroupNonUniformMinMax(spirv::ir::BuiltinCall* call, core::BuiltinFn fn) { | |
| auto* value = call->Args()[2]; | |
| auto* orig_type = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| auto* type = orig_type; | |
| if (orig_type->IsUnsignedIntegerScalarOrVector()) { | |
| type = ty.MatchWidth(ty.i32(), type); | |
| value = b.Convert(type, value)->Result(); | |
| } | |
| value = b.Call(type, fn, Vector{value})->Result(); | |
| if (type != orig_type) { | |
| value = b.Convert(call->Result()->Type(), value)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(value); | |
| }); | |
| call->Destroy(); | |
| } | |
| void GroupNonUniformQuadSwap(spirv::ir::BuiltinCall* call) { | |
| auto* value = call->Args()[1]; | |
| auto* dir_val = call->Args()[2]; | |
| TINT_ASSERT(dir_val->Is<core::ir::Constant>()); | |
| auto* cnst = dir_val->As<core::ir::Constant>(); | |
| TINT_ASSERT(cnst); | |
| uint32_t dir = cnst->Value()->ValueAs<uint32_t>(); | |
| core::BuiltinFn fn = core::BuiltinFn::kNone; | |
| switch (dir) { | |
| case 0: | |
| fn = core::BuiltinFn::kQuadSwapX; | |
| break; | |
| case 1: | |
| fn = core::BuiltinFn::kQuadSwapY; | |
| break; | |
| case 2: | |
| fn = core::BuiltinFn::kQuadSwapDiagonal; | |
| break; | |
| default: | |
| TINT_UNREACHABLE(); | |
| } | |
| auto* type = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| if (type->DeepestElement()->Is<core::type::Bool>()) { | |
| type = ty.MatchWidth(ty.u32(), type); | |
| value = b.Convert(type, value)->Result(); | |
| } | |
| core::ir::Value* c = b.Call(type, fn, Vector{value})->Result(); | |
| if (type != call->Result()->Type()) { | |
| c = b.Convert(call->Result()->Type(), c)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(c); | |
| }); | |
| call->Destroy(); | |
| } | |
| void GroupNonUniformBuiltin(spirv::ir::BuiltinCall* call, core::BuiltinFn fn) { | |
| auto* value = call->Args()[1]; | |
| auto* id = call->Args()[2]; | |
| auto* type = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| if (type->DeepestElement()->Is<core::type::Bool>()) { | |
| type = ty.MatchWidth(ty.u32(), type); | |
| value = b.Convert(type, value)->Result(); | |
| } | |
| core::ir::Value* c = b.Call(type, fn, Vector{value, id})->Result(); | |
| if (type != call->Result()->Type()) { | |
| c = b.Convert(call->Result()->Type(), c)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(c); | |
| }); | |
| call->Destroy(); | |
| } | |
| void GroupNonUniformBroadcastFirst(spirv::ir::BuiltinCall* call) { | |
| auto* value = call->Args()[1]; | |
| auto* type = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| if (type->DeepestElement()->Is<core::type::Bool>()) { | |
| type = ty.MatchWidth(ty.u32(), type); | |
| value = b.Convert(type, value)->Result(); | |
| } | |
| core::ir::Value* c = | |
| b.Call(type, core::BuiltinFn::kSubgroupBroadcastFirst, Vector{value})->Result(); | |
| if (type != call->Result()->Type()) { | |
| c = b.Convert(call->Result()->Type(), c)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(c); | |
| }); | |
| call->Destroy(); | |
| } | |
| void OuterProduct(spirv::ir::BuiltinCall* call) { | |
| auto* vector1 = call->Args()[0]; | |
| auto* vector2 = call->Args()[1]; | |
| uint32_t rows = vector1->Type()->As<core::type::Vector>()->Width(); | |
| uint32_t cols = vector2->Type()->As<core::type::Vector>()->Width(); | |
| auto* elem_ty = vector1->Type()->DeepestElement(); | |
| b.InsertBefore(call, [&] { | |
| Vector<core::ir::Value*, 4> col_vectors; | |
| for (uint32_t col = 0; col < cols; ++col) { | |
| Vector<core::ir::Value*, 4> col_elements; | |
| auto* v2_element = b.Access(elem_ty, vector2, u32(col)); | |
| for (uint32_t row = 0; row < rows; ++row) { | |
| auto* v1_element = b.Access(elem_ty, vector1, u32(row)); | |
| auto* result = b.Multiply(v1_element, v2_element)->Result(); | |
| col_elements.Push(result); | |
| } | |
| auto* row_vector = b.Construct(ty.vec(elem_ty, rows), col_elements)->Result(); | |
| col_vectors.Push(row_vector); | |
| } | |
| b.ConstructWithResult(call->DetachResult(), col_vectors); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Select(spirv::ir::BuiltinCall* call) { | |
| auto* cond = call->Args()[0]; | |
| auto* true_ = call->Args()[1]; | |
| auto* false_ = call->Args()[2]; | |
| b.InsertBefore(call, [&] { | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kSelect, false_, true_, cond); | |
| }); | |
| call->Destroy(); | |
| } | |
| // FMod(x, y) emulated with: x - y * floor(x / y) | |
| void FMod(spirv::ir::BuiltinCall* call) { | |
| auto* x = call->Args()[0]; | |
| auto* y = call->Args()[1]; | |
| auto* res_ty = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| auto* div = b.Divide(x, y); | |
| auto* floor = b.Call(res_ty, core::BuiltinFn::kFloor, div); | |
| auto* mul = b.Multiply(y, floor); | |
| auto* sub = b.Subtract(x, mul); | |
| call->Result()->ReplaceAllUsesWith(sub->Result()); | |
| }); | |
| call->Destroy(); | |
| } | |
| void SNegate(spirv::ir::BuiltinCall* call) { | |
| auto* val = call->Args()[0]; | |
| auto* res_ty = call->Result()->Type(); | |
| auto* neg_ty = ty.MatchWidth(ty.i32(), val->Type()); | |
| b.InsertBefore(call, [&] { | |
| if (val->Type() != neg_ty) { | |
| val = b.Bitcast(neg_ty, val)->Result(); | |
| } | |
| val = b.Negation(val)->Result(); | |
| if (neg_ty != res_ty) { | |
| val = b.Bitcast(res_ty, val)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(val); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Not(spirv::ir::BuiltinCall* call) { | |
| auto* val = call->Args()[0]; | |
| auto* result_ty = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| auto* complement = b.Complement(val)->Result(); | |
| if (val->Type() != result_ty) { | |
| complement = b.Bitcast(result_ty, complement)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(complement); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ConvertSToF(spirv::ir::BuiltinCall* call) { | |
| b.InsertBefore(call, [&] { | |
| auto* result_ty = call->Result()->Type(); | |
| auto* arg = call->Args()[0]; | |
| if (arg->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| arg = b.Bitcast(ty.MatchWidth(ty.i32(), result_ty), arg)->Result(); | |
| } | |
| b.ConvertWithResult(call->DetachResult(), arg); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ConvertUToF(spirv::ir::BuiltinCall* call) { | |
| b.InsertBefore(call, [&] { | |
| auto* result_ty = call->Result()->Type(); | |
| auto* arg = call->Args()[0]; | |
| if (arg->Type()->IsSignedIntegerScalarOrVector()) { | |
| arg = b.Bitcast(ty.MatchWidth(ty.u32(), result_ty), arg)->Result(); | |
| } | |
| b.ConvertWithResult(call->DetachResult(), arg); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ConvertFToS(spirv::ir::BuiltinCall* call) { | |
| b.InsertBefore(call, [&] { | |
| auto* res_ty = call->Result()->Type(); | |
| auto deepest = res_ty->DeepestElement(); | |
| auto* res = b.Convert(ty.MatchWidth(ty.i32(), res_ty), call->Args()[0])->Result(); | |
| if (deepest->IsUnsignedIntegerScalar()) { | |
| res = b.Bitcast(res_ty, res)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(res); | |
| }); | |
| call->Destroy(); | |
| } | |
| void EmitBinaryWrappedAsFirstArg(spirv::ir::BuiltinCall* call, core::BinaryOp op) { | |
| const auto& args = call->Args(); | |
| auto* lhs = args[0]; | |
| auto* rhs = args[1]; | |
| auto* op_ty = lhs->Type(); | |
| auto* res_ty = call->Result()->Type(); | |
| b.InsertBefore(call, [&] { | |
| if (rhs->Type() != op_ty) { | |
| rhs = b.Bitcast(op_ty, rhs)->Result(); | |
| } | |
| auto* c = b.Binary(op, op_ty, lhs, rhs)->Result(); | |
| if (res_ty != op_ty) { | |
| c = b.Bitcast(res_ty, c)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(c); | |
| }); | |
| call->Destroy(); | |
| } | |
| void BitwiseAnd(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kAnd); | |
| } | |
| void BitwiseOr(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kOr); | |
| } | |
| void BitwiseXor(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kXor); | |
| } | |
| void Add(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kAdd); | |
| } | |
| void Sub(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kSubtract); | |
| } | |
| void Mul(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kMultiply); | |
| } | |
| void EmitBinaryWrappedSignedSpirvMethods(spirv::ir::BuiltinCall* call, core::BinaryOp op) { | |
| const auto& args = call->Args(); | |
| auto* lhs = args[0]; | |
| auto* rhs = args[1]; | |
| auto* res_ty = call->Result()->Type(); | |
| auto* op_ty = ty.MatchWidth(ty.i32(), res_ty); | |
| b.InsertBefore(call, [&] { | |
| if (lhs->Type() != op_ty) { | |
| lhs = b.Bitcast(op_ty, lhs)->Result(); | |
| } | |
| if (rhs->Type() != op_ty) { | |
| rhs = b.Bitcast(op_ty, rhs)->Result(); | |
| } | |
| auto* c = b.Binary(op, op_ty, lhs, rhs)->Result(); | |
| if (res_ty != op_ty) { | |
| c = b.Bitcast(res_ty, c)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(c); | |
| }); | |
| call->Destroy(); | |
| } | |
| void SDiv(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedSignedSpirvMethods(call, core::BinaryOp::kDivide); | |
| } | |
| void SMod(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWrappedSignedSpirvMethods(call, core::BinaryOp::kModulo); | |
| } | |
| void EmitBinaryMatchedArgs(spirv::ir::BuiltinCall* call, core::BinaryOp op) { | |
| const auto& args = call->Args(); | |
| auto* lhs = args[0]; | |
| auto* rhs = args[1]; | |
| b.InsertBefore(call, [&] { | |
| if (rhs->Type() != lhs->Type()) { | |
| rhs = b.Bitcast(lhs->Type(), rhs)->Result(); | |
| } | |
| b.BinaryWithResult(call->DetachResult(), op, lhs, rhs)->Result(); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Equal(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryMatchedArgs(call, core::BinaryOp::kEqual); | |
| } | |
| void NotEqual(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryMatchedArgs(call, core::BinaryOp::kNotEqual); | |
| } | |
| void EmitBinaryWithSignedArgs(spirv::ir::BuiltinCall* call, core::BinaryOp op) { | |
| const auto& args = call->Args(); | |
| auto* lhs = args[0]; | |
| auto* rhs = args[1]; | |
| auto* arg_ty = ty.MatchWidth(ty.i32(), call->Result()->Type()); | |
| b.InsertBefore(call, [&] { | |
| if (lhs->Type() != arg_ty) { | |
| lhs = b.Bitcast(arg_ty, lhs)->Result(); | |
| } | |
| if (rhs->Type() != arg_ty) { | |
| rhs = b.Bitcast(arg_ty, rhs)->Result(); | |
| } | |
| b.BinaryWithResult(call->DetachResult(), op, lhs, rhs)->Result(); | |
| }); | |
| call->Destroy(); | |
| } | |
| void SGreaterThan(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithSignedArgs(call, core::BinaryOp::kGreaterThan); | |
| } | |
| void SGreaterThanEqual(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithSignedArgs(call, core::BinaryOp::kGreaterThanEqual); | |
| } | |
| void SLessThan(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithSignedArgs(call, core::BinaryOp::kLessThan); | |
| } | |
| void SLessThanEqual(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithSignedArgs(call, core::BinaryOp::kLessThanEqual); | |
| } | |
| void EmitBinaryWithUnsignedArgs(spirv::ir::BuiltinCall* call, core::BinaryOp op) { | |
| const auto& args = call->Args(); | |
| auto* lhs = args[0]; | |
| auto* rhs = args[1]; | |
| auto* arg_ty = ty.MatchWidth(ty.u32(), call->Result()->Type()); | |
| b.InsertBefore(call, [&] { | |
| if (lhs->Type() != arg_ty) { | |
| lhs = b.Bitcast(arg_ty, lhs)->Result(); | |
| } | |
| if (rhs->Type() != arg_ty) { | |
| rhs = b.Bitcast(arg_ty, rhs)->Result(); | |
| } | |
| b.BinaryWithResult(call->DetachResult(), op, lhs, rhs)->Result(); | |
| }); | |
| call->Destroy(); | |
| } | |
| void UGreaterThan(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithUnsignedArgs(call, core::BinaryOp::kGreaterThan); | |
| } | |
| void UGreaterThanEqual(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithUnsignedArgs(call, core::BinaryOp::kGreaterThanEqual); | |
| } | |
| void ULessThan(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithUnsignedArgs(call, core::BinaryOp::kLessThan); | |
| } | |
| void ULessThanEqual(spirv::ir::BuiltinCall* call) { | |
| EmitBinaryWithUnsignedArgs(call, core::BinaryOp::kLessThanEqual); | |
| } | |
| // The SPIR-V Signed methods all interpret their arguments as signed (regardless of the type of | |
| // the argument). In order to satisfy this, we must bitcast any unsigned argument to a signed | |
| // type before calling the WGSL equivalent method. | |
| // | |
| // The result of the WGSL method will match the arguments, or in this case a signed value. If | |
| // the SPIR-V instruction expected an unsigned result we must bitcast the WGSL result to the | |
| // corrrect unsigned type. | |
| void WrapSignedSpirvMethods(spirv::ir::BuiltinCall* call, core::BuiltinFn func) { | |
| auto args = call->Args(); | |
| b.InsertBefore(call, [&] { | |
| auto* result_ty = call->Result()->Type(); | |
| Vector<core::ir::Value*, 2> new_args; | |
| for (auto* arg : args) { | |
| if (arg->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| arg = b.Bitcast(ty.MatchWidth(ty.i32(), result_ty), arg)->Result(); | |
| } | |
| new_args.Push(arg); | |
| } | |
| auto* new_call = b.Call(result_ty, func, new_args); | |
| core::ir::Value* replacement = new_call->Result(); | |
| if (result_ty->DeepestElement() == ty.u32()) { | |
| new_call->Result()->SetType(ty.MatchWidth(ty.i32(), result_ty)); | |
| replacement = b.Bitcast(result_ty, replacement)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(replacement); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Sign(spirv::ir::BuiltinCall* call) { | |
| WrapSignedSpirvMethods(call, core::BuiltinFn::kSign); | |
| } | |
| void Abs(spirv::ir::BuiltinCall* call) { WrapSignedSpirvMethods(call, core::BuiltinFn::kAbs); } | |
| void FindSMsb(spirv::ir::BuiltinCall* call) { | |
| WrapSignedSpirvMethods(call, core::BuiltinFn::kFirstLeadingBit); | |
| } | |
| void SMax(spirv::ir::BuiltinCall* call) { WrapSignedSpirvMethods(call, core::BuiltinFn::kMax); } | |
| void SMin(spirv::ir::BuiltinCall* call) { WrapSignedSpirvMethods(call, core::BuiltinFn::kMin); } | |
| void SClamp(spirv::ir::BuiltinCall* call) { | |
| WrapSignedSpirvMethods(call, core::BuiltinFn::kClamp); | |
| } | |
| void Ldexp(spirv::ir::BuiltinCall* call) { | |
| WrapSignedSpirvMethods(call, core::BuiltinFn::kLdexp); | |
| } | |
| // The SPIR-V Unsigned methods all interpret their arguments as unsigned (regardless of the type | |
| // of the argument). In order to satisfy this, we must bitcast any signed argument to an | |
| // unsigned type before calling the WGSL equivalent method. | |
| // | |
| // The result of the WGSL method will match the arguments, or in this case an unsigned value. If | |
| // the SPIR-V instruction expected a signed result we must bitcast the WGSL result to the | |
| // correct signed type. | |
| void WrapUnsignedSpirvMethods(spirv::ir::BuiltinCall* call, core::BuiltinFn func) { | |
| auto args = call->Args(); | |
| b.InsertBefore(call, [&] { | |
| auto* result_ty = call->Result()->Type(); | |
| Vector<core::ir::Value*, 2> new_args; | |
| for (auto* arg : args) { | |
| if (arg->Type()->IsSignedIntegerScalarOrVector()) { | |
| arg = b.Bitcast(ty.MatchWidth(ty.u32(), result_ty), arg)->Result(); | |
| } | |
| new_args.Push(arg); | |
| } | |
| auto* new_call = b.Call(result_ty, func, new_args); | |
| core::ir::Value* replacement = new_call->Result(); | |
| if (result_ty->DeepestElement() == ty.i32()) { | |
| new_call->Result()->SetType(ty.MatchWidth(ty.u32(), result_ty)); | |
| replacement = b.Bitcast(result_ty, replacement)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(replacement); | |
| }); | |
| call->Destroy(); | |
| } | |
| void UMax(spirv::ir::BuiltinCall* call) { | |
| WrapUnsignedSpirvMethods(call, core::BuiltinFn::kMax); | |
| } | |
| void UMin(spirv::ir::BuiltinCall* call) { | |
| WrapUnsignedSpirvMethods(call, core::BuiltinFn::kMin); | |
| } | |
| void UClamp(spirv::ir::BuiltinCall* call) { | |
| WrapUnsignedSpirvMethods(call, core::BuiltinFn::kClamp); | |
| } | |
| void FindUMsb(spirv::ir::BuiltinCall* call) { | |
| WrapUnsignedSpirvMethods(call, core::BuiltinFn::kFirstLeadingBit); | |
| } | |
| void Normalize(spirv::ir::BuiltinCall* call) { | |
| auto* arg = call->Args()[0]; | |
| b.InsertBefore(call, [&] { | |
| core::BuiltinFn fn = core::BuiltinFn::kNormalize; | |
| if (arg->Type()->IsScalar()) { | |
| fn = core::BuiltinFn::kSign; | |
| } | |
| b.CallWithResult(call->DetachResult(), fn, Vector<core::ir::Value*, 1>{arg}); | |
| }); | |
| call->Destroy(); | |
| } | |
| void FindILsb(spirv::ir::BuiltinCall* call) { | |
| auto* arg = call->Args()[0]; | |
| b.InsertBefore(call, [&] { | |
| auto* arg_ty = arg->Type(); | |
| auto* ret_ty = call->Result()->Type(); | |
| auto* v = | |
| b.Call(arg_ty, core::BuiltinFn::kFirstTrailingBit, Vector<core::ir::Value*, 1>{arg}) | |
| ->Result(); | |
| if (arg_ty != ret_ty) { | |
| v = b.Bitcast(ret_ty, v)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(v); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Refract(spirv::ir::BuiltinCall* call) { | |
| auto args = call->Args(); | |
| auto* I = args[0]; | |
| auto* N = args[1]; | |
| auto* eta = args[2]; | |
| b.InsertBefore(call, [&] { | |
| if (I->Type()->IsFloatScalar()) { | |
| auto* src_ty = I->Type(); | |
| auto* vec_ty = ty.vec(src_ty, 2); | |
| auto* zero = b.Zero(src_ty); | |
| I = b.Construct(vec_ty, I, zero)->Result(); | |
| N = b.Construct(vec_ty, N, zero)->Result(); | |
| auto* c = b.Call(vec_ty, core::BuiltinFn::kRefract, | |
| Vector<core::ir::Value*, 3>{I, N, eta}); | |
| auto* s = b.Swizzle(src_ty, c, {0}); | |
| call->Result()->ReplaceAllUsesWith(s->Result()); | |
| } else { | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kRefract, | |
| Vector<core::ir::Value*, 3>{I, N, eta}); | |
| } | |
| }); | |
| call->Destroy(); | |
| } | |
| void Reflect(spirv::ir::BuiltinCall* call) { | |
| auto args = call->Args(); | |
| auto* I = args[0]; | |
| auto* N = args[1]; | |
| b.InsertBefore(call, [&] { | |
| if (I->Type()->IsFloatScalar()) { | |
| auto* v = b.Multiply(I, N)->Result(); | |
| v = b.Multiply(v, N)->Result(); | |
| v = b.Multiply(v, 2.0_f)->Result(); | |
| v = b.Subtract(I, v)->Result(); | |
| call->Result()->ReplaceAllUsesWith(v); | |
| } else { | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kReflect, | |
| Vector<core::ir::Value*, 2>{I, N}); | |
| } | |
| }); | |
| call->Destroy(); | |
| } | |
| void FaceForward(spirv::ir::BuiltinCall* call) { | |
| auto args = call->Args(); | |
| auto* N = args[0]; | |
| auto* I = args[1]; | |
| auto* Nref = args[2]; | |
| b.InsertBefore(call, [&] { | |
| if (I->Type()->IsFloatScalar()) { | |
| auto* neg = b.Negation(N); | |
| auto* sel = b.Multiply(I, Nref)->Result(); | |
| sel = b.LessThan(sel, b.Zero(sel->Type()))->Result(); | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kSelect, neg, N, sel); | |
| } else { | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kFaceForward, N, I, Nref); | |
| } | |
| }); | |
| call->Destroy(); | |
| } | |
| void Modf(spirv::ir::BuiltinCall* call) { | |
| auto* x = call->Args()[0]; | |
| auto* i = call->Args()[1]; | |
| auto* result_ty = call->Result()->Type(); | |
| auto* modf_result_ty = core::type::CreateModfResult(ty, ir.symbols, result_ty); | |
| b.InsertBefore(call, [&] { | |
| auto* c = b.Call(modf_result_ty, core::BuiltinFn::kModf, x); | |
| auto* whole = b.Access(result_ty, c, 1_u); | |
| b.Store(i, whole); | |
| b.AccessWithResult(call->DetachResult(), c, 0_u); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Frexp(spirv::ir::BuiltinCall* call) { | |
| auto* x = call->Args()[0]; | |
| auto* i = call->Args()[1]; | |
| auto* result_ty = call->Result()->Type(); | |
| auto* frexp_result_ty = core::type::CreateFrexpResult(ty, ir.symbols, result_ty); | |
| b.InsertBefore(call, [&] { | |
| auto* c = b.Call(frexp_result_ty, core::BuiltinFn::kFrexp, x); | |
| auto* exp = b.Access(ty.MatchWidth(ty.i32(), result_ty), c, 1_u)->Result(); | |
| if (i->Type()->UnwrapPtr()->DeepestElement()->IsUnsignedIntegerScalar()) { | |
| exp = b.Bitcast(i->Type()->UnwrapPtr(), exp)->Result(); | |
| } | |
| b.Store(i, exp); | |
| b.AccessWithResult(call->DetachResult(), c, 0_u); | |
| }); | |
| call->Destroy(); | |
| } | |
| void BitFieldInsert(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| auto* e = args[0]; | |
| auto* newbits = args[1]; | |
| auto* offset = args[2]; | |
| auto* count = args[3]; | |
| b.InsertBefore(call, [&] { | |
| if (offset->Type()->IsSignedIntegerScalar()) { | |
| offset = b.Bitcast(ty.u32(), offset)->Result(); | |
| } | |
| if (count->Type()->IsSignedIntegerScalar()) { | |
| count = b.Bitcast(ty.u32(), count)->Result(); | |
| } | |
| b.CallWithResult(call->DetachResult(), core::BuiltinFn::kInsertBits, e, newbits, offset, | |
| count); | |
| }); | |
| call->Destroy(); | |
| } | |
| void BitFieldUExtract(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| auto* e = args[0]; | |
| auto* offset = args[1]; | |
| auto* count = args[2]; | |
| b.InsertBefore(call, [&] { | |
| bool cast_result = false; | |
| auto* call_ty = ty.MatchWidth(ty.u32(), e->Type()); | |
| if (e->Type()->DeepestElement()->IsSignedIntegerScalar()) { | |
| e = b.Bitcast(call_ty, e)->Result(); | |
| cast_result = true; | |
| } | |
| if (offset->Type()->IsSignedIntegerScalar()) { | |
| offset = b.Bitcast(ty.u32(), offset)->Result(); | |
| } | |
| if (count->Type()->IsSignedIntegerScalar()) { | |
| count = b.Bitcast(ty.u32(), count)->Result(); | |
| } | |
| auto* res = b.Call(call_ty, core::BuiltinFn::kExtractBits, e, offset, count)->Result(); | |
| if (cast_result) { | |
| res = b.Bitcast(call->Result()->Type(), res)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(res); | |
| }); | |
| call->Destroy(); | |
| } | |
| void BitFieldSExtract(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| auto* e = args[0]; | |
| auto* offset = args[1]; | |
| auto* count = args[2]; | |
| b.InsertBefore(call, [&] { | |
| bool cast_result = false; | |
| auto* call_ty = ty.MatchWidth(ty.i32(), e->Type()); | |
| if (e->Type()->DeepestElement()->IsUnsignedIntegerScalar()) { | |
| e = b.Bitcast(call_ty, e)->Result(); | |
| cast_result = true; | |
| } | |
| if (offset->Type()->IsSignedIntegerScalar()) { | |
| offset = b.Bitcast(ty.u32(), offset)->Result(); | |
| } | |
| if (count->Type()->IsSignedIntegerScalar()) { | |
| count = b.Bitcast(ty.u32(), count)->Result(); | |
| } | |
| auto* res = b.Call(call_ty, core::BuiltinFn::kExtractBits, e, offset, count)->Result(); | |
| if (cast_result) { | |
| res = b.Bitcast(call->Result()->Type(), res)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(res); | |
| }); | |
| call->Destroy(); | |
| } | |
| void BitCount(spirv::ir::BuiltinCall* call) { | |
| auto arg = call->Args()[0]; | |
| b.InsertBefore(call, [&] { | |
| auto* res_ty = call->Result()->Type(); | |
| auto* arg_ty = arg->Type(); | |
| auto* bc = b.Call(arg_ty, core::BuiltinFn::kCountOneBits, arg)->Result(); | |
| if (res_ty != arg_ty) { | |
| bc = b.Bitcast(res_ty, bc)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(bc); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ShiftLeftLogical(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| b.InsertBefore(call, [&] { | |
| auto* base = args[0]; | |
| auto* shift = args[1]; | |
| if (!shift->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| shift = b.Bitcast(ty.MatchWidth(ty.u32(), shift->Type()), shift)->Result(); | |
| } | |
| auto* bin = b.Binary(core::BinaryOp::kShiftLeft, base->Type(), base, shift)->Result(); | |
| if (base->Type() != call->Result()->Type()) { | |
| bin = b.Bitcast(call->Result()->Type(), bin)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(bin); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ShiftRightLogical(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| b.InsertBefore(call, [&] { | |
| auto* base = args[0]; | |
| auto* shift = args[1]; | |
| auto* u_ty = ty.MatchWidth(ty.u32(), base->Type()); | |
| if (!base->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| base = b.Bitcast(u_ty, base)->Result(); | |
| } | |
| if (!shift->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| shift = b.Bitcast(u_ty, shift)->Result(); | |
| } | |
| auto* bin = b.Binary(core::BinaryOp::kShiftRight, u_ty, base, shift)->Result(); | |
| if (u_ty != call->Result()->Type()) { | |
| bin = b.Bitcast(call->Result()->Type(), bin)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(bin); | |
| }); | |
| call->Destroy(); | |
| } | |
| void ShiftRightArithmetic(spirv::ir::BuiltinCall* call) { | |
| const auto& args = call->Args(); | |
| b.InsertBefore(call, [&] { | |
| auto* base = args[0]; | |
| auto* shift = args[1]; | |
| auto* s_ty = ty.MatchWidth(ty.i32(), base->Type()); | |
| if (!base->Type()->IsSignedIntegerScalarOrVector()) { | |
| base = b.Bitcast(s_ty, base)->Result(); | |
| } | |
| if (!shift->Type()->IsUnsignedIntegerScalarOrVector()) { | |
| shift = b.Bitcast(ty.MatchWidth(ty.u32(), shift->Type()), shift)->Result(); | |
| } | |
| auto* bin = b.Binary(core::BinaryOp::kShiftRight, s_ty, base, shift)->Result(); | |
| if (s_ty != call->Result()->Type()) { | |
| bin = b.Bitcast(call->Result()->Type(), bin)->Result(); | |
| } | |
| call->Result()->ReplaceAllUsesWith(bin); | |
| }); | |
| call->Destroy(); | |
| } | |
| void Inverse(spirv::ir::BuiltinCall* call) { | |
| auto* arg = call->Args()[0]; | |
| auto* mat_ty = arg->Type()->As<core::type::Matrix>(); | |
| TINT_ASSERT(mat_ty); | |
| TINT_ASSERT(mat_ty->Columns() == mat_ty->Rows()); | |
| auto* elem_ty = mat_ty->Type(); | |
| b.InsertBefore(call, [&] { | |
| auto* det = | |
| b.Call(elem_ty, core::BuiltinFn::kDeterminant, Vector<core::ir::Value*, 1>{arg}); | |
| core::ir::Value* one = nullptr; | |
| if (elem_ty->Is<core::type::F32>()) { | |
| one = b.Constant(1.0_f); | |
| } else if (elem_ty->Is<core::type::F16>()) { | |
| one = b.Constant(1.0_h); | |
| } else { | |
| TINT_UNREACHABLE(); | |
| } | |
| auto* inv_det = b.Divide(one, det); | |
| // Returns (m * n) - (o * p) | |
| auto sub_mul2 = [&](auto* m, auto* n, auto* o, auto* p) { | |
| auto* x = b.Multiply(m, n); | |
| auto* y = b.Multiply(o, p); | |
| return b.Subtract(x, y); | |
| }; | |
| // Returns (m * n) - (o * p) + (q * r) | |
| auto sub_add_mul3 = [&](auto* m, auto* n, auto* o, auto* p, auto* q, auto* r) { | |
| auto* w = b.Multiply(m, n); | |
| auto* x = b.Multiply(o, p); | |
| auto* y = b.Multiply(q, r); | |
| auto* z = b.Subtract(w, x); | |
| return b.Add(z, y); | |
| }; | |
| // Returns (m * n) + (o * p) - (q * r) | |
| auto add_sub_mul3 = [&](auto* m, auto* n, auto* o, auto* p, auto* q, auto* r) { | |
| auto* w = b.Multiply(m, n); | |
| auto* x = b.Multiply(o, p); | |
| auto* y = b.Multiply(q, r); | |
| auto* z = b.Add(w, x); | |
| return b.Subtract(z, y); | |
| }; | |
| switch (mat_ty->Columns()) { | |
| case 2: { | |
| auto* neg_inv_det = b.Negation(inv_det); | |
| auto* ma = b.Access(elem_ty, arg, 0_u, 0_u); | |
| auto* mb = b.Access(elem_ty, arg, 0_u, 1_u); | |
| auto* mc = b.Access(elem_ty, arg, 1_u, 0_u); | |
| auto* md = b.Access(elem_ty, arg, 1_u, 1_u); | |
| auto* r_00 = b.Multiply(inv_det, md); | |
| auto* r_01 = b.Multiply(neg_inv_det, mb); | |
| auto* r_10 = b.Multiply(neg_inv_det, mc); | |
| auto* r_11 = b.Multiply(inv_det, ma); | |
| auto* r1 = b.Construct(ty.vec2(elem_ty), r_00, r_01); | |
| auto* r2 = b.Construct(ty.vec2(elem_ty), r_10, r_11); | |
| b.ConstructWithResult(call->DetachResult(), r1, r2); | |
| break; | |
| } | |
| case 3: { | |
| auto* ma = b.Access(elem_ty, arg, 0_u, 0_u); | |
| auto* mb = b.Access(elem_ty, arg, 0_u, 1_u); | |
| auto* mc = b.Access(elem_ty, arg, 0_u, 2_u); | |
| auto* md = b.Access(elem_ty, arg, 1_u, 0_u); | |
| auto* me = b.Access(elem_ty, arg, 1_u, 1_u); | |
| auto* mf = b.Access(elem_ty, arg, 1_u, 2_u); | |
| auto* mg = b.Access(elem_ty, arg, 2_u, 0_u); | |
| auto* mh = b.Access(elem_ty, arg, 2_u, 1_u); | |
| auto* mi = b.Access(elem_ty, arg, 2_u, 2_u); | |
| // e * i - f * h | |
| auto* r_00 = sub_mul2(me, mi, mf, mh); | |
| // c * h - b * i | |
| auto* r_01 = sub_mul2(mc, mh, mb, mi); | |
| // b * f - c * e | |
| auto* r_02 = sub_mul2(mb, mf, mc, me); | |
| // f * g - d * i | |
| auto* r_10 = sub_mul2(mf, mg, md, mi); | |
| // a * i - c * g | |
| auto* r_11 = sub_mul2(ma, mi, mc, mg); | |
| // c * d - a * f | |
| auto* r_12 = sub_mul2(mc, md, ma, mf); | |
| // d * h - e * g | |
| auto* r_20 = sub_mul2(md, mh, me, mg); | |
| // b * g - a * h | |
| auto* r_21 = sub_mul2(mb, mg, ma, mh); | |
| // a * e - b * d | |
| auto* r_22 = sub_mul2(ma, me, mb, md); | |
| auto* r1 = b.Construct(ty.vec3(elem_ty), r_00, r_01, r_02); | |
| auto* r2 = b.Construct(ty.vec3(elem_ty), r_10, r_11, r_12); | |
| auto* r3 = b.Construct(ty.vec3(elem_ty), r_20, r_21, r_22); | |
| auto* m = b.Construct(mat_ty, r1, r2, r3); | |
| auto* inv = b.Multiply(inv_det, m); | |
| call->Result()->ReplaceAllUsesWith(inv->Result()); | |
| break; | |
| } | |
| case 4: { | |
| auto* ma = b.Access(elem_ty, arg, 0_u, 0_u); | |
| auto* mb = b.Access(elem_ty, arg, 0_u, 1_u); | |
| auto* mc = b.Access(elem_ty, arg, 0_u, 2_u); | |
| auto* md = b.Access(elem_ty, arg, 0_u, 3_u); | |
| auto* me = b.Access(elem_ty, arg, 1_u, 0_u); | |
| auto* mf = b.Access(elem_ty, arg, 1_u, 1_u); | |
| auto* mg = b.Access(elem_ty, arg, 1_u, 2_u); | |
| auto* mh = b.Access(elem_ty, arg, 1_u, 3_u); | |
| auto* mi = b.Access(elem_ty, arg, 2_u, 0_u); | |
| auto* mj = b.Access(elem_ty, arg, 2_u, 1_u); | |
| auto* mk = b.Access(elem_ty, arg, 2_u, 2_u); | |
| auto* ml = b.Access(elem_ty, arg, 2_u, 3_u); | |
| auto* mm = b.Access(elem_ty, arg, 3_u, 0_u); | |
| auto* mn = b.Access(elem_ty, arg, 3_u, 1_u); | |
| auto* mo = b.Access(elem_ty, arg, 3_u, 2_u); | |
| auto* mp = b.Access(elem_ty, arg, 3_u, 3_u); | |
| // kplo = k * p - l * o | |
| auto* kplo = sub_mul2(mk, mp, ml, mo); | |
| // jpln = j * p - l * n | |
| auto* jpln = sub_mul2(mj, mp, ml, mn); | |
| // jokn = j * o - k * n; | |
| auto* jokn = sub_mul2(mj, mo, mk, mn); | |
| // gpho = g * p - h * o | |
| auto* gpho = sub_mul2(mg, mp, mh, mo); | |
| // fphn = f * p - h * n | |
| auto* fphn = sub_mul2(mf, mp, mh, mn); | |
| // fogn = f * o - g * n; | |
| auto* fogn = sub_mul2(mf, mo, mg, mn); | |
| // glhk = g * l - h * k | |
| auto* glhk = sub_mul2(mg, ml, mh, mk); | |
| // flhj = f * l - h * j | |
| auto* flhj = sub_mul2(mf, ml, mh, mj); | |
| // fkgj = f * k - g * j; | |
| auto* fkgj = sub_mul2(mf, mk, mg, mj); | |
| // iplm = i * p - l * m | |
| auto* iplm = sub_mul2(mi, mp, ml, mm); | |
| // iokm = i * o - k * m | |
| auto* iokm = sub_mul2(mi, mo, mk, mm); | |
| // ephm = e * p - h * m; | |
| auto* ephm = sub_mul2(me, mp, mh, mm); | |
| // eogm = e * o - g * m | |
| auto* eogm = sub_mul2(me, mo, mg, mm); | |
| // elhi = e * l - h * i | |
| auto* elhi = sub_mul2(me, ml, mh, mi); | |
| // ekgi = e * k - g * i; | |
| auto* ekgi = sub_mul2(me, mk, mg, mi); | |
| // injm = i * n - j * m | |
| auto* injm = sub_mul2(mi, mn, mj, mm); | |
| // enfm = e * n - f * m | |
| auto* enfm = sub_mul2(me, mn, mf, mm); | |
| // ejfi = e * j - f * i; | |
| auto* ejfi = sub_mul2(me, mj, mf, mi); | |
| auto* neg_b = b.Negation(mb); | |
| // f * kplo - g * jpln + h * jokn | |
| auto* r_00 = sub_add_mul3(mf, kplo, mg, jpln, mh, jokn); | |
| // -b * kplo + c * jpln - d * jokn | |
| auto* r_01 = add_sub_mul3(neg_b, kplo, mc, jpln, md, jokn); | |
| // b * gpho - c * fphn + d * fogn | |
| auto* r_02 = sub_add_mul3(mb, gpho, mc, fphn, md, fogn); | |
| // -b * glhk + c * flhj - d * fkgj | |
| auto* r_03 = add_sub_mul3(neg_b, glhk, mc, flhj, md, fkgj); | |
| auto* neg_e = b.Negation(me); | |
| auto* neg_a = b.Negation(ma); | |
| // -e * kplo + g * iplm - h * iokm | |
| auto* r_10 = add_sub_mul3(neg_e, kplo, mg, iplm, mh, iokm); | |
| // a * kplo - c * iplm + d * iokm | |
| auto* r_11 = sub_add_mul3(ma, kplo, mc, iplm, md, iokm); | |
| // -a * gpho + c * ephm - d * eogm | |
| auto* r_12 = add_sub_mul3(neg_a, gpho, mc, ephm, md, eogm); | |
| // a * glhk - c * elhi + d * ekgi | |
| auto* r_13 = sub_add_mul3(ma, glhk, mc, elhi, md, ekgi); | |
| // e * jpln - f * iplm + h * injm | |
| auto* r_20 = sub_add_mul3(me, jpln, mf, iplm, mh, injm); | |
| // -a * jpln + b * iplm - d * injm | |
| auto* r_21 = add_sub_mul3(neg_a, jpln, mb, iplm, md, injm); | |
| // a * fphn - b * ephm + d * enfm | |
| auto* r_22 = sub_add_mul3(ma, fphn, mb, ephm, md, enfm); | |
| // -a * flhj + b * elhi - d * ejfi | |
| auto* r_23 = add_sub_mul3(neg_a, flhj, mb, elhi, md, ejfi); | |
| // -e * jokn + f * iokm - g * injm | |
| auto* r_30 = add_sub_mul3(neg_e, jokn, mf, iokm, mg, injm); | |
| // a * jokn - b * iokm + c * injm | |
| auto* r_31 = sub_add_mul3(ma, jokn, mb, iokm, mc, injm); | |
| // -a * fogn + b * eogm - c * enfm | |
| auto* r_32 = add_sub_mul3(neg_a, fogn, mb, eogm, mc, enfm); | |
| // a * fkgj - b * ekgi + c * ejfi | |
| auto* r_33 = sub_add_mul3(ma, fkgj, mb, ekgi, mc, ejfi); | |
| auto* r1 = b.Construct(ty.vec4(elem_ty), r_00, r_01, r_02, r_03); | |
| auto* r2 = b.Construct(ty.vec4(elem_ty), r_10, r_11, r_12, r_13); | |
| auto* r3 = b.Construct(ty.vec4(elem_ty), r_20, r_21, r_22, r_23); | |
| auto* r4 = b.Construct(ty.vec4(elem_ty), r_30, r_31, r_32, r_33); | |
| auto* m = b.Construct(mat_ty, r1, r2, r3, r4); | |
| auto* inv = b.Multiply(inv_det, m); | |
| call->Result()->ReplaceAllUsesWith(inv->Result()); | |
| break; | |
| } | |
| default: { | |
| TINT_UNREACHABLE(); | |
| } | |
| } | |
| }); | |
| call->Destroy(); | |
| } | |
| }; | |
| } // namespace | |
| Result<SuccessType> Builtins(core::ir::Module& ir) { | |
| AssertValid(ir, "before spirv.Builtins"); | |
| State{ir}.Process(); | |
| return Success; | |
| } | |
| } // namespace tint::spirv::reader::lower |