blob: 0ad0221ec86928aab0699bb52050ee49b69ae179 [file] [log] [blame]
dan sinclaird7504732024-06-27 22:10:24 +00001// Copyright 2024 The Dawn & Tint Authors
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3. Neither the name of the copyright holder nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28#include "src/tint/lang/hlsl/writer/raise/promote_initializers.h"
29
30#include "src/tint/lang/core/ir/builder.h"
31#include "src/tint/lang/core/ir/validator.h"
32
33using namespace tint::core::fluent_types; // NOLINT
34using namespace tint::core::number_suffixes; // NOLINT
35
36namespace tint::hlsl::writer::raise {
37
38namespace {
39
40/// PIMPL state for the transform.
41struct State {
42 /// The IR module.
43 core::ir::Module& ir;
44
45 /// The IR builder.
46 core::ir::Builder b{ir};
47
48 /// The type manager.
49 core::type::Manager& ty{ir.Types()};
50
51 /// Process the module.
52 void Process() {
53 for (auto* block : ir.blocks.Objects()) {
54 // In the root block, all structs need to be split out, no nested structs
55 bool is_root_block = block == ir.root_block;
56
57 Process(block, is_root_block);
58 }
59 }
60
61 struct ValueInfo {
62 core::ir::Instruction* inst;
dan sinclair9fa7c892024-06-29 21:13:48 +000063 size_t index;
dan sinclaird7504732024-06-27 22:10:24 +000064 core::ir::Value* val;
65 };
66
67 void Process(core::ir::Block* block, bool is_root_block) {
68 Vector<ValueInfo, 4> worklist;
dan sinclaird7504732024-06-27 22:10:24 +000069
70 for (auto* inst : *block) {
71 if (inst->Is<core::ir::Let>()) {
72 continue;
73 }
74 if (inst->Is<core::ir::Var>()) {
dan sinclair9fa7c892024-06-29 21:13:48 +000075 // In the root block we need to split struct and array vars out to turn them into
76 // `static const` variables.
77 if (!is_root_block) {
78 continue;
dan sinclaird7504732024-06-27 22:10:24 +000079 }
dan sinclaird7504732024-06-27 22:10:24 +000080 }
81
dan sinclair9fa7c892024-06-29 21:13:48 +000082 // Check each operand of the instruction to determine if it's a struct or array.
83 auto operands = inst->Operands();
84 for (size_t i = 0; i < operands.Length(); ++i) {
85 auto* operand = operands[i];
86 if (!operand || !operand->Type() ||
dan sinclaird7504732024-06-27 22:10:24 +000087 !operand->Type()->IsAnyOf<core::type::Struct, core::type::Array>()) {
88 continue;
89 }
90 if (operand->IsAnyOf<core::ir::InstructionResult, core::ir::Constant>()) {
dan sinclair9fa7c892024-06-29 21:13:48 +000091 worklist.Push({inst, i, operand});
dan sinclaird7504732024-06-27 22:10:24 +000092 }
93 }
94 }
95
dan sinclair9fa7c892024-06-29 21:13:48 +000096 Vector<core::ir::Construct*, 4> const_worklist;
dan sinclaird7504732024-06-27 22:10:24 +000097 for (auto& item : worklist) {
98 if (auto* res = As<core::ir::InstructionResult>(item.val)) {
dan sinclair9fa7c892024-06-29 21:13:48 +000099 PutInLet(item.inst, item.index, res);
dan sinclaird7504732024-06-27 22:10:24 +0000100 } else if (auto* val = As<core::ir::Constant>(item.val)) {
dan sinclair9fa7c892024-06-29 21:13:48 +0000101 auto* let = PutInLet(item.inst, item.index, val);
102 auto ret = HoistModuleScopeLetToConstruct(is_root_block, item.inst, let, val);
103 if (ret.has_value()) {
104 const_worklist.Insert(0, *ret);
105 }
106 }
107 }
108
109 // If any element in the constant is `struct` or `array` it needs to be pulled out
110 // into it's own `let`. That also means the `constant` value needs to turn into a
111 // `Construct`.
112 while (!const_worklist.IsEmpty()) {
113 auto item = const_worklist.Pop();
114
115 tint::Slice<core::ir::Value* const> args = item->Args();
116 for (size_t i = 0; i < args.Length(); ++i) {
117 auto ret = ProcessConstant(args[i], item, i);
118 if (ret.has_value()) {
119 const_worklist.Insert(0, *ret);
120 }
dan sinclaird7504732024-06-27 22:10:24 +0000121 }
122 }
123 }
124
dan sinclair9fa7c892024-06-29 21:13:48 +0000125 // Process a constant operand and replace if it's a struct initializer
126 std::optional<core::ir::Construct*> ProcessConstant(core::ir::Value* operand,
127 core::ir::Construct* parent,
128 size_t idx) {
129 auto* const_val = operand->As<core::ir::Constant>();
130 TINT_ASSERT(const_val);
131
132 if (!const_val->Type()->Is<core::type::Struct>()) {
133 return std::nullopt;
134 }
135
136 auto* let = b.Let(const_val->Type());
137
138 Vector<core::ir::Value*, 4> new_args = GatherArgs(const_val);
139
140 auto* construct = b.Construct(const_val->Type(), new_args);
141 let->SetValue(construct->Result(0));
142
143 // Put the `let` in before the `construct` value that we're based off of
144 let->InsertBefore(parent);
145 // Put the new `construct` in before the `let`.
146 construct->InsertBefore(let);
147
148 // Replace the argument in the originating `construct` with the new `let`.
149 parent->SetArg(idx, let->Result(0));
150
151 return {construct};
152 }
153
154 // Determine if this is a root block var which contains a struct initializer and, if
155 // so, setup the instruction for the needed replacement.
156 std::optional<core::ir::Construct*> HoistModuleScopeLetToConstruct(bool is_root_block,
157 core::ir::Instruction* inst,
158 core::ir::Let* let,
159 core::ir::Constant* val) {
160 // Only care about root-block variables
161 if (!is_root_block || !inst->Is<core::ir::Var>()) {
162 return std::nullopt;
163 }
164 // Only care about struct constants
165 if (!val->Type()->Is<core::type::Struct>()) {
166 return std::nullopt;
167 }
168
169 // This may not actually need to be a `construct` but pull it out now to
170 // make further changes, if they're necessary, easier.
171 Vector<core::ir::Value*, 4> args = GatherArgs(val);
172
173 // Turn the `constant` into a `construct` call and replace the value of the `let` that
174 // was created.
175 auto* construct = b.Construct(val->Type(), args);
176 let->SetValue(construct->Result(0));
177 construct->InsertBefore(let);
178
179 return {construct};
180 }
181
182 // Gather the arguments to the constant and create a `ir::Value` array from them which can
183 // be used in a `construct`.
184 Vector<core::ir::Value*, 4> GatherArgs(core::ir::Constant* val) {
185 Vector<core::ir::Value*, 4> args;
186 if (auto* const_val = val->Value()->As<core::constant::Composite>()) {
187 for (auto v : const_val->elements) {
188 args.Push(b.Constant(v));
189 }
190 } else if (auto* splat_val = val->Value()->As<core::constant::Splat>()) {
191 args.Push(b.Constant(splat_val->el));
192 }
193 return args;
194 }
195
dan sinclaird7504732024-06-27 22:10:24 +0000196 core::ir::Let* MakeLet(core::ir::Value* value) {
197 auto* let = b.Let(value->Type());
dan sinclaird7504732024-06-27 22:10:24 +0000198 let->SetValue(value);
199
200 auto name = b.ir.NameOf(value);
201 if (name.IsValid()) {
202 b.ir.SetName(let->Result(0), name);
203 b.ir.ClearName(value);
204 }
205 return let;
206 }
207
dan sinclair9fa7c892024-06-29 21:13:48 +0000208 core::ir::Let* PutInLet(core::ir::Instruction* inst, size_t index, core::ir::Value* value) {
dan sinclaird7504732024-06-27 22:10:24 +0000209 auto* let = MakeLet(value);
210 let->InsertBefore(inst);
dan sinclaird7504732024-06-27 22:10:24 +0000211
dan sinclair9fa7c892024-06-29 21:13:48 +0000212 inst->SetOperand(index, let->Result(0));
213 return let;
dan sinclaird7504732024-06-27 22:10:24 +0000214 }
215};
216
217} // namespace
218
219Result<SuccessType> PromoteInitializers(core::ir::Module& ir) {
220 auto result = ValidateAndDumpIfNeeded(ir, "PromoteInitializers transform");
221 if (result != Success) {
222 return result;
223 }
224
225 State{ir}.Process();
226
227 return Success;
228}
229
230} // namespace tint::hlsl::writer::raise