blob: 781e245cbe5edd1c23ed9a7f1386d940a715daf3 [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/ast_raise/subgroup_ballot.h"
#include <utility>
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/wgsl/program/clone_context.h"
#include "src/tint/lang/wgsl/program/program_builder.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
#include "src/tint/lang/wgsl/sem/call.h"
#include "src/tint/lang/wgsl/sem/function.h"
#include "src/tint/lang/wgsl/sem/statement.h"
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::SubgroupBallot);
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::SubgroupBallot::SimdActiveThreadsMask);
using namespace tint::core::number_suffixes; // NOLINT
using namespace tint::core::fluent_types; // NOLINT
namespace tint::msl::writer {
/// PIMPL state for the transform
struct SubgroupBallot::State {
/// The source program
const Program& src;
/// The target program builder
ProgramBuilder b;
/// The clone context
program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
/// The name of the `tint_subgroup_ballot` helper function.
Symbol ballot_helper{};
/// The name of the `tint_subgroup_size_mask` global variable.
Symbol subgroup_size_mask{};
/// The set of a functions that directly call `subgroupBallot()`.
Hashset<const sem::Function*, 4> ballot_callers;
/// Constructor
/// @param program the source program
explicit State(const Program& program) : src(program) {}
/// Runs the transform
/// @returns the new program or SkipTransform if the transform is not required
ApplyResult Run() {
auto& sem = src.Sem();
bool made_changes = false;
for (auto* node : ctx.src->ASTNodes().Objects()) {
auto* call = sem.Get<sem::Call>(node);
if (call) {
// If this is a call to a `subgroupBallot()` builtin, replace it with a call to the
// helper function and make a note of the function that we are in.
auto* builtin = call->Target()->As<sem::BuiltinFn>();
if (builtin && builtin->Fn() == wgsl::BuiltinFn::kSubgroupBallot) {
ctx.Replace(call->Declaration(), b.Call(GetHelper()));
ballot_callers.Add(call->Stmt()->Function());
made_changes = true;
}
}
}
if (!made_changes) {
return SkipTransform;
}
// Set the subgroup size mask at the start of each entry point that transitively calls
// `subgroupBallot()`.
for (auto* global : src.AST().GlobalDeclarations()) {
auto* func = global->As<ast::Function>();
if (func && func->IsEntryPoint() && TransitvelyCallsSubgroupBallot(sem.Get(func))) {
SetSubgroupSizeMask(func);
}
}
ctx.Clone();
return resolver::Resolve(b);
}
/// Get (or create) the `tint_msl_subgroup` helper function.
/// @returns the name of the helper function
Symbol GetHelper() {
if (!ballot_helper) {
auto intrinsic = b.Symbols().New("tint_msl_simd_active_threads_mask");
subgroup_size_mask = b.Symbols().New("tint_subgroup_size_mask");
ballot_helper = b.Symbols().New("tint_msl_subgroup_ballot");
// Declare the `tint_msl_subgroup_ballot` intrinsic function, which will use the
// `simd_active_threads_mask` function to return 64-bit vote.
b.Func(intrinsic, Empty, b.ty.vec2<u32>(), nullptr,
Vector{b.ASTNodes().Create<SimdActiveThreadsMask>(b.ID(), b.AllocateNodeID()),
b.Disable(ast::DisabledValidation::kFunctionHasNoBody)});
// Declare the `tint_subgroup_size_mask` variable.
b.GlobalVar(subgroup_size_mask, core::AddressSpace::kPrivate, b.ty.vec4<u32>());
// Declare the `tint_msl_subgroup_ballot` helper function as follows:
// fn tint_msl_subgroup_ballot() -> vec4u {
// let vote : vec2u = vec4f(tint_simd_active_threads_mask(), 0, 0);
// return (vote & tint_subgroup_size_mask);
// }
auto* vote = b.Let(b.Sym(), b.Call(b.ty.vec4<u32>(), b.Call(intrinsic), 0_u, 0_u));
b.Func(ballot_helper, Empty, b.ty.vec4<u32>(),
Vector{
b.Decl(vote),
b.Return(b.And(vote, subgroup_size_mask)),
});
}
return ballot_helper;
}
/// Check if a function directly or transitively calls the `subgroupBallot()` builtin.
/// @param func the function to check
/// @returns true if the function transitively calls `subgroupBallot()`
bool TransitvelyCallsSubgroupBallot(const sem::Function* func) {
if (ballot_callers.Contains(func)) {
return true;
}
for (auto* called : func->TransitivelyCalledFunctions()) {
if (ballot_callers.Contains(called)) {
return true;
}
}
return false;
}
/// Add code to set the `subgroup_size_mask` variable at the start of an entry point.
/// @param ep the entry point
void SetSubgroupSizeMask(const ast::Function* ep) {
// Check the entry point parameters for an existing `subgroup_size` builtin.
Symbol subgroup_size;
for (auto* param : ep->params) {
auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes);
if (builtin &&
src.Sem()
.Get<sem::BuiltinEnumExpression<core::BuiltinValue>>(builtin->builtin)
->Value() == core::BuiltinValue::kSubgroupSize) {
subgroup_size = ctx.Clone(param->name->symbol);
}
}
if (!subgroup_size.IsValid()) {
// No `subgroup_size` builtin parameter was found, so add one.
subgroup_size = b.Symbols().New("tint_subgroup_size");
ctx.InsertBack(ep->params, b.Param(subgroup_size, b.ty.u32(),
Vector{
b.Builtin(core::BuiltinValue::kSubgroupSize),
}));
}
// Add the following to the top of the entry point:
// {
// let gt = subgroup_size > 32;
// subgroup_size_mask[0] = select(0xffffffff >> (32 - subgroup_size), 0xffffffff, gt);
// subgroup_size_mask[1] = select(0, 0xffffffff >> (64 - subgroup_size), gt);
// }
auto* gt = b.Let(b.Sym("gt"), b.GreaterThan(subgroup_size, 32_u));
auto* lo =
b.Call("select", b.Shr(0xffffffff_u, b.Sub(32_u, subgroup_size)), 0xffffffff_u, gt);
auto* hi = b.Call("select", 0_u, b.Shr(0xffffffff_u, b.Sub(64_u, subgroup_size)), gt);
auto* block = b.Block(Vector{
b.Decl(gt),
b.Assign(b.IndexAccessor(subgroup_size_mask, 0_u), lo),
b.Assign(b.IndexAccessor(subgroup_size_mask, 1_u), hi),
});
ctx.InsertFront(ep->body->statements, block);
}
};
SubgroupBallot::SubgroupBallot() = default;
SubgroupBallot::~SubgroupBallot() = default;
ast::transform::Transform::ApplyResult SubgroupBallot::Apply(const Program& src,
const ast::transform::DataMap&,
ast::transform::DataMap&) const {
return State(src).Run();
}
SubgroupBallot::SimdActiveThreadsMask::~SimdActiveThreadsMask() = default;
const SubgroupBallot::SimdActiveThreadsMask* SubgroupBallot::SimdActiveThreadsMask::Clone(
ast::CloneContext& ctx) const {
return ctx.dst->ASTNodes().Create<SubgroupBallot::SimdActiveThreadsMask>(
ctx.dst->ID(), ctx.dst->AllocateNodeID());
}
} // namespace tint::msl::writer