blob: b5bbaf130da9cb392f83dbd546cb0e05d8100d65 [file] [log] [blame]
// Copyright 2023 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/msl/writer/raise/builtin_polyfill.h"
#include <atomic>
#include <utility>
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/constant.h"
#include "src/tint/lang/core/ir/core_builtin_call.h"
#include "src/tint/lang/core/ir/function.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/msl/barrier_type.h"
#include "src/tint/lang/msl/builtin_fn.h"
#include "src/tint/lang/msl/ir/builtin_call.h"
#include "src/tint/lang/msl/ir/member_builtin_call.h"
#include "src/tint/lang/msl/ir/memory_order.h"
#include "src/tint/utils/containers/hashmap.h"
namespace tint::msl::writer::raise {
namespace {
using namespace tint::core::fluent_types; // 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()};
/// A map from an atomic pointer type to an atomicCompareExchangeWeak polyfill.
Hashmap<const core::type::Type*, core::ir::Function*, 2> atomic_compare_exchange_polyfills{};
/// Process the module.
void Process() {
// Find the builtins that need replacing.
Vector<core::ir::CoreBuiltinCall*, 4> worklist;
for (auto* inst : ir.Instructions()) {
if (auto* builtin = inst->As<core::ir::CoreBuiltinCall>()) {
switch (builtin->Func()) {
case core::BuiltinFn::kAtomicAdd:
case core::BuiltinFn::kAtomicAnd:
case core::BuiltinFn::kAtomicCompareExchangeWeak:
case core::BuiltinFn::kAtomicExchange:
case core::BuiltinFn::kAtomicLoad:
case core::BuiltinFn::kAtomicMax:
case core::BuiltinFn::kAtomicMin:
case core::BuiltinFn::kAtomicOr:
case core::BuiltinFn::kAtomicStore:
case core::BuiltinFn::kAtomicSub:
case core::BuiltinFn::kAtomicXor:
case core::BuiltinFn::kTextureSample:
case core::BuiltinFn::kStorageBarrier:
case core::BuiltinFn::kWorkgroupBarrier:
case core::BuiltinFn::kTextureBarrier:
worklist.Push(builtin);
break;
default:
break;
}
}
}
// Replace the builtins that we found.
for (auto* builtin : worklist) {
switch (builtin->Func()) {
// Atomics.
case core::BuiltinFn::kAtomicAdd:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchAddExplicit);
break;
case core::BuiltinFn::kAtomicAnd:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchAndExplicit);
break;
case core::BuiltinFn::kAtomicCompareExchangeWeak:
AtomicCompareExchangeWeak(builtin);
break;
case core::BuiltinFn::kAtomicExchange:
AtomicCall(builtin, msl::BuiltinFn::kAtomicExchangeExplicit);
break;
case core::BuiltinFn::kAtomicLoad:
AtomicCall(builtin, msl::BuiltinFn::kAtomicLoadExplicit);
break;
case core::BuiltinFn::kAtomicMax:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchMaxExplicit);
break;
case core::BuiltinFn::kAtomicMin:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchMinExplicit);
break;
case core::BuiltinFn::kAtomicOr:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchOrExplicit);
break;
case core::BuiltinFn::kAtomicStore:
AtomicCall(builtin, msl::BuiltinFn::kAtomicStoreExplicit);
break;
case core::BuiltinFn::kAtomicSub:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchSubExplicit);
break;
case core::BuiltinFn::kAtomicXor:
AtomicCall(builtin, msl::BuiltinFn::kAtomicFetchXorExplicit);
break;
// Texture builtins.
case core::BuiltinFn::kTextureSample:
TextureSample(builtin);
break;
// Barriers.
case core::BuiltinFn::kStorageBarrier:
ThreadgroupBarrier(builtin, BarrierType::kDevice);
break;
case core::BuiltinFn::kWorkgroupBarrier:
ThreadgroupBarrier(builtin, BarrierType::kThreadGroup);
break;
case core::BuiltinFn::kTextureBarrier:
ThreadgroupBarrier(builtin, BarrierType::kTexture);
break;
default:
break;
}
}
}
/// Replace an atomic builtin call with an equivalent MSL intrinsic.
/// @param builtin the builtin call instruction
void AtomicCall(core::ir::CoreBuiltinCall* builtin, msl::BuiltinFn intrinsic) {
auto args = Vector<core::ir::Value*, 4>{builtin->Args()};
args.Push(ir.allocators.values.Create<msl::ir::MemoryOrder>(
b.ConstantValue(u32(std::memory_order_relaxed))));
auto* call = b.CallWithResult<msl::ir::BuiltinCall>(builtin->DetachResult(), intrinsic,
std::move(args));
call->InsertBefore(builtin);
builtin->Destroy();
}
/// Replace an atomicCompareExchangeWeak builtin call with an equivalent MSL polyfill.
/// @param builtin the builtin call instruction
void AtomicCompareExchangeWeak(core::ir::CoreBuiltinCall* builtin) {
// Get or generate a polyfill function.
auto* atomic_ptr = builtin->Args()[0]->Type();
auto* polyfill = atomic_compare_exchange_polyfills.GetOrAdd(atomic_ptr, [&] {
// The polyfill function performs the equivalent to the following:
// int old_value = cmp;
// bool exchanged = atomic_compare_exchange_weak_explicit(
// atomic_ptr, old_value, val,
// memory_order_relaxed, memory_order_relaxed);
// return __atomic_compare_exchange_result_i32(old_value, exchanged);
auto* ptr = b.FunctionParam("atomic_ptr", atomic_ptr);
auto* cmp = b.FunctionParam("cmp", builtin->Args()[1]->Type());
auto* val = b.FunctionParam("val", builtin->Args()[2]->Type());
auto* func = b.Function(builtin->Result(0)->Type());
func->SetParams({ptr, cmp, val});
b.Append(func->Block(), [&] {
auto* old_value = b.Var<function>("old_value", cmp)->Result(0);
auto* order = ir.allocators.values.Create<msl::ir::MemoryOrder>(
b.ConstantValue(u32(std::memory_order_relaxed)));
auto* call = b.Call<msl::ir::BuiltinCall>(
ty.bool_(), BuiltinFn::kAtomicCompareExchangeWeakExplicit,
Vector{ptr, old_value, val, order, order});
auto* result =
b.Construct(builtin->Result(0)->Type(), Vector{
b.Load(old_value)->Result(0),
call->Result(0),
});
b.Return(func, result);
});
return func;
});
// Call the polyfill function.
auto args = Vector<core::ir::Value*, 4>{builtin->Args()};
auto* call = b.CallWithResult(builtin->DetachResult(), polyfill, std::move(args));
call->InsertBefore(builtin);
builtin->Destroy();
}
/// Replace a textureSample call with the equivalent MSL intrinsic.
/// @param builtin the builtin call instruction
void TextureSample(core::ir::CoreBuiltinCall* builtin) {
// The MSL intrinsic is a member function, so we split the first argument off as the object.
auto args = Vector<core::ir::Value*, 4>(builtin->Args().Offset(1));
auto* call = b.MemberCallWithResult<msl::ir::MemberBuiltinCall>(
builtin->DetachResult(), msl::BuiltinFn::kSample, builtin->Args()[0], std::move(args));
call->InsertBefore(builtin);
builtin->Destroy();
}
/// Replace a barrier builtin with the `threadgroupBarrier()` intrinsic.
/// @param builtin the builtin call instruction
/// @param type the barrier type
void ThreadgroupBarrier(core::ir::CoreBuiltinCall* builtin, BarrierType type) {
// Replace the builtin call with a call to the msl.threadgroup_barrier intrinsic.
auto args = Vector<core::ir::Value*, 1>{b.Constant(u32(type))};
auto* call = b.CallWithResult<msl::ir::BuiltinCall>(
builtin->DetachResult(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args));
call->InsertBefore(builtin);
builtin->Destroy();
}
};
} // namespace
Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
if (result != Success) {
return result.Failure();
}
State{ir}.Process();
return Success;
}
} // namespace tint::msl::writer::raise