blob: a78679191ea54c172b6a47fb2bb1689d814e0594 [file] [log] [blame]
// 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/shader_io.h"
#include <utility>
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/ir/transform/common/referenced_module_vars.h"
#include "src/tint/lang/core/ir/validator.h"
namespace tint::spirv::reader::lower {
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 block to its containing function.
Hashmap<core::ir::Block*, core::ir::Function*, 64> block_to_function{};
/// A map from each function to a map from input variable to parameter.
Hashmap<core::ir::Function*, Hashmap<core::ir::Var*, core::ir::Value*, 4>, 8>
function_parameter_map{};
/// The set of output variables that have been processed.
Hashset<core::ir::Var*, 4> output_variables{};
/// The mapping from functions to their transitively referenced output variables.
core::ir::ReferencedModuleVars referenced_output_vars{
ir, [](const core::ir::Var* var) {
auto* view = var->Result(0)->Type()->As<core::type::MemoryView>();
return view && view->AddressSpace() == core::AddressSpace::kOut;
}};
/// Process the module.
void Process() {
// Process outputs first, as that may introduce new functions that input variables need to
// be propagated through.
ProcessOutputs();
ProcessInputs();
}
/// Process output variables.
/// Changes output variables to the `private` address space and wraps entry points that produce
/// outputs with new functions that copy the outputs from the private variables to the return
/// value.
void ProcessOutputs() {
// Update entry point functions to return their outputs, using a wrapper function.
// Use a worklist as `ProcessEntryPointOutputs()` will add new functions.
Vector<core::ir::Function*, 4> entry_points;
for (auto& func : ir.functions) {
if (func->Stage() != core::ir::Function::PipelineStage::kUndefined) {
entry_points.Push(func);
}
}
for (auto& ep : entry_points) {
ProcessEntryPointOutputs(ep);
}
// Remove attributes from all of the original structs and module-scope output variables.
// This is done last as we need to copy attributes during `ProcessEntryPointOutputs()`.
for (auto& var : output_variables) {
var->SetAttributes({});
if (auto* str = var->Result(0)->Type()->UnwrapPtr()->As<core::type::Struct>()) {
for (auto* member : str->Members()) {
// TODO(crbug.com/tint/745): Remove the const_cast.
const_cast<core::type::StructMember*>(member)->SetAttributes({});
}
}
}
}
/// Process input variables.
/// Pass inputs down the call stack as parameters to any functions that need them.
void ProcessInputs() {
// Seed the block-to-function map with the function entry blocks.
for (auto& func : ir.functions) {
block_to_function.Add(func->Block(), func);
}
// Gather the list of all module-scope input variables.
Vector<core::ir::Var*, 4> inputs;
for (auto* global : *ir.root_block) {
if (auto* var = global->As<core::ir::Var>()) {
auto addrspace = var->Result(0)->Type()->As<core::type::Pointer>()->AddressSpace();
if (addrspace == core::AddressSpace::kIn) {
inputs.Push(var);
}
}
}
// Replace the input variables with function parameters.
for (auto* var : inputs) {
ReplaceInputPointerUses(var, var->Result(0));
var->Destroy();
}
}
/// Replace an output pointer address space to make it `private`.
/// @param value the output variable
void ReplaceOutputPointerAddressSpace(core::ir::InstructionResult* value) {
// Change the address space to `private`.
auto* old_ptr_type = value->Type();
auto* new_ptr_type = ty.ptr(core::AddressSpace::kPrivate, old_ptr_type->UnwrapPtr());
value->SetType(new_ptr_type);
// Update all uses of the module-scope variable.
value->ForEachUse([&](core::ir::Usage use) {
if (auto* access = use.instruction->As<core::ir::Access>()) {
ReplaceOutputPointerAddressSpace(access->Result(0));
} else if (!use.instruction->IsAnyOf<core::ir::Load, core::ir::LoadVectorElement,
core::ir::Store, core::ir::StoreVectorElement>()) {
TINT_UNREACHABLE()
<< "unexpected instruction: " << use.instruction->TypeInfo().name;
}
});
}
/// Process the outputs of an entry point function, adding a wrapper function to forward outputs
/// through the return value.
/// @param ep the entry point
void ProcessEntryPointOutputs(core::ir::Function* ep) {
const auto& referenced_outputs = referenced_output_vars.TransitiveReferences(ep);
if (referenced_outputs.IsEmpty()) {
return;
}
// Add a wrapper function to return either a single value or a struct.
auto* wrapper = b.Function(ty.void_(), ep->Stage());
if (auto name = ir.NameOf(ep)) {
ir.SetName(ep, name.Name() + "_inner");
ir.SetName(wrapper, name);
}
// Call the original entry point and make it a regular function.
ep->SetStage(core::ir::Function::PipelineStage::kUndefined);
b.Append(wrapper->Block(), [&] { //
b.Call(ep);
});
// Collect all outputs into a list of struct member declarations.
// Also add instructions to load their final values in the wrapper function.
Vector<core::ir::Value*, 4> results;
Vector<core::type::Manager::StructMemberDesc, 4> output_descriptors;
auto add_output = [&](Symbol name, const core::type::Type* type,
core::type::StructMemberAttributes attributes) {
if (!name) {
name = ir.symbols.New();
}
output_descriptors.Push(core::type::Manager::StructMemberDesc{name, type, attributes});
};
for (auto* var : referenced_outputs) {
// Change the address space of the variable to private and update its uses, if we
// haven't already seen this variable.
if (output_variables.Add(var)) {
ReplaceOutputPointerAddressSpace(var->Result(0));
}
// Copy the variable attributes to the struct member.
const auto& original_attributes = var->Attributes();
core::type::StructMemberAttributes var_attributes;
var_attributes.invariant = original_attributes.invariant;
var_attributes.builtin = original_attributes.builtin;
var_attributes.location = original_attributes.location;
var_attributes.interpolation = original_attributes.interpolation;
auto var_type = var->Result(0)->Type()->UnwrapPtr();
if (auto* str = var_type->As<core::type::Struct>()) {
// Add an output for each member of the struct.
for (auto* member : str->Members()) {
// Use the base variable attributes if not specified directly on the member.
auto member_attributes = member->Attributes();
if (auto base_loc = var_attributes.location) {
// Location values increment from the base location value on the variable.
member_attributes.location = base_loc.value() + member->Index();
}
if (!member_attributes.interpolation) {
member_attributes.interpolation = var_attributes.interpolation;
}
add_output(member->Name(), member->Type(), std::move(member_attributes));
// Load the final result from the member of the original struct variable.
b.Append(wrapper->Block(), [&] { //
auto* access =
b.Access(ty.ptr<private_>(member->Type()), var, u32(member->Index()));
results.Push(b.Load(access)->Result(0));
});
}
} else {
// Load the final result from the original variable.
b.Append(wrapper->Block(), [&] {
results.Push(b.Load(var)->Result(0));
// If we're dealing with sample_mask, extract the scalar from the array.
if (var_attributes.builtin == core::BuiltinValue::kSampleMask) {
var_type = ty.u32();
results.Back() = b.Access(ty.u32(), results.Back(), u32(0))->Result(0);
}
});
add_output(ir.NameOf(var), var_type, std::move(var_attributes));
}
}
if (output_descriptors.Length() == 1) {
// Copy the output attributes to the function return.
const auto& attributes = output_descriptors[0].attributes;
wrapper->SetReturnInvariant(attributes.invariant);
if (attributes.builtin) {
wrapper->SetReturnBuiltin(attributes.builtin.value());
} else if (attributes.location) {
core::ir::Location loc;
loc.value = attributes.location.value();
loc.interpolation = attributes.interpolation;
wrapper->SetReturnLocation(std::move(loc));
}
// Return the output from the wrapper function.
wrapper->SetReturnType(output_descriptors[0].type);
b.Append(wrapper->Block(), [&] { //
b.Return(wrapper, results[0]);
});
} else {
// Create a struct to hold all of the output values.
auto* str = ty.Struct(ir.symbols.New(), std::move(output_descriptors));
wrapper->SetReturnType(str);
// Collect the output values and return them from the wrapper function.
b.Append(wrapper->Block(), [&] { //
b.Return(wrapper, b.Construct(str, std::move(results)));
});
}
}
/// Replace a use of an input pointer value.
/// @param var the originating input variable
/// @param value the input pointer value
void ReplaceInputPointerUses(core::ir::Var* var, core::ir::Value* value) {
Vector<core::ir::Instruction*, 8> to_destroy;
value->ForEachUse([&](core::ir::Usage use) {
auto* object = value;
if (object->Type()->Is<core::type::Pointer>()) {
// Get (or create) the function parameter that will replace the variable.
auto* func = ContainingFunction(use.instruction);
object = GetParameter(func, var);
}
Switch(
use.instruction,
[&](core::ir::Load* l) {
// Fold the load away and replace its uses with the new parameter.
l->Result(0)->ReplaceAllUsesWith(object);
to_destroy.Push(l);
},
[&](core::ir::LoadVectorElement* lve) {
// Replace the vector element load with an access instruction.
auto* access = b.AccessWithResult(lve->DetachResult(), object, lve->Index());
access->InsertBefore(lve);
to_destroy.Push(lve);
},
[&](core::ir::Access* a) {
if (!a->Indices().IsEmpty()) {
// Remove the pointer from the source and destination type.
a->SetOperand(core::ir::Access::kObjectOperandOffset, object);
a->Result(0)->SetType(a->Result(0)->Type()->UnwrapPtr());
ReplaceInputPointerUses(var, a->Result(0));
} else {
// Fold the access away and replace its uses.
ReplaceInputPointerUses(var, a->Result(0));
to_destroy.Push(a);
}
},
TINT_ICE_ON_NO_MATCH);
});
// Clean up orphaned instructions.
for (auto* inst : to_destroy) {
inst->Destroy();
}
}
/// Get the function that contains an instruction.
/// @param inst the instruction
/// @returns the function
core::ir::Function* ContainingFunction(core::ir::Instruction* inst) {
return block_to_function.GetOrAdd(inst->Block(), [&] { //
return ContainingFunction(inst->Block()->Parent());
});
}
/// Get or create a function parameter to replace a module-scope variable.
/// @param func the function
/// @param var the module-scope variable
/// @returns the function parameter
core::ir::Value* GetParameter(core::ir::Function* func, core::ir::Var* var) {
return function_parameter_map.GetOrAddZero(func).GetOrAdd(var, [&] {
const bool entry_point = func->Stage() != core::ir::Function::PipelineStage::kUndefined;
auto* var_type = var->Result(0)->Type()->UnwrapPtr();
// Use a scalar u32 for sample_mask builtins for entry point parameters.
if (entry_point && var->Attributes().builtin == core::BuiltinValue::kSampleMask) {
TINT_ASSERT(var_type->Is<core::type::Array>());
TINT_ASSERT(var_type->As<core::type::Array>()->ConstantCount() == 1u);
var_type = ty.u32();
}
// Create a new function parameter for the input.
auto* param = b.FunctionParam(var_type);
func->AppendParam(param);
if (auto name = ir.NameOf(var)) {
ir.SetName(param, name);
}
// Add attributes to the parameter if this is an entry point function.
if (entry_point) {
AddEntryPointParameterAttributes(param, var->Attributes());
}
// Update the callsites of this function.
func->ForEachUse([&](core::ir::Usage use) {
if (auto* call = use.instruction->As<core::ir::UserCall>()) {
// Recurse into the calling function.
auto* caller = ContainingFunction(call);
call->AppendArg(GetParameter(caller, var));
} else if (!use.instruction->Is<core::ir::Return>()) {
TINT_UNREACHABLE()
<< "unexpected instruction: " << use.instruction->TypeInfo().name;
}
});
core::ir::Value* result = param;
if (entry_point && var->Attributes().builtin == core::BuiltinValue::kSampleMask) {
// Construct an array from the scalar sample_mask builtin value for entry points.
b.Prepend(func->Block(), [&] { //
result = b.Construct(var->Result(0)->Type()->UnwrapPtr(), param)->Result(0);
});
}
return result;
});
}
/// Add attributes to an entry point function parameter.
/// @param param the parameter
/// @param attributes the attributes
void AddEntryPointParameterAttributes(core::ir::FunctionParam* param,
const core::ir::IOAttributes& attributes) {
if (auto* str = param->Type()->UnwrapPtr()->As<core::type::Struct>()) {
for (auto* member : str->Members()) {
// Use the base variable attributes if not specified directly on the member.
auto member_attributes = member->Attributes();
if (auto base_loc = attributes.location) {
// Location values increment from the base location value on the variable.
member_attributes.location = base_loc.value() + member->Index();
}
if (!member_attributes.interpolation) {
member_attributes.interpolation = attributes.interpolation;
}
// TODO(crbug.com/tint/745): Remove the const_cast.
const_cast<core::type::StructMember*>(member)->SetAttributes(
std::move(member_attributes));
}
} else {
// Set attributes directly on the function parameter.
param->SetInvariant(attributes.invariant);
if (attributes.builtin) {
param->SetBuiltin(attributes.builtin.value());
} else if (attributes.location) {
core::ir::Location loc;
loc.value = attributes.location.value();
loc.interpolation = attributes.interpolation;
param->SetLocation(std::move(loc));
}
}
}
};
} // namespace
Result<SuccessType> ShaderIO(core::ir::Module& ir) {
auto result = ValidateAndDumpIfNeeded(ir, "ShaderIO transform");
if (result != Success) {
return result.Failure();
}
State{ir}.Process();
return Success;
}
} // namespace tint::spirv::reader::lower