blob: 30b3982da18ee95d7d9cd8b382af005637cc40dc [file] [log] [blame]
Austin Engcc2516a2023-10-17 20:57:54 +00001// Copyright 2022 The Dawn & Tint Authors
James Price62515982022-11-09 15:40:06 +00002//
Austin Engcc2516a2023-10-17 20:57:54 +00003// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
James Price62515982022-11-09 15:40:06 +00005//
Austin Engcc2516a2023-10-17 20:57:54 +00006// 1. Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
James Price62515982022-11-09 15:40:06 +00008//
Austin Engcc2516a2023-10-17 20:57:54 +00009// 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.
James Price62515982022-11-09 15:40:06 +000027
dan sinclair99181d82023-07-20 01:14:15 +000028#include "src/tint/lang/wgsl/ast/transform/demote_to_helper.h"
James Price62515982022-11-09 15:40:06 +000029
James Price8cd34f82022-11-14 20:30:38 +000030#include <unordered_map>
James Price62515982022-11-09 15:40:06 +000031#include <unordered_set>
32#include <utility>
33
dan sinclair352f8c82023-07-21 00:40:07 +000034#include "src/tint/lang/core/type/reference.h"
Ben Clayton7dc86192023-08-01 00:37:35 +000035#include "src/tint/lang/wgsl/ast/transform/hoist_to_decl_before.h"
Ben Claytonae18c412023-07-29 13:00:40 +000036#include "src/tint/lang/wgsl/program/clone_context.h"
dan sinclair96db5542023-07-20 09:21:10 +000037#include "src/tint/lang/wgsl/program/program_builder.h"
Ben Clayton915ceca2023-07-29 13:12:58 +000038#include "src/tint/lang/wgsl/resolver/resolve.h"
dan sinclaird3b13692023-07-20 01:14:15 +000039#include "src/tint/lang/wgsl/sem/block_statement.h"
40#include "src/tint/lang/wgsl/sem/call.h"
41#include "src/tint/lang/wgsl/sem/function.h"
42#include "src/tint/lang/wgsl/sem/statement.h"
dan sinclair22b4dd22023-07-21 00:40:07 +000043#include "src/tint/utils/containers/map.h"
44#include "src/tint/utils/rtti/switch.h"
James Price62515982022-11-09 15:40:06 +000045
James Priceb4acbb82023-05-11 21:27:16 +000046TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::DemoteToHelper);
James Price62515982022-11-09 15:40:06 +000047
dan sinclairce6dffe2023-08-14 21:01:40 +000048using namespace tint::core::number_suffixes; // NOLINT
James Price62515982022-11-09 15:40:06 +000049
James Priceb4acbb82023-05-11 21:27:16 +000050namespace tint::ast::transform {
James Price62515982022-11-09 15:40:06 +000051
52DemoteToHelper::DemoteToHelper() = default;
53
54DemoteToHelper::~DemoteToHelper() = default;
55
Ben Clayton5ed5cc42023-09-22 10:31:04 +000056Transform::ApplyResult DemoteToHelper::Apply(const Program& src, const DataMap&, DataMap&) const {
57 auto& sem = src.Sem();
James Price62515982022-11-09 15:40:06 +000058
59 // Collect the set of functions that need to be processed.
60 // A function needs to be processed if it is reachable by a shader that contains a discard at
61 // any point in its call hierarchy.
62 std::unordered_set<const sem::Function*> functions_to_process;
Ben Clayton5ed5cc42023-09-22 10:31:04 +000063 for (auto* func : src.AST().Functions()) {
James Price62515982022-11-09 15:40:06 +000064 if (!func->IsEntryPoint()) {
65 continue;
66 }
67
68 // Determine whether this entry point and its callees need to be transformed.
69 bool needs_transform = false;
70 if (sem.Get(func)->DiscardStatement()) {
71 needs_transform = true;
72 } else {
73 for (auto* callee : sem.Get(func)->TransitivelyCalledFunctions()) {
74 if (callee->DiscardStatement()) {
75 needs_transform = true;
76 break;
77 }
78 }
79 }
80 if (!needs_transform) {
81 continue;
82 }
83
84 // Process the entry point and its callees.
85 functions_to_process.insert(sem.Get(func));
86 for (auto* callee : sem.Get(func)->TransitivelyCalledFunctions()) {
87 functions_to_process.insert(callee);
88 }
89 }
90
91 if (functions_to_process.empty()) {
92 return SkipTransform;
93 }
94
95 ProgramBuilder b;
Ben Clayton5ed5cc42023-09-22 10:31:04 +000096 program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
James Price62515982022-11-09 15:40:06 +000097
98 // Create a module-scope flag that indicates whether the current invocation has been discarded.
99 auto flag = b.Symbols().New("tint_discarded");
Ben Claytoncd52f382023-08-07 13:11:08 +0000100 b.GlobalVar(flag, core::AddressSpace::kPrivate, b.Expr(false));
James Price62515982022-11-09 15:40:06 +0000101
102 // Replace all discard statements with a statement that marks the invocation as discarded.
James Price2b7406a2023-05-12 01:43:50 +0000103 ctx.ReplaceAll(
104 [&](const DiscardStatement*) -> const Statement* { return b.Assign(flag, b.Expr(true)); });
James Price62515982022-11-09 15:40:06 +0000105
106 // Insert a conditional discard at the end of each entry point that does not end with a return.
107 for (auto* func : functions_to_process) {
108 if (func->Declaration()->IsEntryPoint()) {
109 auto* sem_body = sem.Get(func->Declaration()->body);
110 if (sem_body->Behaviors().Contains(sem::Behavior::kNext)) {
111 ctx.InsertBack(func->Declaration()->body->statements,
112 b.If(flag, b.Block(b.Discard())));
113 }
114 }
115 }
116
117 HoistToDeclBefore hoist_to_decl_before(ctx);
118
119 // Mask all writes to host-visible memory using the discarded flag.
120 // We also insert a discard statement before all return statements in entry points for shaders
121 // that discard.
dan sinclaircedcdf32023-08-10 02:39:48 +0000122 std::unordered_map<const core::type::Type*, Symbol> atomic_cmpxchg_result_types;
Ben Clayton5ed5cc42023-09-22 10:31:04 +0000123 for (auto* node : src.ASTNodes().Objects()) {
James Price62515982022-11-09 15:40:06 +0000124 Switch(
125 node,
126
127 // Mask assignments to storage buffer variables.
James Price2b7406a2023-05-12 01:43:50 +0000128 [&](const AssignmentStatement* assign) {
James Price62515982022-11-09 15:40:06 +0000129 // Skip writes in functions that are not called from shaders that discard.
130 auto* func = sem.Get(assign)->Function();
131 if (functions_to_process.count(func) == 0) {
132 return;
133 }
134
James Price78ae4c22022-11-09 18:27:12 +0000135 // Skip phony assignments.
James Price2b7406a2023-05-12 01:43:50 +0000136 if (assign->lhs->Is<PhonyExpression>()) {
James Price78ae4c22022-11-09 18:27:12 +0000137 return;
138 }
139
James Price62515982022-11-09 15:40:06 +0000140 // Skip writes to invocation-private address spaces.
dan sinclaircedcdf32023-08-10 02:39:48 +0000141 auto* ref = sem.GetVal(assign->lhs)->Type()->As<core::type::Reference>();
James Price62515982022-11-09 15:40:06 +0000142 switch (ref->AddressSpace()) {
Ben Claytoncd52f382023-08-07 13:11:08 +0000143 case core::AddressSpace::kStorage:
James Price62515982022-11-09 15:40:06 +0000144 // Need to mask these.
145 break;
Ben Claytoncd52f382023-08-07 13:11:08 +0000146 case core::AddressSpace::kFunction:
147 case core::AddressSpace::kPrivate:
148 case core::AddressSpace::kOut:
James Price62515982022-11-09 15:40:06 +0000149 // Skip these.
150 return;
151 default:
Ben Claytonf848af22023-07-28 16:37:32 +0000152 TINT_UNREACHABLE()
James Price62515982022-11-09 15:40:06 +0000153 << "write to unhandled address space: " << ref->AddressSpace();
154 }
155
James Priceb8271112024-03-15 16:34:40 +0000156 // If the RHS has side effects (which may contain derivative operations), we need to
157 // hoist it out to a separate declaration so that it does not get masked.
158 auto* rhs = sem.GetVal(assign->rhs);
159 if (rhs->HasSideEffects()) {
160 hoist_to_decl_before.Add(rhs, assign->rhs,
161 HoistToDeclBefore::VariableKind::kLet);
162 }
163
James Price62515982022-11-09 15:40:06 +0000164 // Mask the assignment using the invocation-discarded flag.
165 ctx.Replace(assign, b.If(b.Not(flag), b.Block(ctx.Clone(assign))));
166 },
167
168 // Mask builtins that write to host-visible memory.
James Price2b7406a2023-05-12 01:43:50 +0000169 [&](const CallExpression* call) {
James Price62515982022-11-09 15:40:06 +0000170 auto* sem_call = sem.Get<sem::Call>(call);
171 auto* stmt = sem_call ? sem_call->Stmt() : nullptr;
172 auto* func = stmt ? stmt->Function() : nullptr;
Ben Claytond9766dc2023-09-21 12:41:20 +0000173 auto* builtin = sem_call ? sem_call->Target()->As<sem::BuiltinFn>() : nullptr;
James Price62515982022-11-09 15:40:06 +0000174 if (functions_to_process.count(func) == 0 || !builtin) {
175 return;
176 }
177
Ben Claytondfc815c2023-09-25 15:38:43 +0000178 if (builtin->Fn() == wgsl::BuiltinFn::kTextureStore) {
James Price62515982022-11-09 15:40:06 +0000179 // A call to textureStore() will always be a statement.
180 // Wrap it inside a conditional block.
181 auto* masked_call = b.If(b.Not(flag), b.Block(ctx.Clone(stmt->Declaration())));
182 ctx.Replace(stmt->Declaration(), masked_call);
Ben Claytondfc815c2023-09-25 15:38:43 +0000183 } else if (builtin->IsAtomic() && builtin->Fn() != wgsl::BuiltinFn::kAtomicLoad) {
James Price62515982022-11-09 15:40:06 +0000184 // A call to an atomic builtin can be a statement or an expression.
James Price2b7406a2023-05-12 01:43:50 +0000185 if (auto* call_stmt = stmt->Declaration()->As<CallStatement>();
James Price62515982022-11-09 15:40:06 +0000186 call_stmt && call_stmt->expr == call) {
187 // This call is a statement.
188 // Wrap it inside a conditional block.
189 auto* masked_call = b.If(b.Not(flag), b.Block(ctx.Clone(call_stmt)));
190 ctx.Replace(stmt->Declaration(), masked_call);
191 } else {
192 // This call is an expression.
193 // We transform:
194 // let y = x + atomicAdd(&p, 1);
195 // Into:
196 // var tmp : i32;
197 // if (!tint_discarded) {
198 // tmp = atomicAdd(&p, 1);
199 // }
200 // let y = x + tmp;
201 auto result = b.Sym();
Ben Claytonf83de0f2024-02-03 05:14:32 +0000202 auto result_ty = CreateASTTypeFor(ctx, sem_call->Type());
203 auto* masked_call =
204 b.If(b.Not(flag),
205 b.Block(b.Assign(result, ctx.CloneWithoutTransform(call))));
James Price8cd34f82022-11-14 20:30:38 +0000206 auto* result_decl = b.Decl(b.Var(result, result_ty));
James Price62515982022-11-09 15:40:06 +0000207 hoist_to_decl_before.Prepare(sem_call);
208 hoist_to_decl_before.InsertBefore(stmt, result_decl);
209 hoist_to_decl_before.InsertBefore(stmt, masked_call);
210 ctx.Replace(call, b.Expr(result));
211 }
212 }
213 },
214
215 // Insert a conditional discard before all return statements in entry points.
James Price2b7406a2023-05-12 01:43:50 +0000216 [&](const ReturnStatement* ret) {
James Price62515982022-11-09 15:40:06 +0000217 auto* func = sem.Get(ret)->Function();
218 if (func->Declaration()->IsEntryPoint() && functions_to_process.count(func)) {
219 auto* discard = b.If(flag, b.Block(b.Discard()));
220 ctx.InsertBefore(sem.Get(ret)->Block()->Declaration()->statements, ret,
221 discard);
222 }
223 });
224 }
225
226 ctx.Clone();
Ben Clayton915ceca2023-07-29 13:12:58 +0000227 return resolver::Resolve(b);
James Price62515982022-11-09 15:40:06 +0000228}
229
James Priceb4acbb82023-05-11 21:27:16 +0000230} // namespace tint::ast::transform