1 //
2 // Copyright (C) 2017-2018 Google, Inc.
3 // Copyright (C) 2017 LunarG, Inc.
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 // Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //
14 // Redistributions in binary form must reproduce the above
15 // copyright notice, this list of conditions and the following
16 // disclaimer in the documentation and/or other materials provided
17 // with the distribution.
18 //
19 // Neither the name of 3Dlabs Inc. Ltd. nor the names of its
20 // contributors may be used to endorse or promote products derived
21 // from this software without specific prior written permission.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 // POSSIBILITY OF SUCH DAMAGE.
35 //
36
37 #include "hlslParseHelper.h"
38 #include "hlslScanContext.h"
39 #include "hlslGrammar.h"
40 #include "hlslAttributes.h"
41
42 #include "../Include/Common.h"
43 #include "../MachineIndependent/Scan.h"
44 #include "../MachineIndependent/preprocessor/PpContext.h"
45
46 #include "../OSDependent/osinclude.h"
47
48 #include <algorithm>
49 #include <functional>
50 #include <cctype>
51 #include <array>
52 #include <set>
53
54 namespace glslang {
55
HlslParseContext(TSymbolTable & symbolTable,TIntermediate & interm,bool parsingBuiltins,int version,EProfile profile,const SpvVersion & spvVersion,EShLanguage language,TInfoSink & infoSink,const TString sourceEntryPointName,bool forwardCompatible,EShMessages messages)56 HlslParseContext::HlslParseContext(TSymbolTable& symbolTable, TIntermediate& interm, bool parsingBuiltins,
57 int version, EProfile profile, const SpvVersion& spvVersion, EShLanguage language,
58 TInfoSink& infoSink,
59 const TString sourceEntryPointName,
60 bool forwardCompatible, EShMessages messages) :
61 TParseContextBase(symbolTable, interm, parsingBuiltins, version, profile, spvVersion, language, infoSink,
62 forwardCompatible, messages, &sourceEntryPointName),
63 annotationNestingLevel(0),
64 inputPatch(nullptr),
65 nextInLocation(0), nextOutLocation(0),
66 entryPointFunction(nullptr),
67 entryPointFunctionBody(nullptr),
68 gsStreamOutput(nullptr),
69 clipDistanceOutput(nullptr),
70 cullDistanceOutput(nullptr),
71 clipDistanceInput(nullptr),
72 cullDistanceInput(nullptr),
73 parsingEntrypointParameters(false)
74 {
75 globalUniformDefaults.clear();
76 globalUniformDefaults.layoutMatrix = ElmRowMajor;
77 globalUniformDefaults.layoutPacking = ElpStd140;
78
79 globalBufferDefaults.clear();
80 globalBufferDefaults.layoutMatrix = ElmRowMajor;
81 globalBufferDefaults.layoutPacking = ElpStd430;
82
83 globalInputDefaults.clear();
84 globalOutputDefaults.clear();
85
86 clipSemanticNSizeIn.fill(0);
87 cullSemanticNSizeIn.fill(0);
88 clipSemanticNSizeOut.fill(0);
89 cullSemanticNSizeOut.fill(0);
90
91 // "Shaders in the transform
92 // feedback capturing mode have an initial global default of
93 // layout(xfb_buffer = 0) out;"
94 if (language == EShLangVertex ||
95 language == EShLangTessControl ||
96 language == EShLangTessEvaluation ||
97 language == EShLangGeometry)
98 globalOutputDefaults.layoutXfbBuffer = 0;
99
100 if (language == EShLangGeometry)
101 globalOutputDefaults.layoutStream = 0;
102 }
103
~HlslParseContext()104 HlslParseContext::~HlslParseContext()
105 {
106 }
107
initializeExtensionBehavior()108 void HlslParseContext::initializeExtensionBehavior()
109 {
110 TParseContextBase::initializeExtensionBehavior();
111
112 // HLSL allows #line by default.
113 extensionBehavior[E_GL_GOOGLE_cpp_style_line_directive] = EBhEnable;
114 }
115
setLimits(const TBuiltInResource & r)116 void HlslParseContext::setLimits(const TBuiltInResource& r)
117 {
118 resources = r;
119 intermediate.setLimits(resources);
120 }
121
122 //
123 // Parse an array of strings using the parser in HlslRules.
124 //
125 // Returns true for successful acceptance of the shader, false if any errors.
126 //
parseShaderStrings(TPpContext & ppContext,TInputScanner & input,bool versionWillBeError)127 bool HlslParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner& input, bool versionWillBeError)
128 {
129 currentScanner = &input;
130 ppContext.setInput(input, versionWillBeError);
131
132 HlslScanContext scanContext(*this, ppContext);
133 HlslGrammar grammar(scanContext, *this);
134 if (!grammar.parse()) {
135 // Print a message formated such that if you click on the message it will take you right to
136 // the line through most UIs.
137 const glslang::TSourceLoc& sourceLoc = input.getSourceLoc();
138 infoSink.info << sourceLoc.getFilenameStr() << "(" << sourceLoc.line << "): error at column " << sourceLoc.column
139 << ", HLSL parsing failed.\n";
140 ++numErrors;
141 return false;
142 }
143
144 finish();
145
146 return numErrors == 0;
147 }
148
149 //
150 // Return true if this l-value node should be converted in some manner.
151 // For instance: turning a load aggregate into a store in an l-value.
152 //
shouldConvertLValue(const TIntermNode * node) const153 bool HlslParseContext::shouldConvertLValue(const TIntermNode* node) const
154 {
155 if (node == nullptr || node->getAsTyped() == nullptr)
156 return false;
157
158 const TIntermAggregate* lhsAsAggregate = node->getAsAggregate();
159 const TIntermBinary* lhsAsBinary = node->getAsBinaryNode();
160
161 // If it's a swizzled/indexed aggregate, look at the left node instead.
162 if (lhsAsBinary != nullptr &&
163 (lhsAsBinary->getOp() == EOpVectorSwizzle || lhsAsBinary->getOp() == EOpIndexDirect))
164 lhsAsAggregate = lhsAsBinary->getLeft()->getAsAggregate();
165 if (lhsAsAggregate != nullptr && lhsAsAggregate->getOp() == EOpImageLoad)
166 return true;
167
168 return false;
169 }
170
growGlobalUniformBlock(const TSourceLoc & loc,TType & memberType,const TString & memberName,TTypeList * newTypeList)171 void HlslParseContext::growGlobalUniformBlock(const TSourceLoc& loc, TType& memberType, const TString& memberName,
172 TTypeList* newTypeList)
173 {
174 newTypeList = nullptr;
175 correctUniform(memberType.getQualifier());
176 if (memberType.isStruct()) {
177 auto it = ioTypeMap.find(memberType.getStruct());
178 if (it != ioTypeMap.end() && it->second.uniform)
179 newTypeList = it->second.uniform;
180 }
181 TParseContextBase::growGlobalUniformBlock(loc, memberType, memberName, newTypeList);
182 }
183
184 //
185 // Return a TLayoutFormat corresponding to the given texture type.
186 //
getLayoutFromTxType(const TSourceLoc & loc,const TType & txType)187 TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const TType& txType)
188 {
189 if (txType.isStruct()) {
190 // TODO: implement.
191 error(loc, "unimplemented: structure type in image or buffer", "", "");
192 return ElfNone;
193 }
194
195 const int components = txType.getVectorSize();
196 const TBasicType txBasicType = txType.getBasicType();
197
198 const auto selectFormat = [this,&components](TLayoutFormat v1, TLayoutFormat v2, TLayoutFormat v4) -> TLayoutFormat {
199 if (intermediate.getNoStorageFormat())
200 return ElfNone;
201
202 return components == 1 ? v1 :
203 components == 2 ? v2 : v4;
204 };
205
206 switch (txBasicType) {
207 case EbtFloat: return selectFormat(ElfR32f, ElfRg32f, ElfRgba32f);
208 case EbtInt: return selectFormat(ElfR32i, ElfRg32i, ElfRgba32i);
209 case EbtUint: return selectFormat(ElfR32ui, ElfRg32ui, ElfRgba32ui);
210 default:
211 error(loc, "unknown basic type in image format", "", "");
212 return ElfNone;
213 }
214 }
215
216 //
217 // Both test and if necessary, spit out an error, to see if the node is really
218 // an l-value that can be operated on this way.
219 //
220 // Returns true if there was an error.
221 //
lValueErrorCheck(const TSourceLoc & loc,const char * op,TIntermTyped * node)222 bool HlslParseContext::lValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
223 {
224 if (shouldConvertLValue(node)) {
225 // if we're writing to a texture, it must be an RW form.
226
227 TIntermAggregate* lhsAsAggregate = node->getAsAggregate();
228 TIntermTyped* object = lhsAsAggregate->getSequence()[0]->getAsTyped();
229
230 if (!object->getType().getSampler().isImage()) {
231 error(loc, "operator[] on a non-RW texture must be an r-value", "", "");
232 return true;
233 }
234 }
235
236 // We tolerate samplers as l-values, even though they are nominally
237 // illegal, because we expect a later optimization to eliminate them.
238 if (node->getType().getBasicType() == EbtSampler) {
239 intermediate.setNeedsLegalization();
240 return false;
241 }
242
243 // Let the base class check errors
244 return TParseContextBase::lValueErrorCheck(loc, op, node);
245 }
246
247 //
248 // This function handles l-value conversions and verifications. It uses, but is not synonymous
249 // with lValueErrorCheck. That function accepts an l-value directly, while this one must be
250 // given the surrounding tree - e.g, with an assignment, so we can convert the assign into a
251 // series of other image operations.
252 //
253 // Most things are passed through unmodified, except for error checking.
254 //
handleLvalue(const TSourceLoc & loc,const char * op,TIntermTyped * & node)255 TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char* op, TIntermTyped*& node)
256 {
257 if (node == nullptr)
258 return nullptr;
259
260 TIntermBinary* nodeAsBinary = node->getAsBinaryNode();
261 TIntermUnary* nodeAsUnary = node->getAsUnaryNode();
262 TIntermAggregate* sequence = nullptr;
263
264 TIntermTyped* lhs = nodeAsUnary ? nodeAsUnary->getOperand() :
265 nodeAsBinary ? nodeAsBinary->getLeft() :
266 nullptr;
267
268 // Early bail out if there is no conversion to apply
269 if (!shouldConvertLValue(lhs)) {
270 if (lhs != nullptr)
271 if (lValueErrorCheck(loc, op, lhs))
272 return nullptr;
273 return node;
274 }
275
276 // *** If we get here, we're going to apply some conversion to an l-value.
277
278 // Helper to create a load.
279 const auto makeLoad = [&](TIntermSymbol* rhsTmp, TIntermTyped* object, TIntermTyped* coord, const TType& derefType) {
280 TIntermAggregate* loadOp = new TIntermAggregate(EOpImageLoad);
281 loadOp->setLoc(loc);
282 loadOp->getSequence().push_back(object);
283 loadOp->getSequence().push_back(intermediate.addSymbol(*coord->getAsSymbolNode()));
284 loadOp->setType(derefType);
285
286 sequence = intermediate.growAggregate(sequence,
287 intermediate.addAssign(EOpAssign, rhsTmp, loadOp, loc),
288 loc);
289 };
290
291 // Helper to create a store.
292 const auto makeStore = [&](TIntermTyped* object, TIntermTyped* coord, TIntermSymbol* rhsTmp) {
293 TIntermAggregate* storeOp = new TIntermAggregate(EOpImageStore);
294 storeOp->getSequence().push_back(object);
295 storeOp->getSequence().push_back(coord);
296 storeOp->getSequence().push_back(intermediate.addSymbol(*rhsTmp));
297 storeOp->setLoc(loc);
298 storeOp->setType(TType(EbtVoid));
299
300 sequence = intermediate.growAggregate(sequence, storeOp);
301 };
302
303 // Helper to create an assign.
304 const auto makeBinary = [&](TOperator op, TIntermTyped* lhs, TIntermTyped* rhs) {
305 sequence = intermediate.growAggregate(sequence,
306 intermediate.addBinaryNode(op, lhs, rhs, loc, lhs->getType()),
307 loc);
308 };
309
310 // Helper to complete sequence by adding trailing variable, so we evaluate to the right value.
311 const auto finishSequence = [&](TIntermSymbol* rhsTmp, const TType& derefType) -> TIntermAggregate* {
312 // Add a trailing use of the temp, so the sequence returns the proper value.
313 sequence = intermediate.growAggregate(sequence, intermediate.addSymbol(*rhsTmp));
314 sequence->setOperator(EOpSequence);
315 sequence->setLoc(loc);
316 sequence->setType(derefType);
317
318 return sequence;
319 };
320
321 // Helper to add unary op
322 const auto makeUnary = [&](TOperator op, TIntermSymbol* rhsTmp) {
323 sequence = intermediate.growAggregate(sequence,
324 intermediate.addUnaryNode(op, intermediate.addSymbol(*rhsTmp), loc,
325 rhsTmp->getType()),
326 loc);
327 };
328
329 // Return true if swizzle or index writes all components of the given variable.
330 const auto writesAllComponents = [&](TIntermSymbol* var, TIntermBinary* swizzle) -> bool {
331 if (swizzle == nullptr) // not a swizzle or index
332 return true;
333
334 // Track which components are being set.
335 std::array<bool, 4> compIsSet;
336 compIsSet.fill(false);
337
338 const TIntermConstantUnion* asConst = swizzle->getRight()->getAsConstantUnion();
339 const TIntermAggregate* asAggregate = swizzle->getRight()->getAsAggregate();
340
341 // This could be either a direct index, or a swizzle.
342 if (asConst) {
343 compIsSet[asConst->getConstArray()[0].getIConst()] = true;
344 } else if (asAggregate) {
345 const TIntermSequence& seq = asAggregate->getSequence();
346 for (int comp=0; comp<int(seq.size()); ++comp)
347 compIsSet[seq[comp]->getAsConstantUnion()->getConstArray()[0].getIConst()] = true;
348 } else {
349 assert(0);
350 }
351
352 // Return true if all components are being set by the index or swizzle
353 return std::all_of(compIsSet.begin(), compIsSet.begin() + var->getType().getVectorSize(),
354 [](bool isSet) { return isSet; } );
355 };
356
357 // Create swizzle matching input swizzle
358 const auto addSwizzle = [&](TIntermSymbol* var, TIntermBinary* swizzle) -> TIntermTyped* {
359 if (swizzle)
360 return intermediate.addBinaryNode(swizzle->getOp(), var, swizzle->getRight(), loc, swizzle->getType());
361 else
362 return var;
363 };
364
365 TIntermBinary* lhsAsBinary = lhs->getAsBinaryNode();
366 TIntermAggregate* lhsAsAggregate = lhs->getAsAggregate();
367 bool lhsIsSwizzle = false;
368
369 // If it's a swizzled L-value, remember the swizzle, and use the LHS.
370 if (lhsAsBinary != nullptr && (lhsAsBinary->getOp() == EOpVectorSwizzle || lhsAsBinary->getOp() == EOpIndexDirect)) {
371 lhsAsAggregate = lhsAsBinary->getLeft()->getAsAggregate();
372 lhsIsSwizzle = true;
373 }
374
375 TIntermTyped* object = lhsAsAggregate->getSequence()[0]->getAsTyped();
376 TIntermTyped* coord = lhsAsAggregate->getSequence()[1]->getAsTyped();
377
378 const TSampler& texSampler = object->getType().getSampler();
379
380 TType objDerefType;
381 getTextureReturnType(texSampler, objDerefType);
382
383 if (nodeAsBinary) {
384 TIntermTyped* rhs = nodeAsBinary->getRight();
385 const TOperator assignOp = nodeAsBinary->getOp();
386
387 bool isModifyOp = false;
388
389 switch (assignOp) {
390 case EOpAddAssign:
391 case EOpSubAssign:
392 case EOpMulAssign:
393 case EOpVectorTimesMatrixAssign:
394 case EOpVectorTimesScalarAssign:
395 case EOpMatrixTimesScalarAssign:
396 case EOpMatrixTimesMatrixAssign:
397 case EOpDivAssign:
398 case EOpModAssign:
399 case EOpAndAssign:
400 case EOpInclusiveOrAssign:
401 case EOpExclusiveOrAssign:
402 case EOpLeftShiftAssign:
403 case EOpRightShiftAssign:
404 isModifyOp = true;
405 // fall through...
406 case EOpAssign:
407 {
408 // Since this is an lvalue, we'll convert an image load to a sequence like this
409 // (to still provide the value):
410 // OpSequence
411 // OpImageStore(object, lhs, rhs)
412 // rhs
413 // But if it's not a simple symbol RHS (say, a fn call), we don't want to duplicate the RHS,
414 // so we'll convert instead to this:
415 // OpSequence
416 // rhsTmp = rhs
417 // OpImageStore(object, coord, rhsTmp)
418 // rhsTmp
419 // If this is a read-modify-write op, like +=, we issue:
420 // OpSequence
421 // coordtmp = load's param1
422 // rhsTmp = OpImageLoad(object, coordTmp)
423 // rhsTmp op= rhs
424 // OpImageStore(object, coordTmp, rhsTmp)
425 // rhsTmp
426 //
427 // If the lvalue is swizzled, we apply that when writing the temp variable, like so:
428 // ...
429 // rhsTmp.some_swizzle = ...
430 // For partial writes, an error is generated.
431
432 TIntermSymbol* rhsTmp = rhs->getAsSymbolNode();
433 TIntermTyped* coordTmp = coord;
434
435 if (rhsTmp == nullptr || isModifyOp || lhsIsSwizzle) {
436 rhsTmp = makeInternalVariableNode(loc, "storeTemp", objDerefType);
437
438 // Partial updates not yet supported
439 if (!writesAllComponents(rhsTmp, lhsAsBinary)) {
440 error(loc, "unimplemented: partial image updates", "", "");
441 }
442
443 // Assign storeTemp = rhs
444 if (isModifyOp) {
445 // We have to make a temp var for the coordinate, to avoid evaluating it twice.
446 coordTmp = makeInternalVariableNode(loc, "coordTemp", coord->getType());
447 makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
448 makeLoad(rhsTmp, object, coordTmp, objDerefType); // rhsTmp = OpImageLoad(object, coordTmp)
449 }
450
451 // rhsTmp op= rhs.
452 makeBinary(assignOp, addSwizzle(intermediate.addSymbol(*rhsTmp), lhsAsBinary), rhs);
453 }
454
455 makeStore(object, coordTmp, rhsTmp); // add a store
456 return finishSequence(rhsTmp, objDerefType); // return rhsTmp from sequence
457 }
458
459 default:
460 break;
461 }
462 }
463
464 if (nodeAsUnary) {
465 const TOperator assignOp = nodeAsUnary->getOp();
466
467 switch (assignOp) {
468 case EOpPreIncrement:
469 case EOpPreDecrement:
470 {
471 // We turn this into:
472 // OpSequence
473 // coordtmp = load's param1
474 // rhsTmp = OpImageLoad(object, coordTmp)
475 // rhsTmp op
476 // OpImageStore(object, coordTmp, rhsTmp)
477 // rhsTmp
478
479 TIntermSymbol* rhsTmp = makeInternalVariableNode(loc, "storeTemp", objDerefType);
480 TIntermTyped* coordTmp = makeInternalVariableNode(loc, "coordTemp", coord->getType());
481
482 makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
483 makeLoad(rhsTmp, object, coordTmp, objDerefType); // rhsTmp = OpImageLoad(object, coordTmp)
484 makeUnary(assignOp, rhsTmp); // op rhsTmp
485 makeStore(object, coordTmp, rhsTmp); // OpImageStore(object, coordTmp, rhsTmp)
486 return finishSequence(rhsTmp, objDerefType); // return rhsTmp from sequence
487 }
488
489 case EOpPostIncrement:
490 case EOpPostDecrement:
491 {
492 // We turn this into:
493 // OpSequence
494 // coordtmp = load's param1
495 // rhsTmp1 = OpImageLoad(object, coordTmp)
496 // rhsTmp2 = rhsTmp1
497 // rhsTmp2 op
498 // OpImageStore(object, coordTmp, rhsTmp2)
499 // rhsTmp1 (pre-op value)
500 TIntermSymbol* rhsTmp1 = makeInternalVariableNode(loc, "storeTempPre", objDerefType);
501 TIntermSymbol* rhsTmp2 = makeInternalVariableNode(loc, "storeTempPost", objDerefType);
502 TIntermTyped* coordTmp = makeInternalVariableNode(loc, "coordTemp", coord->getType());
503
504 makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
505 makeLoad(rhsTmp1, object, coordTmp, objDerefType); // rhsTmp1 = OpImageLoad(object, coordTmp)
506 makeBinary(EOpAssign, rhsTmp2, rhsTmp1); // rhsTmp2 = rhsTmp1
507 makeUnary(assignOp, rhsTmp2); // rhsTmp op
508 makeStore(object, coordTmp, rhsTmp2); // OpImageStore(object, coordTmp, rhsTmp2)
509 return finishSequence(rhsTmp1, objDerefType); // return rhsTmp from sequence
510 }
511
512 default:
513 break;
514 }
515 }
516
517 if (lhs)
518 if (lValueErrorCheck(loc, op, lhs))
519 return nullptr;
520
521 return node;
522 }
523
handlePragma(const TSourceLoc & loc,const TVector<TString> & tokens)524 void HlslParseContext::handlePragma(const TSourceLoc& loc, const TVector<TString>& tokens)
525 {
526 if (pragmaCallback)
527 pragmaCallback(loc.line, tokens);
528
529 if (tokens.size() == 0)
530 return;
531
532 // These pragmas are case insensitive in HLSL, so we'll compare in lower case.
533 TVector<TString> lowerTokens = tokens;
534
535 for (auto it = lowerTokens.begin(); it != lowerTokens.end(); ++it)
536 std::transform(it->begin(), it->end(), it->begin(), ::tolower);
537
538 // Handle pack_matrix
539 if (tokens.size() == 4 && lowerTokens[0] == "pack_matrix" && tokens[1] == "(" && tokens[3] == ")") {
540 // Note that HLSL semantic order is Mrc, not Mcr like SPIR-V, so we reverse the sense.
541 // Row major becomes column major and vice versa.
542
543 if (lowerTokens[2] == "row_major") {
544 globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmColumnMajor;
545 } else if (lowerTokens[2] == "column_major") {
546 globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmRowMajor;
547 } else {
548 // unknown majorness strings are treated as (HLSL column major)==(SPIR-V row major)
549 warn(loc, "unknown pack_matrix pragma value", tokens[2].c_str(), "");
550 globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmRowMajor;
551 }
552 return;
553 }
554
555 // Handle once
556 if (lowerTokens[0] == "once") {
557 warn(loc, "not implemented", "#pragma once", "");
558 return;
559 }
560 }
561
562 //
563 // Look at a '.' matrix selector string and change it into components
564 // for a matrix. There are two types:
565 //
566 // _21 second row, first column (one based)
567 // _m21 third row, second column (zero based)
568 //
569 // Returns true if there is no error.
570 //
parseMatrixSwizzleSelector(const TSourceLoc & loc,const TString & fields,int cols,int rows,TSwizzleSelectors<TMatrixSelector> & components)571 bool HlslParseContext::parseMatrixSwizzleSelector(const TSourceLoc& loc, const TString& fields, int cols, int rows,
572 TSwizzleSelectors<TMatrixSelector>& components)
573 {
574 int startPos[MaxSwizzleSelectors];
575 int numComps = 0;
576 TString compString = fields;
577
578 // Find where each component starts,
579 // recording the first character position after the '_'.
580 for (size_t c = 0; c < compString.size(); ++c) {
581 if (compString[c] == '_') {
582 if (numComps >= MaxSwizzleSelectors) {
583 error(loc, "matrix component swizzle has too many components", compString.c_str(), "");
584 return false;
585 }
586 if (c > compString.size() - 3 ||
587 ((compString[c+1] == 'm' || compString[c+1] == 'M') && c > compString.size() - 4)) {
588 error(loc, "matrix component swizzle missing", compString.c_str(), "");
589 return false;
590 }
591 startPos[numComps++] = (int)c + 1;
592 }
593 }
594
595 // Process each component
596 for (int i = 0; i < numComps; ++i) {
597 int pos = startPos[i];
598 int bias = -1;
599 if (compString[pos] == 'm' || compString[pos] == 'M') {
600 bias = 0;
601 ++pos;
602 }
603 TMatrixSelector comp;
604 comp.coord1 = compString[pos+0] - '0' + bias;
605 comp.coord2 = compString[pos+1] - '0' + bias;
606 if (comp.coord1 < 0 || comp.coord1 >= cols) {
607 error(loc, "matrix row component out of range", compString.c_str(), "");
608 return false;
609 }
610 if (comp.coord2 < 0 || comp.coord2 >= rows) {
611 error(loc, "matrix column component out of range", compString.c_str(), "");
612 return false;
613 }
614 components.push_back(comp);
615 }
616
617 return true;
618 }
619
620 // If the 'comps' express a column of a matrix,
621 // return the column. Column means the first coords all match.
622 //
623 // Otherwise, return -1.
624 //
getMatrixComponentsColumn(int rows,const TSwizzleSelectors<TMatrixSelector> & selector)625 int HlslParseContext::getMatrixComponentsColumn(int rows, const TSwizzleSelectors<TMatrixSelector>& selector)
626 {
627 int col = -1;
628
629 // right number of comps?
630 if (selector.size() != rows)
631 return -1;
632
633 // all comps in the same column?
634 // rows in order?
635 col = selector[0].coord1;
636 for (int i = 0; i < rows; ++i) {
637 if (col != selector[i].coord1)
638 return -1;
639 if (i != selector[i].coord2)
640 return -1;
641 }
642
643 return col;
644 }
645
646 //
647 // Handle seeing a variable identifier in the grammar.
648 //
handleVariable(const TSourceLoc & loc,const TString * string)649 TIntermTyped* HlslParseContext::handleVariable(const TSourceLoc& loc, const TString* string)
650 {
651 int thisDepth;
652 TSymbol* symbol = symbolTable.find(*string, thisDepth);
653 if (symbol && symbol->getAsVariable() && symbol->getAsVariable()->isUserType()) {
654 error(loc, "expected symbol, not user-defined type", string->c_str(), "");
655 return nullptr;
656 }
657
658 const TVariable* variable = nullptr;
659 const TAnonMember* anon = symbol ? symbol->getAsAnonMember() : nullptr;
660 TIntermTyped* node = nullptr;
661 if (anon) {
662 // It was a member of an anonymous container, which could be a 'this' structure.
663
664 // Create a subtree for its dereference.
665 if (thisDepth > 0) {
666 variable = getImplicitThis(thisDepth);
667 if (variable == nullptr)
668 error(loc, "cannot access member variables (static member function?)", "this", "");
669 }
670 if (variable == nullptr)
671 variable = anon->getAnonContainer().getAsVariable();
672
673 TIntermTyped* container = intermediate.addSymbol(*variable, loc);
674 TIntermTyped* constNode = intermediate.addConstantUnion(anon->getMemberNumber(), loc);
675 node = intermediate.addIndex(EOpIndexDirectStruct, container, constNode, loc);
676
677 node->setType(*(*variable->getType().getStruct())[anon->getMemberNumber()].type);
678 if (node->getType().hiddenMember())
679 error(loc, "member of nameless block was not redeclared", string->c_str(), "");
680 } else {
681 // Not a member of an anonymous container.
682
683 // The symbol table search was done in the lexical phase.
684 // See if it was a variable.
685 variable = symbol ? symbol->getAsVariable() : nullptr;
686 if (variable) {
687 if ((variable->getType().getBasicType() == EbtBlock ||
688 variable->getType().getBasicType() == EbtStruct) && variable->getType().getStruct() == nullptr) {
689 error(loc, "cannot be used (maybe an instance name is needed)", string->c_str(), "");
690 variable = nullptr;
691 }
692 } else {
693 if (symbol)
694 error(loc, "variable name expected", string->c_str(), "");
695 }
696
697 // Recovery, if it wasn't found or was not a variable.
698 if (variable == nullptr) {
699 error(loc, "unknown variable", string->c_str(), "");
700 variable = new TVariable(string, TType(EbtVoid));
701 }
702
703 if (variable->getType().getQualifier().isFrontEndConstant())
704 node = intermediate.addConstantUnion(variable->getConstArray(), variable->getType(), loc);
705 else
706 node = intermediate.addSymbol(*variable, loc);
707 }
708
709 if (variable->getType().getQualifier().isIo())
710 intermediate.addIoAccessed(*string);
711
712 return node;
713 }
714
715 //
716 // Handle operator[] on any objects it applies to. Currently:
717 // Textures
718 // Buffers
719 //
handleBracketOperator(const TSourceLoc & loc,TIntermTyped * base,TIntermTyped * index)720 TIntermTyped* HlslParseContext::handleBracketOperator(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
721 {
722 // handle r-value operator[] on textures and images. l-values will be processed later.
723 if (base->getType().getBasicType() == EbtSampler && !base->isArray()) {
724 const TSampler& sampler = base->getType().getSampler();
725 if (sampler.isImage() || sampler.isTexture()) {
726 if (! mipsOperatorMipArg.empty() && mipsOperatorMipArg.back().mipLevel == nullptr) {
727 // The first operator[] to a .mips[] sequence is the mip level. We'll remember it.
728 mipsOperatorMipArg.back().mipLevel = index;
729 return base; // next [] index is to the same base.
730 } else {
731 TIntermAggregate* load = new TIntermAggregate(sampler.isImage() ? EOpImageLoad : EOpTextureFetch);
732
733 TType sampReturnType;
734 getTextureReturnType(sampler, sampReturnType);
735
736 load->setType(sampReturnType);
737 load->setLoc(loc);
738 load->getSequence().push_back(base);
739 load->getSequence().push_back(index);
740
741 // Textures need a MIP. If we saw one go by, use it. Otherwise, use zero.
742 if (sampler.isTexture()) {
743 if (! mipsOperatorMipArg.empty()) {
744 load->getSequence().push_back(mipsOperatorMipArg.back().mipLevel);
745 mipsOperatorMipArg.pop_back();
746 } else {
747 load->getSequence().push_back(intermediate.addConstantUnion(0, loc, true));
748 }
749 }
750
751 return load;
752 }
753 }
754 }
755
756 // Handle operator[] on structured buffers: this indexes into the array element of the buffer.
757 // indexStructBufferContent returns nullptr if it isn't a structuredbuffer (SSBO).
758 TIntermTyped* sbArray = indexStructBufferContent(loc, base);
759 if (sbArray != nullptr) {
760 // Now we'll apply the [] index to that array
761 const TOperator idxOp = (index->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect;
762
763 TIntermTyped* element = intermediate.addIndex(idxOp, sbArray, index, loc);
764 const TType derefType(sbArray->getType(), 0);
765 element->setType(derefType);
766 return element;
767 }
768
769 return nullptr;
770 }
771
772 //
773 // Cast index value to a uint if it isn't already (for operator[], load indexes, etc)
makeIntegerIndex(TIntermTyped * index)774 TIntermTyped* HlslParseContext::makeIntegerIndex(TIntermTyped* index)
775 {
776 const TBasicType indexBasicType = index->getType().getBasicType();
777 const int vecSize = index->getType().getVectorSize();
778
779 // We can use int types directly as the index
780 if (indexBasicType == EbtInt || indexBasicType == EbtUint ||
781 indexBasicType == EbtInt64 || indexBasicType == EbtUint64)
782 return index;
783
784 // Cast index to unsigned integer if it isn't one.
785 return intermediate.addConversion(EOpConstructUint, TType(EbtUint, EvqTemporary, vecSize), index);
786 }
787
788 //
789 // Handle seeing a base[index] dereference in the grammar.
790 //
handleBracketDereference(const TSourceLoc & loc,TIntermTyped * base,TIntermTyped * index)791 TIntermTyped* HlslParseContext::handleBracketDereference(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
792 {
793 index = makeIntegerIndex(index);
794
795 if (index == nullptr) {
796 error(loc, " unknown index type ", "", "");
797 return nullptr;
798 }
799
800 TIntermTyped* result = handleBracketOperator(loc, base, index);
801
802 if (result != nullptr)
803 return result; // it was handled as an operator[]
804
805 bool flattened = false;
806 int indexValue = 0;
807 if (index->getQualifier().isFrontEndConstant())
808 indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst();
809
810 variableCheck(base);
811 if (! base->isArray() && ! base->isMatrix() && ! base->isVector()) {
812 if (base->getAsSymbolNode())
813 error(loc, " left of '[' is not of type array, matrix, or vector ",
814 base->getAsSymbolNode()->getName().c_str(), "");
815 else
816 error(loc, " left of '[' is not of type array, matrix, or vector ", "expression", "");
817 } else if (base->getType().getQualifier().isFrontEndConstant() &&
818 index->getQualifier().isFrontEndConstant()) {
819 // both base and index are front-end constants
820 checkIndex(loc, base->getType(), indexValue);
821 return intermediate.foldDereference(base, indexValue, loc);
822 } else {
823 // at least one of base and index is variable...
824
825 if (index->getQualifier().isFrontEndConstant())
826 checkIndex(loc, base->getType(), indexValue);
827
828 if (base->getType().isScalarOrVec1())
829 result = base;
830 else if (base->getAsSymbolNode() && wasFlattened(base)) {
831 if (index->getQualifier().storage != EvqConst)
832 error(loc, "Invalid variable index to flattened array", base->getAsSymbolNode()->getName().c_str(), "");
833
834 result = flattenAccess(base, indexValue);
835 flattened = (result != base);
836 } else {
837 if (index->getQualifier().isFrontEndConstant()) {
838 if (base->getType().isUnsizedArray())
839 base->getWritableType().updateImplicitArraySize(indexValue + 1);
840 else
841 checkIndex(loc, base->getType(), indexValue);
842 result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
843 } else
844 result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
845 }
846 }
847
848 if (result == nullptr) {
849 // Insert dummy error-recovery result
850 result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
851 } else {
852 // If the array reference was flattened, it has the correct type. E.g, if it was
853 // a uniform array, it was flattened INTO a set of scalar uniforms, not scalar temps.
854 // In that case, we preserve the qualifiers.
855 if (!flattened) {
856 // Insert valid dereferenced result
857 TType newType(base->getType(), 0); // dereferenced type
858 if (base->getType().getQualifier().storage == EvqConst && index->getQualifier().storage == EvqConst)
859 newType.getQualifier().storage = EvqConst;
860 else
861 newType.getQualifier().storage = EvqTemporary;
862 result->setType(newType);
863 }
864 }
865
866 return result;
867 }
868
869 // Handle seeing a binary node with a math operation.
handleBinaryMath(const TSourceLoc & loc,const char * str,TOperator op,TIntermTyped * left,TIntermTyped * right)870 TIntermTyped* HlslParseContext::handleBinaryMath(const TSourceLoc& loc, const char* str, TOperator op,
871 TIntermTyped* left, TIntermTyped* right)
872 {
873 TIntermTyped* result = intermediate.addBinaryMath(op, left, right, loc);
874 if (result == nullptr)
875 binaryOpError(loc, str, left->getCompleteString(), right->getCompleteString());
876
877 return result;
878 }
879
880 // Handle seeing a unary node with a math operation.
handleUnaryMath(const TSourceLoc & loc,const char * str,TOperator op,TIntermTyped * childNode)881 TIntermTyped* HlslParseContext::handleUnaryMath(const TSourceLoc& loc, const char* str, TOperator op,
882 TIntermTyped* childNode)
883 {
884 TIntermTyped* result = intermediate.addUnaryMath(op, childNode, loc);
885
886 if (result)
887 return result;
888 else
889 unaryOpError(loc, str, childNode->getCompleteString());
890
891 return childNode;
892 }
893 //
894 // Return true if the name is a struct buffer method
895 //
isStructBufferMethod(const TString & name) const896 bool HlslParseContext::isStructBufferMethod(const TString& name) const
897 {
898 return
899 name == "GetDimensions" ||
900 name == "Load" ||
901 name == "Load2" ||
902 name == "Load3" ||
903 name == "Load4" ||
904 name == "Store" ||
905 name == "Store2" ||
906 name == "Store3" ||
907 name == "Store4" ||
908 name == "InterlockedAdd" ||
909 name == "InterlockedAnd" ||
910 name == "InterlockedCompareExchange" ||
911 name == "InterlockedCompareStore" ||
912 name == "InterlockedExchange" ||
913 name == "InterlockedMax" ||
914 name == "InterlockedMin" ||
915 name == "InterlockedOr" ||
916 name == "InterlockedXor" ||
917 name == "IncrementCounter" ||
918 name == "DecrementCounter" ||
919 name == "Append" ||
920 name == "Consume";
921 }
922
923 //
924 // Handle seeing a base.field dereference in the grammar, where 'field' is a
925 // swizzle or member variable.
926 //
handleDotDereference(const TSourceLoc & loc,TIntermTyped * base,const TString & field)927 TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TIntermTyped* base, const TString& field)
928 {
929 variableCheck(base);
930
931 if (base->isArray()) {
932 error(loc, "cannot apply to an array:", ".", field.c_str());
933 return base;
934 }
935
936 TIntermTyped* result = base;
937
938 if (base->getType().getBasicType() == EbtSampler) {
939 // Handle .mips[mipid][pos] operation on textures
940 const TSampler& sampler = base->getType().getSampler();
941 if (sampler.isTexture() && field == "mips") {
942 // Push a null to signify that we expect a mip level under operator[] next.
943 mipsOperatorMipArg.push_back(tMipsOperatorData(loc, nullptr));
944 // Keep 'result' pointing to 'base', since we expect an operator[] to go by next.
945 } else {
946 if (field == "mips")
947 error(loc, "unexpected texture type for .mips[][] operator:",
948 base->getType().getCompleteString().c_str(), "");
949 else
950 error(loc, "unexpected operator on texture type:", field.c_str(),
951 base->getType().getCompleteString().c_str());
952 }
953 } else if (base->isVector() || base->isScalar()) {
954 TSwizzleSelectors<TVectorSelector> selectors;
955 parseSwizzleSelector(loc, field, base->getVectorSize(), selectors);
956
957 if (base->isScalar()) {
958 if (selectors.size() == 1)
959 return result;
960 else {
961 TType type(base->getBasicType(), EvqTemporary, selectors.size());
962 return addConstructor(loc, base, type);
963 }
964 }
965 if (base->getVectorSize() == 1) {
966 TType scalarType(base->getBasicType(), EvqTemporary, 1);
967 if (selectors.size() == 1)
968 return addConstructor(loc, base, scalarType);
969 else {
970 TType vectorType(base->getBasicType(), EvqTemporary, selectors.size());
971 return addConstructor(loc, addConstructor(loc, base, scalarType), vectorType);
972 }
973 }
974
975 if (base->getType().getQualifier().isFrontEndConstant())
976 result = intermediate.foldSwizzle(base, selectors, loc);
977 else {
978 if (selectors.size() == 1) {
979 TIntermTyped* index = intermediate.addConstantUnion(selectors[0], loc);
980 result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
981 result->setType(TType(base->getBasicType(), EvqTemporary));
982 } else {
983 TIntermTyped* index = intermediate.addSwizzle(selectors, loc);
984 result = intermediate.addIndex(EOpVectorSwizzle, base, index, loc);
985 result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision,
986 selectors.size()));
987 }
988 }
989 } else if (base->isMatrix()) {
990 TSwizzleSelectors<TMatrixSelector> selectors;
991 if (! parseMatrixSwizzleSelector(loc, field, base->getMatrixCols(), base->getMatrixRows(), selectors))
992 return result;
993
994 if (selectors.size() == 1) {
995 // Representable by m[c][r]
996 if (base->getType().getQualifier().isFrontEndConstant()) {
997 result = intermediate.foldDereference(base, selectors[0].coord1, loc);
998 result = intermediate.foldDereference(result, selectors[0].coord2, loc);
999 } else {
1000 result = intermediate.addIndex(EOpIndexDirect, base,
1001 intermediate.addConstantUnion(selectors[0].coord1, loc),
1002 loc);
1003 TType dereferencedCol(base->getType(), 0);
1004 result->setType(dereferencedCol);
1005 result = intermediate.addIndex(EOpIndexDirect, result,
1006 intermediate.addConstantUnion(selectors[0].coord2, loc),
1007 loc);
1008 TType dereferenced(dereferencedCol, 0);
1009 result->setType(dereferenced);
1010 }
1011 } else {
1012 int column = getMatrixComponentsColumn(base->getMatrixRows(), selectors);
1013 if (column >= 0) {
1014 // Representable by m[c]
1015 if (base->getType().getQualifier().isFrontEndConstant())
1016 result = intermediate.foldDereference(base, column, loc);
1017 else {
1018 result = intermediate.addIndex(EOpIndexDirect, base, intermediate.addConstantUnion(column, loc),
1019 loc);
1020 TType dereferenced(base->getType(), 0);
1021 result->setType(dereferenced);
1022 }
1023 } else {
1024 // general case, not a column, not a single component
1025 TIntermTyped* index = intermediate.addSwizzle(selectors, loc);
1026 result = intermediate.addIndex(EOpMatrixSwizzle, base, index, loc);
1027 result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision,
1028 selectors.size()));
1029 }
1030 }
1031 } else if (base->getBasicType() == EbtStruct || base->getBasicType() == EbtBlock) {
1032 const TTypeList* fields = base->getType().getStruct();
1033 bool fieldFound = false;
1034 int member;
1035 for (member = 0; member < (int)fields->size(); ++member) {
1036 if ((*fields)[member].type->getFieldName() == field) {
1037 fieldFound = true;
1038 break;
1039 }
1040 }
1041 if (fieldFound) {
1042 if (base->getAsSymbolNode() && wasFlattened(base)) {
1043 result = flattenAccess(base, member);
1044 } else {
1045 if (base->getType().getQualifier().storage == EvqConst)
1046 result = intermediate.foldDereference(base, member, loc);
1047 else {
1048 TIntermTyped* index = intermediate.addConstantUnion(member, loc);
1049 result = intermediate.addIndex(EOpIndexDirectStruct, base, index, loc);
1050 result->setType(*(*fields)[member].type);
1051 }
1052 }
1053 } else
1054 error(loc, "no such field in structure", field.c_str(), "");
1055 } else
1056 error(loc, "does not apply to this type:", field.c_str(), base->getType().getCompleteString().c_str());
1057
1058 return result;
1059 }
1060
1061 //
1062 // Return true if the field should be treated as a built-in method.
1063 // Return false otherwise.
1064 //
isBuiltInMethod(const TSourceLoc &,TIntermTyped * base,const TString & field)1065 bool HlslParseContext::isBuiltInMethod(const TSourceLoc&, TIntermTyped* base, const TString& field)
1066 {
1067 if (base == nullptr)
1068 return false;
1069
1070 variableCheck(base);
1071
1072 if (base->getType().getBasicType() == EbtSampler) {
1073 return true;
1074 } else if (isStructBufferType(base->getType()) && isStructBufferMethod(field)) {
1075 return true;
1076 } else if (field == "Append" ||
1077 field == "RestartStrip") {
1078 // We cannot check the type here: it may be sanitized if we're not compiling a geometry shader, but
1079 // the code is around in the shader source.
1080 return true;
1081 } else
1082 return false;
1083 }
1084
1085 // Independently establish a built-in that is a member of a structure.
1086 // 'arraySizes' are what's desired for the independent built-in, whatever
1087 // the higher-level source/expression of them was.
splitBuiltIn(const TString & baseName,const TType & memberType,const TArraySizes * arraySizes,const TQualifier & outerQualifier)1088 void HlslParseContext::splitBuiltIn(const TString& baseName, const TType& memberType, const TArraySizes* arraySizes,
1089 const TQualifier& outerQualifier)
1090 {
1091 // Because of arrays of structs, we might be asked more than once,
1092 // but the arraySizes passed in should have captured the whole thing
1093 // the first time.
1094 // However, clip/cull rely on multiple updates.
1095 if (!isClipOrCullDistance(memberType))
1096 if (splitBuiltIns.find(tInterstageIoData(memberType.getQualifier().builtIn, outerQualifier.storage)) !=
1097 splitBuiltIns.end())
1098 return;
1099
1100 TVariable* ioVar = makeInternalVariable(baseName + "." + memberType.getFieldName(), memberType);
1101
1102 if (arraySizes != nullptr && !memberType.isArray())
1103 ioVar->getWritableType().copyArraySizes(*arraySizes);
1104
1105 splitBuiltIns[tInterstageIoData(memberType.getQualifier().builtIn, outerQualifier.storage)] = ioVar;
1106 if (!isClipOrCullDistance(ioVar->getType()))
1107 trackLinkage(*ioVar);
1108
1109 // Merge qualifier from the user structure
1110 mergeQualifiers(ioVar->getWritableType().getQualifier(), outerQualifier);
1111
1112 // Fix the builtin type if needed (e.g, some types require fixed array sizes, no matter how the
1113 // shader declared them). This is done after mergeQualifiers(), in case fixBuiltInIoType looks
1114 // at the qualifier to determine e.g, in or out qualifications.
1115 fixBuiltInIoType(ioVar->getWritableType());
1116
1117 // But, not location, we're losing that
1118 ioVar->getWritableType().getQualifier().layoutLocation = TQualifier::layoutLocationEnd;
1119 }
1120
1121 // Split a type into
1122 // 1. a struct of non-I/O members
1123 // 2. a collection of independent I/O variables
split(const TVariable & variable)1124 void HlslParseContext::split(const TVariable& variable)
1125 {
1126 // Create a new variable:
1127 const TType& clonedType = *variable.getType().clone();
1128 const TType& splitType = split(clonedType, variable.getName(), clonedType.getQualifier());
1129 splitNonIoVars[variable.getUniqueId()] = makeInternalVariable(variable.getName(), splitType);
1130 }
1131
1132 // Recursive implementation of split().
1133 // Returns reference to the modified type.
split(const TType & type,const TString & name,const TQualifier & outerQualifier)1134 const TType& HlslParseContext::split(const TType& type, const TString& name, const TQualifier& outerQualifier)
1135 {
1136 if (type.isStruct()) {
1137 TTypeList* userStructure = type.getWritableStruct();
1138 for (auto ioType = userStructure->begin(); ioType != userStructure->end(); ) {
1139 if (ioType->type->isBuiltIn()) {
1140 // move out the built-in
1141 splitBuiltIn(name, *ioType->type, type.getArraySizes(), outerQualifier);
1142 ioType = userStructure->erase(ioType);
1143 } else {
1144 split(*ioType->type, name + "." + ioType->type->getFieldName(), outerQualifier);
1145 ++ioType;
1146 }
1147 }
1148 }
1149
1150 return type;
1151 }
1152
1153 // Is this an aggregate that should be flattened?
1154 // Can be applied to intermediate levels of type in a hierarchy.
1155 // Some things like flattening uniform arrays are only about the top level
1156 // of the aggregate, triggered on 'topLevel'.
shouldFlatten(const TType & type,TStorageQualifier qualifier,bool topLevel) const1157 bool HlslParseContext::shouldFlatten(const TType& type, TStorageQualifier qualifier, bool topLevel) const
1158 {
1159 switch (qualifier) {
1160 case EvqVaryingIn:
1161 case EvqVaryingOut:
1162 return type.isStruct() || type.isArray();
1163 case EvqUniform:
1164 return (type.isArray() && intermediate.getFlattenUniformArrays() && topLevel) ||
1165 (type.isStruct() && type.containsOpaque());
1166 default:
1167 return false;
1168 };
1169 }
1170
1171 // Top level variable flattening: construct data
flatten(const TVariable & variable,bool linkage,bool arrayed)1172 void HlslParseContext::flatten(const TVariable& variable, bool linkage, bool arrayed)
1173 {
1174 const TType& type = variable.getType();
1175
1176 // If it's a standalone built-in, there is nothing to flatten
1177 if (type.isBuiltIn() && !type.isStruct())
1178 return;
1179
1180 auto entry = flattenMap.insert(std::make_pair(variable.getUniqueId(),
1181 TFlattenData(type.getQualifier().layoutBinding,
1182 type.getQualifier().layoutLocation)));
1183
1184 // if flattening arrayed io struct, array each member of dereferenced type
1185 if (arrayed) {
1186 const TType dereferencedType(type, 0);
1187 flatten(variable, dereferencedType, entry.first->second, variable.getName(), linkage,
1188 type.getQualifier(), type.getArraySizes());
1189 } else {
1190 flatten(variable, type, entry.first->second, variable.getName(), linkage,
1191 type.getQualifier(), nullptr);
1192 }
1193 }
1194
1195 // Recursively flatten the given variable at the provided type, building the flattenData as we go.
1196 //
1197 // This is mutually recursive with flattenStruct and flattenArray.
1198 // We are going to flatten an arbitrarily nested composite structure into a linear sequence of
1199 // members, and later on, we want to turn a path through the tree structure into a final
1200 // location in this linear sequence.
1201 //
1202 // If the tree was N-ary, that can be directly calculated. However, we are dealing with
1203 // arbitrary numbers - perhaps a struct of 7 members containing an array of 3. Thus, we must
1204 // build a data structure to allow the sequence of bracket and dot operators on arrays and
1205 // structs to arrive at the proper member.
1206 //
1207 // To avoid storing a tree with pointers, we are going to flatten the tree into a vector of integers.
1208 // The leaves are the indexes into the flattened member array.
1209 // Each level will have the next location for the Nth item stored sequentially, so for instance:
1210 //
1211 // struct { float2 a[2]; int b; float4 c[3] };
1212 //
1213 // This will produce the following flattened tree:
1214 // Pos: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
1215 // (3, 7, 8, 5, 6, 0, 1, 2, 11, 12, 13, 3, 4, 5}
1216 //
1217 // Given a reference to mystruct.c[1], the access chain is (2,1), so we traverse:
1218 // (0+2) = 8 --> (8+1) = 12 --> 12 = 4
1219 //
1220 // so the 4th flattened member in traversal order is ours.
1221 //
flatten(const TVariable & variable,const TType & type,TFlattenData & flattenData,TString name,bool linkage,const TQualifier & outerQualifier,const TArraySizes * builtInArraySizes)1222 int HlslParseContext::flatten(const TVariable& variable, const TType& type,
1223 TFlattenData& flattenData, TString name, bool linkage,
1224 const TQualifier& outerQualifier,
1225 const TArraySizes* builtInArraySizes)
1226 {
1227 // If something is an arrayed struct, the array flattener will recursively call flatten()
1228 // to then flatten the struct, so this is an "if else": we don't do both.
1229 if (type.isArray())
1230 return flattenArray(variable, type, flattenData, name, linkage, outerQualifier);
1231 else if (type.isStruct())
1232 return flattenStruct(variable, type, flattenData, name, linkage, outerQualifier, builtInArraySizes);
1233 else {
1234 assert(0); // should never happen
1235 return -1;
1236 }
1237 }
1238
1239 // Add a single flattened member to the flattened data being tracked for the composite
1240 // Returns true for the final flattening level.
addFlattenedMember(const TVariable & variable,const TType & type,TFlattenData & flattenData,const TString & memberName,bool linkage,const TQualifier & outerQualifier,const TArraySizes * builtInArraySizes)1241 int HlslParseContext::addFlattenedMember(const TVariable& variable, const TType& type, TFlattenData& flattenData,
1242 const TString& memberName, bool linkage,
1243 const TQualifier& outerQualifier,
1244 const TArraySizes* builtInArraySizes)
1245 {
1246 if (!shouldFlatten(type, outerQualifier.storage, false)) {
1247 // This is as far as we flatten. Insert the variable.
1248 TVariable* memberVariable = makeInternalVariable(memberName, type);
1249 mergeQualifiers(memberVariable->getWritableType().getQualifier(), variable.getType().getQualifier());
1250
1251 if (flattenData.nextBinding != TQualifier::layoutBindingEnd)
1252 memberVariable->getWritableType().getQualifier().layoutBinding = flattenData.nextBinding++;
1253
1254 if (memberVariable->getType().isBuiltIn()) {
1255 // inherited locations are nonsensical for built-ins (TODO: what if semantic had a number)
1256 memberVariable->getWritableType().getQualifier().layoutLocation = TQualifier::layoutLocationEnd;
1257 } else {
1258 // inherited locations must be auto bumped, not replicated
1259 if (flattenData.nextLocation != TQualifier::layoutLocationEnd) {
1260 memberVariable->getWritableType().getQualifier().layoutLocation = flattenData.nextLocation;
1261 flattenData.nextLocation += intermediate.computeTypeLocationSize(memberVariable->getType(), language);
1262 nextOutLocation = std::max(nextOutLocation, flattenData.nextLocation);
1263 }
1264 }
1265
1266 // Only propagate arraysizes here for arrayed io
1267 if (variable.getType().getQualifier().isArrayedIo(language) && builtInArraySizes != nullptr)
1268 memberVariable->getWritableType().copyArraySizes(*builtInArraySizes);
1269
1270 flattenData.offsets.push_back(static_cast<int>(flattenData.members.size()));
1271 flattenData.members.push_back(memberVariable);
1272
1273 if (linkage)
1274 trackLinkage(*memberVariable);
1275
1276 return static_cast<int>(flattenData.offsets.size()) - 1; // location of the member reference
1277 } else {
1278 // Further recursion required
1279 return flatten(variable, type, flattenData, memberName, linkage, outerQualifier, builtInArraySizes);
1280 }
1281 }
1282
1283 // Figure out the mapping between an aggregate's top members and an
1284 // equivalent set of individual variables.
1285 //
1286 // Assumes shouldFlatten() or equivalent was called first.
flattenStruct(const TVariable & variable,const TType & type,TFlattenData & flattenData,TString name,bool linkage,const TQualifier & outerQualifier,const TArraySizes * builtInArraySizes)1287 int HlslParseContext::flattenStruct(const TVariable& variable, const TType& type,
1288 TFlattenData& flattenData, TString name, bool linkage,
1289 const TQualifier& outerQualifier,
1290 const TArraySizes* builtInArraySizes)
1291 {
1292 assert(type.isStruct());
1293
1294 auto members = *type.getStruct();
1295
1296 // Reserve space for this tree level.
1297 int start = static_cast<int>(flattenData.offsets.size());
1298 int pos = start;
1299 flattenData.offsets.resize(int(pos + members.size()), -1);
1300
1301 for (int member = 0; member < (int)members.size(); ++member) {
1302 TType& dereferencedType = *members[member].type;
1303 if (dereferencedType.isBuiltIn())
1304 splitBuiltIn(variable.getName(), dereferencedType, builtInArraySizes, outerQualifier);
1305 else {
1306 const int mpos = addFlattenedMember(variable, dereferencedType, flattenData,
1307 name + "." + dereferencedType.getFieldName(),
1308 linkage, outerQualifier,
1309 builtInArraySizes == nullptr && dereferencedType.isArray()
1310 ? dereferencedType.getArraySizes()
1311 : builtInArraySizes);
1312 flattenData.offsets[pos++] = mpos;
1313 }
1314 }
1315
1316 return start;
1317 }
1318
1319 // Figure out mapping between an array's members and an
1320 // equivalent set of individual variables.
1321 //
1322 // Assumes shouldFlatten() or equivalent was called first.
flattenArray(const TVariable & variable,const TType & type,TFlattenData & flattenData,TString name,bool linkage,const TQualifier & outerQualifier)1323 int HlslParseContext::flattenArray(const TVariable& variable, const TType& type,
1324 TFlattenData& flattenData, TString name, bool linkage,
1325 const TQualifier& outerQualifier)
1326 {
1327 assert(type.isSizedArray());
1328
1329 const int size = type.getOuterArraySize();
1330 const TType dereferencedType(type, 0);
1331
1332 if (name.empty())
1333 name = variable.getName();
1334
1335 // Reserve space for this tree level.
1336 int start = static_cast<int>(flattenData.offsets.size());
1337 int pos = start;
1338 flattenData.offsets.resize(int(pos + size), -1);
1339
1340 for (int element=0; element < size; ++element) {
1341 char elementNumBuf[20]; // sufficient for MAXINT
1342 snprintf(elementNumBuf, sizeof(elementNumBuf)-1, "[%d]", element);
1343 const int mpos = addFlattenedMember(variable, dereferencedType, flattenData,
1344 name + elementNumBuf, linkage, outerQualifier,
1345 type.getArraySizes());
1346
1347 flattenData.offsets[pos++] = mpos;
1348 }
1349
1350 return start;
1351 }
1352
1353 // Return true if we have flattened this node.
wasFlattened(const TIntermTyped * node) const1354 bool HlslParseContext::wasFlattened(const TIntermTyped* node) const
1355 {
1356 return node != nullptr && node->getAsSymbolNode() != nullptr &&
1357 wasFlattened(node->getAsSymbolNode()->getId());
1358 }
1359
1360 // Return true if we have split this structure
wasSplit(const TIntermTyped * node) const1361 bool HlslParseContext::wasSplit(const TIntermTyped* node) const
1362 {
1363 return node != nullptr && node->getAsSymbolNode() != nullptr &&
1364 wasSplit(node->getAsSymbolNode()->getId());
1365 }
1366
1367 // Turn an access into an aggregate that was flattened to instead be
1368 // an access to the individual variable the member was flattened to.
1369 // Assumes wasFlattened() or equivalent was called first.
flattenAccess(TIntermTyped * base,int member)1370 TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member)
1371 {
1372 const TType dereferencedType(base->getType(), member); // dereferenced type
1373 const TIntermSymbol& symbolNode = *base->getAsSymbolNode();
1374 TIntermTyped* flattened = flattenAccess(symbolNode.getId(), member, base->getQualifier().storage,
1375 dereferencedType, symbolNode.getFlattenSubset());
1376
1377 return flattened ? flattened : base;
1378 }
flattenAccess(int uniqueId,int member,TStorageQualifier outerStorage,const TType & dereferencedType,int subset)1379 TIntermTyped* HlslParseContext::flattenAccess(int uniqueId, int member, TStorageQualifier outerStorage,
1380 const TType& dereferencedType, int subset)
1381 {
1382 const auto flattenData = flattenMap.find(uniqueId);
1383
1384 if (flattenData == flattenMap.end())
1385 return nullptr;
1386
1387 // Calculate new cumulative offset from the packed tree
1388 int newSubset = flattenData->second.offsets[subset >= 0 ? subset + member : member];
1389
1390 TIntermSymbol* subsetSymbol;
1391 if (!shouldFlatten(dereferencedType, outerStorage, false)) {
1392 // Finished flattening: create symbol for variable
1393 member = flattenData->second.offsets[newSubset];
1394 const TVariable* memberVariable = flattenData->second.members[member];
1395 subsetSymbol = intermediate.addSymbol(*memberVariable);
1396 subsetSymbol->setFlattenSubset(-1);
1397 } else {
1398
1399 // If this is not the final flattening, accumulate the position and return
1400 // an object of the partially dereferenced type.
1401 subsetSymbol = new TIntermSymbol(uniqueId, "flattenShadow", dereferencedType);
1402 subsetSymbol->setFlattenSubset(newSubset);
1403 }
1404
1405 return subsetSymbol;
1406 }
1407
1408 // For finding where the first leaf is in a subtree of a multi-level aggregate
1409 // that is just getting a subset assigned. Follows the same logic as flattenAccess,
1410 // but logically going down the "left-most" tree branch each step of the way.
1411 //
1412 // Returns the offset into the first leaf of the subset.
findSubtreeOffset(const TIntermNode & node) const1413 int HlslParseContext::findSubtreeOffset(const TIntermNode& node) const
1414 {
1415 const TIntermSymbol* sym = node.getAsSymbolNode();
1416 if (sym == nullptr)
1417 return 0;
1418 if (!sym->isArray() && !sym->isStruct())
1419 return 0;
1420 int subset = sym->getFlattenSubset();
1421 if (subset == -1)
1422 return 0;
1423
1424 // Getting this far means a partial aggregate is identified by the flatten subset.
1425 // Find the first leaf of the subset.
1426
1427 const auto flattenData = flattenMap.find(sym->getId());
1428 if (flattenData == flattenMap.end())
1429 return 0;
1430
1431 return findSubtreeOffset(sym->getType(), subset, flattenData->second.offsets);
1432
1433 do {
1434 subset = flattenData->second.offsets[subset];
1435 } while (true);
1436 }
1437 // Recursively do the desent
findSubtreeOffset(const TType & type,int subset,const TVector<int> & offsets) const1438 int HlslParseContext::findSubtreeOffset(const TType& type, int subset, const TVector<int>& offsets) const
1439 {
1440 if (!type.isArray() && !type.isStruct())
1441 return offsets[subset];
1442 TType derefType(type, 0);
1443 return findSubtreeOffset(derefType, offsets[subset], offsets);
1444 };
1445
1446 // Find and return the split IO TVariable for id, or nullptr if none.
getSplitNonIoVar(int id) const1447 TVariable* HlslParseContext::getSplitNonIoVar(int id) const
1448 {
1449 const auto splitNonIoVar = splitNonIoVars.find(id);
1450 if (splitNonIoVar == splitNonIoVars.end())
1451 return nullptr;
1452
1453 return splitNonIoVar->second;
1454 }
1455
1456 // Pass through to base class after remembering built-in mappings.
trackLinkage(TSymbol & symbol)1457 void HlslParseContext::trackLinkage(TSymbol& symbol)
1458 {
1459 TBuiltInVariable biType = symbol.getType().getQualifier().builtIn;
1460
1461 if (biType != EbvNone)
1462 builtInTessLinkageSymbols[biType] = symbol.clone();
1463
1464 TParseContextBase::trackLinkage(symbol);
1465 }
1466
1467
1468 // Returns true if the built-in is a clip or cull distance variable.
isClipOrCullDistance(TBuiltInVariable builtIn)1469 bool HlslParseContext::isClipOrCullDistance(TBuiltInVariable builtIn)
1470 {
1471 return builtIn == EbvClipDistance || builtIn == EbvCullDistance;
1472 }
1473
1474 // Some types require fixed array sizes in SPIR-V, but can be scalars or
1475 // arrays of sizes SPIR-V doesn't allow. For example, tessellation factors.
1476 // This creates the right size. A conversion is performed when the internal
1477 // type is copied to or from the external type. This corrects the externally
1478 // facing input or output type to abide downstream semantics.
fixBuiltInIoType(TType & type)1479 void HlslParseContext::fixBuiltInIoType(TType& type)
1480 {
1481 int requiredArraySize = 0;
1482 int requiredVectorSize = 0;
1483
1484 switch (type.getQualifier().builtIn) {
1485 case EbvTessLevelOuter: requiredArraySize = 4; break;
1486 case EbvTessLevelInner: requiredArraySize = 2; break;
1487
1488 case EbvSampleMask:
1489 {
1490 // Promote scalar to array of size 1. Leave existing arrays alone.
1491 if (!type.isArray())
1492 requiredArraySize = 1;
1493 break;
1494 }
1495
1496 case EbvWorkGroupId: requiredVectorSize = 3; break;
1497 case EbvGlobalInvocationId: requiredVectorSize = 3; break;
1498 case EbvLocalInvocationId: requiredVectorSize = 3; break;
1499 case EbvTessCoord: requiredVectorSize = 3; break;
1500
1501 default:
1502 if (isClipOrCullDistance(type)) {
1503 const int loc = type.getQualifier().layoutLocation;
1504
1505 if (type.getQualifier().builtIn == EbvClipDistance) {
1506 if (type.getQualifier().storage == EvqVaryingIn)
1507 clipSemanticNSizeIn[loc] = type.getVectorSize();
1508 else
1509 clipSemanticNSizeOut[loc] = type.getVectorSize();
1510 } else {
1511 if (type.getQualifier().storage == EvqVaryingIn)
1512 cullSemanticNSizeIn[loc] = type.getVectorSize();
1513 else
1514 cullSemanticNSizeOut[loc] = type.getVectorSize();
1515 }
1516 }
1517
1518 return;
1519 }
1520
1521 // Alter or set vector size as needed.
1522 if (requiredVectorSize > 0) {
1523 TType newType(type.getBasicType(), type.getQualifier().storage, requiredVectorSize);
1524 newType.getQualifier() = type.getQualifier();
1525
1526 type.shallowCopy(newType);
1527 }
1528
1529 // Alter or set array size as needed.
1530 if (requiredArraySize > 0) {
1531 if (!type.isArray() || type.getOuterArraySize() != requiredArraySize) {
1532 TArraySizes* arraySizes = new TArraySizes;
1533 arraySizes->addInnerSize(requiredArraySize);
1534 type.transferArraySizes(arraySizes);
1535 }
1536 }
1537 }
1538
1539 // Variables that correspond to the user-interface in and out of a stage
1540 // (not the built-in interface) are
1541 // - assigned locations
1542 // - registered as a linkage node (part of the stage's external interface).
1543 // Assumes it is called in the order in which locations should be assigned.
assignToInterface(TVariable & variable)1544 void HlslParseContext::assignToInterface(TVariable& variable)
1545 {
1546 const auto assignLocation = [&](TVariable& variable) {
1547 TType& type = variable.getWritableType();
1548 if (!type.isStruct() || type.getStruct()->size() > 0) {
1549 TQualifier& qualifier = type.getQualifier();
1550 if (qualifier.storage == EvqVaryingIn || qualifier.storage == EvqVaryingOut) {
1551 if (qualifier.builtIn == EbvNone && !qualifier.hasLocation()) {
1552 // Strip off the outer array dimension for those having an extra one.
1553 int size;
1554 if (type.isArray() && qualifier.isArrayedIo(language)) {
1555 TType elementType(type, 0);
1556 size = intermediate.computeTypeLocationSize(elementType, language);
1557 } else
1558 size = intermediate.computeTypeLocationSize(type, language);
1559
1560 if (qualifier.storage == EvqVaryingIn) {
1561 variable.getWritableType().getQualifier().layoutLocation = nextInLocation;
1562 nextInLocation += size;
1563 } else {
1564 variable.getWritableType().getQualifier().layoutLocation = nextOutLocation;
1565 nextOutLocation += size;
1566 }
1567 }
1568 trackLinkage(variable);
1569 }
1570 }
1571 };
1572
1573 if (wasFlattened(variable.getUniqueId())) {
1574 auto& memberList = flattenMap[variable.getUniqueId()].members;
1575 for (auto member = memberList.begin(); member != memberList.end(); ++member)
1576 assignLocation(**member);
1577 } else if (wasSplit(variable.getUniqueId())) {
1578 TVariable* splitIoVar = getSplitNonIoVar(variable.getUniqueId());
1579 assignLocation(*splitIoVar);
1580 } else {
1581 assignLocation(variable);
1582 }
1583 }
1584
1585 //
1586 // Handle seeing a function declarator in the grammar. This is the precursor
1587 // to recognizing a function prototype or function definition.
1588 //
handleFunctionDeclarator(const TSourceLoc & loc,TFunction & function,bool prototype)1589 void HlslParseContext::handleFunctionDeclarator(const TSourceLoc& loc, TFunction& function, bool prototype)
1590 {
1591 //
1592 // Multiple declarations of the same function name are allowed.
1593 //
1594 // If this is a definition, the definition production code will check for redefinitions
1595 // (we don't know at this point if it's a definition or not).
1596 //
1597 bool builtIn;
1598 TSymbol* symbol = symbolTable.find(function.getMangledName(), &builtIn);
1599 const TFunction* prevDec = symbol ? symbol->getAsFunction() : 0;
1600
1601 if (prototype) {
1602 // All built-in functions are defined, even though they don't have a body.
1603 // Count their prototype as a definition instead.
1604 if (symbolTable.atBuiltInLevel())
1605 function.setDefined();
1606 else {
1607 if (prevDec && ! builtIn)
1608 symbol->getAsFunction()->setPrototyped(); // need a writable one, but like having prevDec as a const
1609 function.setPrototyped();
1610 }
1611 }
1612
1613 // This insert won't actually insert it if it's a duplicate signature, but it will still check for
1614 // other forms of name collisions.
1615 if (! symbolTable.insert(function))
1616 error(loc, "function name is redeclaration of existing name", function.getName().c_str(), "");
1617 }
1618
1619 // For struct buffers with counters, we must pass the counter buffer as hidden parameter.
1620 // This adds the hidden parameter to the parameter list in 'paramNodes' if needed.
1621 // Otherwise, it's a no-op
addStructBufferHiddenCounterParam(const TSourceLoc & loc,TParameter & param,TIntermAggregate * & paramNodes)1622 void HlslParseContext::addStructBufferHiddenCounterParam(const TSourceLoc& loc, TParameter& param,
1623 TIntermAggregate*& paramNodes)
1624 {
1625 if (! hasStructBuffCounter(*param.type))
1626 return;
1627
1628 const TString counterBlockName(intermediate.addCounterBufferName(*param.name));
1629
1630 TType counterType;
1631 counterBufferType(loc, counterType);
1632 TVariable *variable = makeInternalVariable(counterBlockName, counterType);
1633
1634 if (! symbolTable.insert(*variable))
1635 error(loc, "redefinition", variable->getName().c_str(), "");
1636
1637 paramNodes = intermediate.growAggregate(paramNodes,
1638 intermediate.addSymbol(*variable, loc),
1639 loc);
1640 }
1641
1642 //
1643 // Handle seeing the function prototype in front of a function definition in the grammar.
1644 // The body is handled after this function returns.
1645 //
1646 // Returns an aggregate of parameter-symbol nodes.
1647 //
handleFunctionDefinition(const TSourceLoc & loc,TFunction & function,const TAttributes & attributes,TIntermNode * & entryPointTree)1648 TIntermAggregate* HlslParseContext::handleFunctionDefinition(const TSourceLoc& loc, TFunction& function,
1649 const TAttributes& attributes,
1650 TIntermNode*& entryPointTree)
1651 {
1652 currentCaller = function.getMangledName();
1653 TSymbol* symbol = symbolTable.find(function.getMangledName());
1654 TFunction* prevDec = symbol ? symbol->getAsFunction() : nullptr;
1655
1656 if (prevDec == nullptr)
1657 error(loc, "can't find function", function.getName().c_str(), "");
1658 // Note: 'prevDec' could be 'function' if this is the first time we've seen function
1659 // as it would have just been put in the symbol table. Otherwise, we're looking up
1660 // an earlier occurrence.
1661
1662 if (prevDec && prevDec->isDefined()) {
1663 // Then this function already has a body.
1664 error(loc, "function already has a body", function.getName().c_str(), "");
1665 }
1666 if (prevDec && ! prevDec->isDefined()) {
1667 prevDec->setDefined();
1668
1669 // Remember the return type for later checking for RETURN statements.
1670 currentFunctionType = &(prevDec->getType());
1671 } else
1672 currentFunctionType = new TType(EbtVoid);
1673 functionReturnsValue = false;
1674
1675 // Entry points need different I/O and other handling, transform it so the
1676 // rest of this function doesn't care.
1677 entryPointTree = transformEntryPoint(loc, function, attributes);
1678
1679 //
1680 // New symbol table scope for body of function plus its arguments
1681 //
1682 pushScope();
1683
1684 //
1685 // Insert parameters into the symbol table.
1686 // If the parameter has no name, it's not an error, just don't insert it
1687 // (could be used for unused args).
1688 //
1689 // Also, accumulate the list of parameters into the AST, so lower level code
1690 // knows where to find parameters.
1691 //
1692 TIntermAggregate* paramNodes = new TIntermAggregate;
1693 for (int i = 0; i < function.getParamCount(); i++) {
1694 TParameter& param = function[i];
1695 if (param.name != nullptr) {
1696 TVariable *variable = new TVariable(param.name, *param.type);
1697
1698 if (i == 0 && function.hasImplicitThis()) {
1699 // Anonymous 'this' members are already in a symbol-table level,
1700 // and we need to know what function parameter to map them to.
1701 symbolTable.makeInternalVariable(*variable);
1702 pushImplicitThis(variable);
1703 }
1704
1705 // Insert the parameters with name in the symbol table.
1706 if (! symbolTable.insert(*variable))
1707 error(loc, "redefinition", variable->getName().c_str(), "");
1708
1709 // Add parameters to the AST list.
1710 if (shouldFlatten(variable->getType(), variable->getType().getQualifier().storage, true)) {
1711 // Expand the AST parameter nodes (but not the name mangling or symbol table view)
1712 // for structures that need to be flattened.
1713 flatten(*variable, false);
1714 const TTypeList* structure = variable->getType().getStruct();
1715 for (int mem = 0; mem < (int)structure->size(); ++mem) {
1716 paramNodes = intermediate.growAggregate(paramNodes,
1717 flattenAccess(variable->getUniqueId(), mem,
1718 variable->getType().getQualifier().storage,
1719 *(*structure)[mem].type),
1720 loc);
1721 }
1722 } else {
1723 // Add the parameter to the AST
1724 paramNodes = intermediate.growAggregate(paramNodes,
1725 intermediate.addSymbol(*variable, loc),
1726 loc);
1727 }
1728
1729 // Add hidden AST parameter for struct buffer counters, if needed.
1730 addStructBufferHiddenCounterParam(loc, param, paramNodes);
1731 } else
1732 paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(*param.type, loc), loc);
1733 }
1734 if (function.hasIllegalImplicitThis())
1735 pushImplicitThis(nullptr);
1736
1737 intermediate.setAggregateOperator(paramNodes, EOpParameters, TType(EbtVoid), loc);
1738 loopNestingLevel = 0;
1739 controlFlowNestingLevel = 0;
1740 postEntryPointReturn = false;
1741
1742 return paramNodes;
1743 }
1744
1745 // Handle all [attrib] attribute for the shader entry point
handleEntryPointAttributes(const TSourceLoc & loc,const TAttributes & attributes)1746 void HlslParseContext::handleEntryPointAttributes(const TSourceLoc& loc, const TAttributes& attributes)
1747 {
1748 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
1749 switch (it->name) {
1750 case EatNumThreads:
1751 {
1752 const TIntermSequence& sequence = it->args->getSequence();
1753 for (int lid = 0; lid < int(sequence.size()); ++lid)
1754 intermediate.setLocalSize(lid, sequence[lid]->getAsConstantUnion()->getConstArray()[0].getIConst());
1755 break;
1756 }
1757 case EatMaxVertexCount:
1758 {
1759 int maxVertexCount;
1760
1761 if (! it->getInt(maxVertexCount)) {
1762 error(loc, "invalid maxvertexcount", "", "");
1763 } else {
1764 if (! intermediate.setVertices(maxVertexCount))
1765 error(loc, "cannot change previously set maxvertexcount attribute", "", "");
1766 }
1767 break;
1768 }
1769 case EatPatchConstantFunc:
1770 {
1771 TString pcfName;
1772 if (! it->getString(pcfName, 0, false)) {
1773 error(loc, "invalid patch constant function", "", "");
1774 } else {
1775 patchConstantFunctionName = pcfName;
1776 }
1777 break;
1778 }
1779 case EatDomain:
1780 {
1781 // Handle [domain("...")]
1782 TString domainStr;
1783 if (! it->getString(domainStr)) {
1784 error(loc, "invalid domain", "", "");
1785 } else {
1786 TLayoutGeometry domain = ElgNone;
1787
1788 if (domainStr == "tri") {
1789 domain = ElgTriangles;
1790 } else if (domainStr == "quad") {
1791 domain = ElgQuads;
1792 } else if (domainStr == "isoline") {
1793 domain = ElgIsolines;
1794 } else {
1795 error(loc, "unsupported domain type", domainStr.c_str(), "");
1796 }
1797
1798 if (language == EShLangTessEvaluation) {
1799 if (! intermediate.setInputPrimitive(domain))
1800 error(loc, "cannot change previously set domain", TQualifier::getGeometryString(domain), "");
1801 } else {
1802 if (! intermediate.setOutputPrimitive(domain))
1803 error(loc, "cannot change previously set domain", TQualifier::getGeometryString(domain), "");
1804 }
1805 }
1806 break;
1807 }
1808 case EatOutputTopology:
1809 {
1810 // Handle [outputtopology("...")]
1811 TString topologyStr;
1812 if (! it->getString(topologyStr)) {
1813 error(loc, "invalid outputtopology", "", "");
1814 } else {
1815 TVertexOrder vertexOrder = EvoNone;
1816 TLayoutGeometry primitive = ElgNone;
1817
1818 if (topologyStr == "point") {
1819 intermediate.setPointMode();
1820 } else if (topologyStr == "line") {
1821 primitive = ElgIsolines;
1822 } else if (topologyStr == "triangle_cw") {
1823 vertexOrder = EvoCw;
1824 primitive = ElgTriangles;
1825 } else if (topologyStr == "triangle_ccw") {
1826 vertexOrder = EvoCcw;
1827 primitive = ElgTriangles;
1828 } else {
1829 error(loc, "unsupported outputtopology type", topologyStr.c_str(), "");
1830 }
1831
1832 if (vertexOrder != EvoNone) {
1833 if (! intermediate.setVertexOrder(vertexOrder)) {
1834 error(loc, "cannot change previously set outputtopology",
1835 TQualifier::getVertexOrderString(vertexOrder), "");
1836 }
1837 }
1838 if (primitive != ElgNone)
1839 intermediate.setOutputPrimitive(primitive);
1840 }
1841 break;
1842 }
1843 case EatPartitioning:
1844 {
1845 // Handle [partitioning("...")]
1846 TString partitionStr;
1847 if (! it->getString(partitionStr)) {
1848 error(loc, "invalid partitioning", "", "");
1849 } else {
1850 TVertexSpacing partitioning = EvsNone;
1851
1852 if (partitionStr == "integer") {
1853 partitioning = EvsEqual;
1854 } else if (partitionStr == "fractional_even") {
1855 partitioning = EvsFractionalEven;
1856 } else if (partitionStr == "fractional_odd") {
1857 partitioning = EvsFractionalOdd;
1858 //} else if (partition == "pow2") { // TODO: currently nothing to map this to.
1859 } else {
1860 error(loc, "unsupported partitioning type", partitionStr.c_str(), "");
1861 }
1862
1863 if (! intermediate.setVertexSpacing(partitioning))
1864 error(loc, "cannot change previously set partitioning",
1865 TQualifier::getVertexSpacingString(partitioning), "");
1866 }
1867 break;
1868 }
1869 case EatOutputControlPoints:
1870 {
1871 // Handle [outputcontrolpoints("...")]
1872 int ctrlPoints;
1873 if (! it->getInt(ctrlPoints)) {
1874 error(loc, "invalid outputcontrolpoints", "", "");
1875 } else {
1876 if (! intermediate.setVertices(ctrlPoints)) {
1877 error(loc, "cannot change previously set outputcontrolpoints attribute", "", "");
1878 }
1879 }
1880 break;
1881 }
1882 case EatEarlyDepthStencil:
1883 intermediate.setEarlyFragmentTests();
1884 break;
1885 case EatBuiltIn:
1886 case EatLocation:
1887 // tolerate these because of dual use of entrypoint and type attributes
1888 break;
1889 default:
1890 warn(loc, "attribute does not apply to entry point", "", "");
1891 break;
1892 }
1893 }
1894 }
1895
1896 // Update the given type with any type-like attribute information in the
1897 // attributes.
transferTypeAttributes(const TSourceLoc & loc,const TAttributes & attributes,TType & type,bool allowEntry)1898 void HlslParseContext::transferTypeAttributes(const TSourceLoc& loc, const TAttributes& attributes, TType& type,
1899 bool allowEntry)
1900 {
1901 if (attributes.size() == 0)
1902 return;
1903
1904 int value;
1905 TString builtInString;
1906 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
1907 switch (it->name) {
1908 case EatLocation:
1909 // location
1910 if (it->getInt(value))
1911 type.getQualifier().layoutLocation = value;
1912 else
1913 error(loc, "needs a literal integer", "location", "");
1914 break;
1915 case EatBinding:
1916 // binding
1917 if (it->getInt(value)) {
1918 type.getQualifier().layoutBinding = value;
1919 type.getQualifier().layoutSet = 0;
1920 } else
1921 error(loc, "needs a literal integer", "binding", "");
1922 // set
1923 if (it->getInt(value, 1))
1924 type.getQualifier().layoutSet = value;
1925 break;
1926 case EatGlobalBinding:
1927 // global cbuffer binding
1928 if (it->getInt(value))
1929 globalUniformBinding = value;
1930 else
1931 error(loc, "needs a literal integer", "global binding", "");
1932 // global cbuffer set
1933 if (it->getInt(value, 1))
1934 globalUniformSet = value;
1935 break;
1936 case EatInputAttachment:
1937 // input attachment
1938 if (it->getInt(value))
1939 type.getQualifier().layoutAttachment = value;
1940 else
1941 error(loc, "needs a literal integer", "input attachment", "");
1942 break;
1943 case EatBuiltIn:
1944 // PointSize built-in
1945 if (it->getString(builtInString, 0, false)) {
1946 if (builtInString == "PointSize")
1947 type.getQualifier().builtIn = EbvPointSize;
1948 }
1949 break;
1950 case EatPushConstant:
1951 // push_constant
1952 type.getQualifier().layoutPushConstant = true;
1953 break;
1954 case EatConstantId:
1955 // specialization constant
1956 if (type.getQualifier().storage != EvqConst) {
1957 error(loc, "needs a const type", "constant_id", "");
1958 break;
1959 }
1960 if (it->getInt(value)) {
1961 TSourceLoc loc;
1962 loc.init();
1963 setSpecConstantId(loc, type.getQualifier(), value);
1964 }
1965 break;
1966
1967 // image formats
1968 case EatFormatRgba32f: type.getQualifier().layoutFormat = ElfRgba32f; break;
1969 case EatFormatRgba16f: type.getQualifier().layoutFormat = ElfRgba16f; break;
1970 case EatFormatR32f: type.getQualifier().layoutFormat = ElfR32f; break;
1971 case EatFormatRgba8: type.getQualifier().layoutFormat = ElfRgba8; break;
1972 case EatFormatRgba8Snorm: type.getQualifier().layoutFormat = ElfRgba8Snorm; break;
1973 case EatFormatRg32f: type.getQualifier().layoutFormat = ElfRg32f; break;
1974 case EatFormatRg16f: type.getQualifier().layoutFormat = ElfRg16f; break;
1975 case EatFormatR11fG11fB10f: type.getQualifier().layoutFormat = ElfR11fG11fB10f; break;
1976 case EatFormatR16f: type.getQualifier().layoutFormat = ElfR16f; break;
1977 case EatFormatRgba16: type.getQualifier().layoutFormat = ElfRgba16; break;
1978 case EatFormatRgb10A2: type.getQualifier().layoutFormat = ElfRgb10A2; break;
1979 case EatFormatRg16: type.getQualifier().layoutFormat = ElfRg16; break;
1980 case EatFormatRg8: type.getQualifier().layoutFormat = ElfRg8; break;
1981 case EatFormatR16: type.getQualifier().layoutFormat = ElfR16; break;
1982 case EatFormatR8: type.getQualifier().layoutFormat = ElfR8; break;
1983 case EatFormatRgba16Snorm: type.getQualifier().layoutFormat = ElfRgba16Snorm; break;
1984 case EatFormatRg16Snorm: type.getQualifier().layoutFormat = ElfRg16Snorm; break;
1985 case EatFormatRg8Snorm: type.getQualifier().layoutFormat = ElfRg8Snorm; break;
1986 case EatFormatR16Snorm: type.getQualifier().layoutFormat = ElfR16Snorm; break;
1987 case EatFormatR8Snorm: type.getQualifier().layoutFormat = ElfR8Snorm; break;
1988 case EatFormatRgba32i: type.getQualifier().layoutFormat = ElfRgba32i; break;
1989 case EatFormatRgba16i: type.getQualifier().layoutFormat = ElfRgba16i; break;
1990 case EatFormatRgba8i: type.getQualifier().layoutFormat = ElfRgba8i; break;
1991 case EatFormatR32i: type.getQualifier().layoutFormat = ElfR32i; break;
1992 case EatFormatRg32i: type.getQualifier().layoutFormat = ElfRg32i; break;
1993 case EatFormatRg16i: type.getQualifier().layoutFormat = ElfRg16i; break;
1994 case EatFormatRg8i: type.getQualifier().layoutFormat = ElfRg8i; break;
1995 case EatFormatR16i: type.getQualifier().layoutFormat = ElfR16i; break;
1996 case EatFormatR8i: type.getQualifier().layoutFormat = ElfR8i; break;
1997 case EatFormatRgba32ui: type.getQualifier().layoutFormat = ElfRgba32ui; break;
1998 case EatFormatRgba16ui: type.getQualifier().layoutFormat = ElfRgba16ui; break;
1999 case EatFormatRgba8ui: type.getQualifier().layoutFormat = ElfRgba8ui; break;
2000 case EatFormatR32ui: type.getQualifier().layoutFormat = ElfR32ui; break;
2001 case EatFormatRgb10a2ui: type.getQualifier().layoutFormat = ElfRgb10a2ui; break;
2002 case EatFormatRg32ui: type.getQualifier().layoutFormat = ElfRg32ui; break;
2003 case EatFormatRg16ui: type.getQualifier().layoutFormat = ElfRg16ui; break;
2004 case EatFormatRg8ui: type.getQualifier().layoutFormat = ElfRg8ui; break;
2005 case EatFormatR16ui: type.getQualifier().layoutFormat = ElfR16ui; break;
2006 case EatFormatR8ui: type.getQualifier().layoutFormat = ElfR8ui; break;
2007 case EatFormatUnknown: type.getQualifier().layoutFormat = ElfNone; break;
2008
2009 case EatNonWritable: type.getQualifier().readonly = true; break;
2010 case EatNonReadable: type.getQualifier().writeonly = true; break;
2011
2012 default:
2013 if (! allowEntry)
2014 warn(loc, "attribute does not apply to a type", "", "");
2015 break;
2016 }
2017 }
2018 }
2019
2020 //
2021 // Do all special handling for the entry point, including wrapping
2022 // the shader's entry point with the official entry point that will call it.
2023 //
2024 // The following:
2025 //
2026 // retType shaderEntryPoint(args...) // shader declared entry point
2027 // { body }
2028 //
2029 // Becomes
2030 //
2031 // out retType ret;
2032 // in iargs<that are input>...;
2033 // out oargs<that are output> ...;
2034 //
2035 // void shaderEntryPoint() // synthesized, but official, entry point
2036 // {
2037 // args<that are input> = iargs...;
2038 // ret = @shaderEntryPoint(args...);
2039 // oargs = args<that are output>...;
2040 // }
2041 // retType @shaderEntryPoint(args...)
2042 // { body }
2043 //
2044 // The symbol table will still map the original entry point name to the
2045 // the modified function and its new name:
2046 //
2047 // symbol table: shaderEntryPoint -> @shaderEntryPoint
2048 //
2049 // Returns nullptr if no entry-point tree was built, otherwise, returns
2050 // a subtree that creates the entry point.
2051 //
transformEntryPoint(const TSourceLoc & loc,TFunction & userFunction,const TAttributes & attributes)2052 TIntermNode* HlslParseContext::transformEntryPoint(const TSourceLoc& loc, TFunction& userFunction,
2053 const TAttributes& attributes)
2054 {
2055 // Return true if this is a tessellation patch constant function input to a domain shader.
2056 const auto isDsPcfInput = [this](const TType& type) {
2057 return language == EShLangTessEvaluation &&
2058 type.contains([](const TType* t) {
2059 return t->getQualifier().builtIn == EbvTessLevelOuter ||
2060 t->getQualifier().builtIn == EbvTessLevelInner;
2061 });
2062 };
2063
2064 // if we aren't in the entry point, fix the IO as such and exit
2065 if (! isEntrypointName(userFunction.getName())) {
2066 remapNonEntryPointIO(userFunction);
2067 return nullptr;
2068 }
2069
2070 entryPointFunction = &userFunction; // needed in finish()
2071
2072 // Handle entry point attributes
2073 handleEntryPointAttributes(loc, attributes);
2074
2075 // entry point logic...
2076
2077 // Move parameters and return value to shader in/out
2078 TVariable* entryPointOutput; // gets created in remapEntryPointIO
2079 TVector<TVariable*> inputs;
2080 TVector<TVariable*> outputs;
2081 remapEntryPointIO(userFunction, entryPointOutput, inputs, outputs);
2082
2083 // Further this return/in/out transform by flattening, splitting, and assigning locations
2084 const auto makeVariableInOut = [&](TVariable& variable) {
2085 if (variable.getType().isStruct()) {
2086 bool arrayed = variable.getType().getQualifier().isArrayedIo(language);
2087 flatten(variable, false /* don't track linkage here, it will be tracked in assignToInterface() */, arrayed);
2088 }
2089 // TODO: flatten arrays too
2090 // TODO: flatten everything in I/O
2091 // TODO: replace all split with flatten, make all paths can create flattened I/O, then split code can be removed
2092
2093 // For clip and cull distance, multiple output variables potentially get merged
2094 // into one in assignClipCullDistance. That code in assignClipCullDistance
2095 // handles the interface logic, so we avoid it here in that case.
2096 if (!isClipOrCullDistance(variable.getType()))
2097 assignToInterface(variable);
2098 };
2099 if (entryPointOutput != nullptr)
2100 makeVariableInOut(*entryPointOutput);
2101 for (auto it = inputs.begin(); it != inputs.end(); ++it)
2102 if (!isDsPcfInput((*it)->getType())) // wait until the end for PCF input (see comment below)
2103 makeVariableInOut(*(*it));
2104 for (auto it = outputs.begin(); it != outputs.end(); ++it)
2105 makeVariableInOut(*(*it));
2106
2107 // In the domain shader, PCF input must be at the end of the linkage. That's because in the
2108 // hull shader there is no ordering: the output comes from the separate PCF, which does not
2109 // participate in the argument list. That is always put at the end of the HS linkage, so the
2110 // input side of the DS must match. The argument may be in any position in the DS argument list
2111 // however, so this ensures the linkage is built in the correct order regardless of argument order.
2112 if (language == EShLangTessEvaluation) {
2113 for (auto it = inputs.begin(); it != inputs.end(); ++it)
2114 if (isDsPcfInput((*it)->getType()))
2115 makeVariableInOut(*(*it));
2116 }
2117
2118 // Add uniform parameters to the $Global uniform block.
2119 TVector<TVariable*> opaque_uniforms;
2120 for (int i = 0; i < userFunction.getParamCount(); i++) {
2121 TType& paramType = *userFunction[i].type;
2122 TString& paramName = *userFunction[i].name;
2123 if (paramType.getQualifier().storage == EvqUniform) {
2124 if (!paramType.containsOpaque()) {
2125 // Add it to the global uniform block.
2126 growGlobalUniformBlock(loc, paramType, paramName);
2127 } else {
2128 // Declare it as a separate variable.
2129 TVariable *var = makeInternalVariable(paramName.c_str(), paramType);
2130 opaque_uniforms.push_back(var);
2131 }
2132 }
2133 }
2134
2135 // Synthesize the call
2136
2137 pushScope(); // matches the one in handleFunctionBody()
2138
2139 // new signature
2140 TType voidType(EbtVoid);
2141 TFunction synthEntryPoint(&userFunction.getName(), voidType);
2142 TIntermAggregate* synthParams = new TIntermAggregate();
2143 intermediate.setAggregateOperator(synthParams, EOpParameters, voidType, loc);
2144 intermediate.setEntryPointMangledName(synthEntryPoint.getMangledName().c_str());
2145 intermediate.incrementEntryPointCount();
2146 TFunction callee(&userFunction.getName(), voidType); // call based on old name, which is still in the symbol table
2147
2148 // change original name
2149 userFunction.addPrefix("@"); // change the name in the function, but not in the symbol table
2150
2151 // Copy inputs (shader-in -> calling arg), while building up the call node
2152 TVector<TVariable*> argVars;
2153 TIntermAggregate* synthBody = new TIntermAggregate();
2154 auto inputIt = inputs.begin();
2155 auto opaqueUniformIt = opaque_uniforms.begin();
2156 TIntermTyped* callingArgs = nullptr;
2157
2158 for (int i = 0; i < userFunction.getParamCount(); i++) {
2159 TParameter& param = userFunction[i];
2160 argVars.push_back(makeInternalVariable(*param.name, *param.type));
2161 argVars.back()->getWritableType().getQualifier().makeTemporary();
2162
2163 // Track the input patch, which is the only non-builtin supported by hull shader PCF.
2164 if (param.getDeclaredBuiltIn() == EbvInputPatch)
2165 inputPatch = argVars.back();
2166
2167 TIntermSymbol* arg = intermediate.addSymbol(*argVars.back());
2168 handleFunctionArgument(&callee, callingArgs, arg);
2169 if (param.type->getQualifier().isParamInput()) {
2170 intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign, arg,
2171 intermediate.addSymbol(**inputIt)));
2172 inputIt++;
2173 }
2174 if (param.type->getQualifier().storage == EvqUniform) {
2175 if (!param.type->containsOpaque()) {
2176 // Look it up in the $Global uniform block.
2177 intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign, arg,
2178 handleVariable(loc, param.name)));
2179 } else {
2180 intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign, arg,
2181 intermediate.addSymbol(**opaqueUniformIt)));
2182 ++opaqueUniformIt;
2183 }
2184 }
2185 }
2186
2187 // Call
2188 currentCaller = synthEntryPoint.getMangledName();
2189 TIntermTyped* callReturn = handleFunctionCall(loc, &callee, callingArgs);
2190 currentCaller = userFunction.getMangledName();
2191
2192 // Return value
2193 if (entryPointOutput) {
2194 TIntermTyped* returnAssign;
2195
2196 // For hull shaders, the wrapped entry point return value is written to
2197 // an array element as indexed by invocation ID, which we might have to make up.
2198 // This is required to match SPIR-V semantics.
2199 if (language == EShLangTessControl) {
2200 TIntermSymbol* invocationIdSym = findTessLinkageSymbol(EbvInvocationId);
2201
2202 // If there is no user declared invocation ID, we must make one.
2203 if (invocationIdSym == nullptr) {
2204 TType invocationIdType(EbtUint, EvqIn, 1);
2205 TString* invocationIdName = NewPoolTString("InvocationId");
2206 invocationIdType.getQualifier().builtIn = EbvInvocationId;
2207
2208 TVariable* variable = makeInternalVariable(*invocationIdName, invocationIdType);
2209
2210 globalQualifierFix(loc, variable->getWritableType().getQualifier());
2211 trackLinkage(*variable);
2212
2213 invocationIdSym = intermediate.addSymbol(*variable);
2214 }
2215
2216 TIntermTyped* element = intermediate.addIndex(EOpIndexIndirect, intermediate.addSymbol(*entryPointOutput),
2217 invocationIdSym, loc);
2218
2219 // Set the type of the array element being dereferenced
2220 const TType derefElementType(entryPointOutput->getType(), 0);
2221 element->setType(derefElementType);
2222
2223 returnAssign = handleAssign(loc, EOpAssign, element, callReturn);
2224 } else {
2225 returnAssign = handleAssign(loc, EOpAssign, intermediate.addSymbol(*entryPointOutput), callReturn);
2226 }
2227 intermediate.growAggregate(synthBody, returnAssign);
2228 } else
2229 intermediate.growAggregate(synthBody, callReturn);
2230
2231 // Output copies
2232 auto outputIt = outputs.begin();
2233 for (int i = 0; i < userFunction.getParamCount(); i++) {
2234 TParameter& param = userFunction[i];
2235
2236 // GS outputs are via emit, so we do not copy them here.
2237 if (param.type->getQualifier().isParamOutput()) {
2238 if (param.getDeclaredBuiltIn() == EbvGsOutputStream) {
2239 // GS output stream does not assign outputs here: it's the Append() method
2240 // which writes to the output, probably multiple times separated by Emit.
2241 // We merely remember the output to use, here.
2242 gsStreamOutput = *outputIt;
2243 } else {
2244 intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign,
2245 intermediate.addSymbol(**outputIt),
2246 intermediate.addSymbol(*argVars[i])));
2247 }
2248
2249 outputIt++;
2250 }
2251 }
2252
2253 // Put the pieces together to form a full function subtree
2254 // for the synthesized entry point.
2255 synthBody->setOperator(EOpSequence);
2256 TIntermNode* synthFunctionDef = synthParams;
2257 handleFunctionBody(loc, synthEntryPoint, synthBody, synthFunctionDef);
2258
2259 entryPointFunctionBody = synthBody;
2260
2261 return synthFunctionDef;
2262 }
2263
handleFunctionBody(const TSourceLoc & loc,TFunction & function,TIntermNode * functionBody,TIntermNode * & node)2264 void HlslParseContext::handleFunctionBody(const TSourceLoc& loc, TFunction& function, TIntermNode* functionBody,
2265 TIntermNode*& node)
2266 {
2267 node = intermediate.growAggregate(node, functionBody);
2268 intermediate.setAggregateOperator(node, EOpFunction, function.getType(), loc);
2269 node->getAsAggregate()->setName(function.getMangledName().c_str());
2270
2271 popScope();
2272 if (function.hasImplicitThis())
2273 popImplicitThis();
2274
2275 if (function.getType().getBasicType() != EbtVoid && ! functionReturnsValue)
2276 error(loc, "function does not return a value:", "", function.getName().c_str());
2277 }
2278
2279 // AST I/O is done through shader globals declared in the 'in' or 'out'
2280 // storage class. An HLSL entry point has a return value, input parameters
2281 // and output parameters. These need to get remapped to the AST I/O.
remapEntryPointIO(TFunction & function,TVariable * & returnValue,TVector<TVariable * > & inputs,TVector<TVariable * > & outputs)2282 void HlslParseContext::remapEntryPointIO(TFunction& function, TVariable*& returnValue,
2283 TVector<TVariable*>& inputs, TVector<TVariable*>& outputs)
2284 {
2285 // We might have in input structure type with no decorations that caused it
2286 // to look like an input type, yet it has (e.g.) interpolation types that
2287 // must be modified that turn it into an input type.
2288 // Hence, a missing ioTypeMap for 'input' might need to be synthesized.
2289 const auto synthesizeEditedInput = [this](TType& type) {
2290 // True if a type needs to be 'flat'
2291 const auto needsFlat = [](const TType& type) {
2292 return type.containsBasicType(EbtInt) ||
2293 type.containsBasicType(EbtUint) ||
2294 type.containsBasicType(EbtInt64) ||
2295 type.containsBasicType(EbtUint64) ||
2296 type.containsBasicType(EbtBool) ||
2297 type.containsBasicType(EbtDouble);
2298 };
2299
2300 if (language == EShLangFragment && needsFlat(type)) {
2301 if (type.isStruct()) {
2302 TTypeList* finalList = nullptr;
2303 auto it = ioTypeMap.find(type.getStruct());
2304 if (it == ioTypeMap.end() || it->second.input == nullptr) {
2305 // Getting here means we have no input struct, but we need one.
2306 auto list = new TTypeList;
2307 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
2308 TType* newType = new TType;
2309 newType->shallowCopy(*member->type);
2310 TTypeLoc typeLoc = { newType, member->loc };
2311 list->push_back(typeLoc);
2312 }
2313 // install the new input type
2314 if (it == ioTypeMap.end()) {
2315 tIoKinds newLists = { list, nullptr, nullptr };
2316 ioTypeMap[type.getStruct()] = newLists;
2317 } else
2318 it->second.input = list;
2319 finalList = list;
2320 } else
2321 finalList = it->second.input;
2322 // edit for 'flat'
2323 for (auto member = finalList->begin(); member != finalList->end(); ++member) {
2324 if (needsFlat(*member->type)) {
2325 member->type->getQualifier().clearInterpolation();
2326 member->type->getQualifier().flat = true;
2327 }
2328 }
2329 } else {
2330 type.getQualifier().clearInterpolation();
2331 type.getQualifier().flat = true;
2332 }
2333 }
2334 };
2335
2336 // Do the actual work to make a type be a shader input or output variable,
2337 // and clear the original to be non-IO (for use as a normal function parameter/return).
2338 const auto makeIoVariable = [this](const char* name, TType& type, TStorageQualifier storage) -> TVariable* {
2339 TVariable* ioVariable = makeInternalVariable(name, type);
2340 clearUniformInputOutput(type.getQualifier());
2341 if (type.isStruct()) {
2342 auto newLists = ioTypeMap.find(ioVariable->getType().getStruct());
2343 if (newLists != ioTypeMap.end()) {
2344 if (storage == EvqVaryingIn && newLists->second.input)
2345 ioVariable->getWritableType().setStruct(newLists->second.input);
2346 else if (storage == EvqVaryingOut && newLists->second.output)
2347 ioVariable->getWritableType().setStruct(newLists->second.output);
2348 }
2349 }
2350 if (storage == EvqVaryingIn) {
2351 correctInput(ioVariable->getWritableType().getQualifier());
2352 if (language == EShLangTessEvaluation)
2353 if (!ioVariable->getType().isArray())
2354 ioVariable->getWritableType().getQualifier().patch = true;
2355 } else {
2356 correctOutput(ioVariable->getWritableType().getQualifier());
2357 }
2358 ioVariable->getWritableType().getQualifier().storage = storage;
2359
2360 fixBuiltInIoType(ioVariable->getWritableType());
2361
2362 return ioVariable;
2363 };
2364
2365 // return value is actually a shader-scoped output (out)
2366 if (function.getType().getBasicType() == EbtVoid) {
2367 returnValue = nullptr;
2368 } else {
2369 if (language == EShLangTessControl) {
2370 // tessellation evaluation in HLSL writes a per-ctrl-pt value, but it needs to be an
2371 // array in SPIR-V semantics. We'll write to it indexed by invocation ID.
2372
2373 returnValue = makeIoVariable("@entryPointOutput", function.getWritableType(), EvqVaryingOut);
2374
2375 TType outputType;
2376 outputType.shallowCopy(function.getType());
2377
2378 // vertices has necessarily already been set when handling entry point attributes.
2379 TArraySizes* arraySizes = new TArraySizes;
2380 arraySizes->addInnerSize(intermediate.getVertices());
2381 outputType.transferArraySizes(arraySizes);
2382
2383 clearUniformInputOutput(function.getWritableType().getQualifier());
2384 returnValue = makeIoVariable("@entryPointOutput", outputType, EvqVaryingOut);
2385 } else {
2386 returnValue = makeIoVariable("@entryPointOutput", function.getWritableType(), EvqVaryingOut);
2387 }
2388 }
2389
2390 // parameters are actually shader-scoped inputs and outputs (in or out)
2391 for (int i = 0; i < function.getParamCount(); i++) {
2392 TType& paramType = *function[i].type;
2393 if (paramType.getQualifier().isParamInput()) {
2394 synthesizeEditedInput(paramType);
2395 TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType, EvqVaryingIn);
2396 inputs.push_back(argAsGlobal);
2397 }
2398 if (paramType.getQualifier().isParamOutput()) {
2399 TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType, EvqVaryingOut);
2400 outputs.push_back(argAsGlobal);
2401 }
2402 }
2403 }
2404
2405 // An HLSL function that looks like an entry point, but is not,
2406 // declares entry point IO built-ins, but these have to be undone.
remapNonEntryPointIO(TFunction & function)2407 void HlslParseContext::remapNonEntryPointIO(TFunction& function)
2408 {
2409 // return value
2410 if (function.getType().getBasicType() != EbtVoid)
2411 clearUniformInputOutput(function.getWritableType().getQualifier());
2412
2413 // parameters.
2414 // References to structuredbuffer types are left unmodified
2415 for (int i = 0; i < function.getParamCount(); i++)
2416 if (!isReference(*function[i].type))
2417 clearUniformInputOutput(function[i].type->getQualifier());
2418 }
2419
2420 // Handle function returns, including type conversions to the function return type
2421 // if necessary.
handleReturnValue(const TSourceLoc & loc,TIntermTyped * value)2422 TIntermNode* HlslParseContext::handleReturnValue(const TSourceLoc& loc, TIntermTyped* value)
2423 {
2424 functionReturnsValue = true;
2425
2426 if (currentFunctionType->getBasicType() == EbtVoid) {
2427 error(loc, "void function cannot return a value", "return", "");
2428 return intermediate.addBranch(EOpReturn, loc);
2429 } else if (*currentFunctionType != value->getType()) {
2430 value = intermediate.addConversion(EOpReturn, *currentFunctionType, value);
2431 if (value && *currentFunctionType != value->getType())
2432 value = intermediate.addUniShapeConversion(EOpReturn, *currentFunctionType, value);
2433 if (value == nullptr || *currentFunctionType != value->getType()) {
2434 error(loc, "type does not match, or is not convertible to, the function's return type", "return", "");
2435 return value;
2436 }
2437 }
2438
2439 return intermediate.addBranch(EOpReturn, value, loc);
2440 }
2441
handleFunctionArgument(TFunction * function,TIntermTyped * & arguments,TIntermTyped * newArg)2442 void HlslParseContext::handleFunctionArgument(TFunction* function,
2443 TIntermTyped*& arguments, TIntermTyped* newArg)
2444 {
2445 TParameter param = { 0, new TType, nullptr };
2446 param.type->shallowCopy(newArg->getType());
2447
2448 function->addParameter(param);
2449 if (arguments)
2450 arguments = intermediate.growAggregate(arguments, newArg);
2451 else
2452 arguments = newArg;
2453 }
2454
2455 // Position may require special handling: we can optionally invert Y.
2456 // See: https://github.com/KhronosGroup/glslang/issues/1173
2457 // https://github.com/KhronosGroup/glslang/issues/494
assignPosition(const TSourceLoc & loc,TOperator op,TIntermTyped * left,TIntermTyped * right)2458 TIntermTyped* HlslParseContext::assignPosition(const TSourceLoc& loc, TOperator op,
2459 TIntermTyped* left, TIntermTyped* right)
2460 {
2461 // If we are not asked for Y inversion, use a plain old assign.
2462 if (!intermediate.getInvertY())
2463 return intermediate.addAssign(op, left, right, loc);
2464
2465 // If we get here, we should invert Y.
2466 TIntermAggregate* assignList = nullptr;
2467
2468 // If this is a complex rvalue, we don't want to dereference it many times. Create a temporary.
2469 TVariable* rhsTempVar = nullptr;
2470 rhsTempVar = makeInternalVariable("@position", right->getType());
2471 rhsTempVar->getWritableType().getQualifier().makeTemporary();
2472
2473 {
2474 TIntermTyped* rhsTempSym = intermediate.addSymbol(*rhsTempVar, loc);
2475 assignList = intermediate.growAggregate(assignList,
2476 intermediate.addAssign(EOpAssign, rhsTempSym, right, loc), loc);
2477 }
2478
2479 // pos.y = -pos.y
2480 {
2481 const int Y = 1;
2482
2483 TIntermTyped* tempSymL = intermediate.addSymbol(*rhsTempVar, loc);
2484 TIntermTyped* tempSymR = intermediate.addSymbol(*rhsTempVar, loc);
2485 TIntermTyped* index = intermediate.addConstantUnion(Y, loc);
2486
2487 TIntermTyped* lhsElement = intermediate.addIndex(EOpIndexDirect, tempSymL, index, loc);
2488 TIntermTyped* rhsElement = intermediate.addIndex(EOpIndexDirect, tempSymR, index, loc);
2489
2490 const TType derefType(right->getType(), 0);
2491
2492 lhsElement->setType(derefType);
2493 rhsElement->setType(derefType);
2494
2495 TIntermTyped* yNeg = intermediate.addUnaryMath(EOpNegative, rhsElement, loc);
2496
2497 assignList = intermediate.growAggregate(assignList, intermediate.addAssign(EOpAssign, lhsElement, yNeg, loc));
2498 }
2499
2500 // Assign the rhs temp (now with Y inversion) to the final output
2501 {
2502 TIntermTyped* rhsTempSym = intermediate.addSymbol(*rhsTempVar, loc);
2503 assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, left, rhsTempSym, loc));
2504 }
2505
2506 assert(assignList != nullptr);
2507 assignList->setOperator(EOpSequence);
2508
2509 return assignList;
2510 }
2511
2512 // Clip and cull distance require special handling due to a semantic mismatch. In HLSL,
2513 // these can be float scalar, float vector, or arrays of float scalar or float vector.
2514 // In SPIR-V, they are arrays of scalar floats in all cases. We must copy individual components
2515 // (e.g, both x and y components of a float2) out into the destination float array.
2516 //
2517 // The values are assigned to sequential members of the output array. The inner dimension
2518 // is vector components. The outer dimension is array elements.
assignClipCullDistance(const TSourceLoc & loc,TOperator op,int semanticId,TIntermTyped * left,TIntermTyped * right)2519 TIntermAggregate* HlslParseContext::assignClipCullDistance(const TSourceLoc& loc, TOperator op, int semanticId,
2520 TIntermTyped* left, TIntermTyped* right)
2521 {
2522 switch (language) {
2523 case EShLangFragment:
2524 case EShLangVertex:
2525 case EShLangGeometry:
2526 break;
2527 default:
2528 error(loc, "unimplemented: clip/cull not currently implemented for this stage", "", "");
2529 return nullptr;
2530 }
2531
2532 TVariable** clipCullVar = nullptr;
2533
2534 // Figure out if we are assigning to, or from, clip or cull distance.
2535 const bool isOutput = isClipOrCullDistance(left->getType());
2536
2537 // This is the rvalue or lvalue holding the clip or cull distance.
2538 TIntermTyped* clipCullNode = isOutput ? left : right;
2539 // This is the value going into or out of the clip or cull distance.
2540 TIntermTyped* internalNode = isOutput ? right : left;
2541
2542 const TBuiltInVariable builtInType = clipCullNode->getQualifier().builtIn;
2543
2544 decltype(clipSemanticNSizeIn)* semanticNSize = nullptr;
2545
2546 // Refer to either the clip or the cull distance, depending on semantic.
2547 switch (builtInType) {
2548 case EbvClipDistance:
2549 clipCullVar = isOutput ? &clipDistanceOutput : &clipDistanceInput;
2550 semanticNSize = isOutput ? &clipSemanticNSizeOut : &clipSemanticNSizeIn;
2551 break;
2552 case EbvCullDistance:
2553 clipCullVar = isOutput ? &cullDistanceOutput : &cullDistanceInput;
2554 semanticNSize = isOutput ? &cullSemanticNSizeOut : &cullSemanticNSizeIn;
2555 break;
2556
2557 // called invalidly: we expected a clip or a cull distance.
2558 // static compile time problem: should not happen.
2559 default: assert(0); return nullptr;
2560 }
2561
2562 // This is the offset in the destination array of a given semantic's data
2563 std::array<int, maxClipCullRegs> semanticOffset;
2564
2565 // Calculate offset of variable of semantic N in destination array
2566 int arrayLoc = 0;
2567 int vecItems = 0;
2568
2569 for (int x = 0; x < maxClipCullRegs; ++x) {
2570 // See if we overflowed the vec4 packing
2571 if ((vecItems + (*semanticNSize)[x]) > 4) {
2572 arrayLoc = (arrayLoc + 3) & (~0x3); // round up to next multiple of 4
2573 vecItems = 0;
2574 }
2575
2576 semanticOffset[x] = arrayLoc;
2577 vecItems += (*semanticNSize)[x];
2578 arrayLoc += (*semanticNSize)[x];
2579 }
2580
2581
2582 // It can have up to 2 array dimensions (in the case of geometry shader inputs)
2583 const TArraySizes* const internalArraySizes = internalNode->getType().getArraySizes();
2584 const int internalArrayDims = internalNode->getType().isArray() ? internalArraySizes->getNumDims() : 0;
2585 // vector sizes:
2586 const int internalVectorSize = internalNode->getType().getVectorSize();
2587 // array sizes, or 1 if it's not an array:
2588 const int internalInnerArraySize = (internalArrayDims > 0 ? internalArraySizes->getDimSize(internalArrayDims-1) : 1);
2589 const int internalOuterArraySize = (internalArrayDims > 1 ? internalArraySizes->getDimSize(0) : 1);
2590
2591 // The created type may be an array of arrays, e.g, for geometry shader inputs.
2592 const bool isImplicitlyArrayed = (language == EShLangGeometry && !isOutput);
2593
2594 // If we haven't created the output already, create it now.
2595 if (*clipCullVar == nullptr) {
2596 // ClipDistance and CullDistance are handled specially in the entry point input/output copy
2597 // algorithm, because they may need to be unpacked from components of vectors (or a scalar)
2598 // into a float array, or vice versa. Here, we make the array the right size and type,
2599 // which depends on the incoming data, which has several potential dimensions:
2600 // * Semantic ID
2601 // * vector size
2602 // * array size
2603 // Of those, semantic ID and array size cannot appear simultaneously.
2604 //
2605 // Also to note: for implicitly arrayed forms (e.g, geometry shader inputs), we need to create two
2606 // array dimensions. The shader's declaration may have one or two array dimensions. One is always
2607 // the geometry's dimension.
2608
2609 const bool useInnerSize = internalArrayDims > 1 || !isImplicitlyArrayed;
2610
2611 const int requiredInnerArraySize = arrayLoc * (useInnerSize ? internalInnerArraySize : 1);
2612 const int requiredOuterArraySize = (internalArrayDims > 0) ? internalArraySizes->getDimSize(0) : 1;
2613
2614 TType clipCullType(EbtFloat, clipCullNode->getType().getQualifier().storage, 1);
2615 clipCullType.getQualifier() = clipCullNode->getType().getQualifier();
2616
2617 // Create required array dimension
2618 TArraySizes* arraySizes = new TArraySizes;
2619 if (isImplicitlyArrayed)
2620 arraySizes->addInnerSize(requiredOuterArraySize);
2621 arraySizes->addInnerSize(requiredInnerArraySize);
2622 clipCullType.transferArraySizes(arraySizes);
2623
2624 // Obtain symbol name: we'll use that for the symbol we introduce.
2625 TIntermSymbol* sym = clipCullNode->getAsSymbolNode();
2626 assert(sym != nullptr);
2627
2628 // We are moving the semantic ID from the layout location, so it is no longer needed or
2629 // desired there.
2630 clipCullType.getQualifier().layoutLocation = TQualifier::layoutLocationEnd;
2631
2632 // Create variable and track its linkage
2633 *clipCullVar = makeInternalVariable(sym->getName().c_str(), clipCullType);
2634
2635 trackLinkage(**clipCullVar);
2636 }
2637
2638 // Create symbol for the clip or cull variable.
2639 TIntermSymbol* clipCullSym = intermediate.addSymbol(**clipCullVar);
2640
2641 // vector sizes:
2642 const int clipCullVectorSize = clipCullSym->getType().getVectorSize();
2643
2644 // array sizes, or 1 if it's not an array:
2645 const TArraySizes* const clipCullArraySizes = clipCullSym->getType().getArraySizes();
2646 const int clipCullOuterArraySize = isImplicitlyArrayed ? clipCullArraySizes->getDimSize(0) : 1;
2647 const int clipCullInnerArraySize = clipCullArraySizes->getDimSize(isImplicitlyArrayed ? 1 : 0);
2648
2649 // clipCullSym has got to be an array of scalar floats, per SPIR-V semantics.
2650 // fixBuiltInIoType() should have handled that upstream.
2651 assert(clipCullSym->getType().isArray());
2652 assert(clipCullSym->getType().getVectorSize() == 1);
2653 assert(clipCullSym->getType().getBasicType() == EbtFloat);
2654
2655 // We may be creating multiple sub-assignments. This is an aggregate to hold them.
2656 // TODO: it would be possible to be clever sometimes and avoid the sequence node if not needed.
2657 TIntermAggregate* assignList = nullptr;
2658
2659 // Holds individual component assignments as we make them.
2660 TIntermTyped* clipCullAssign = nullptr;
2661
2662 // If the types are homomorphic, use a simple assign. No need to mess about with
2663 // individual components.
2664 if (clipCullSym->getType().isArray() == internalNode->getType().isArray() &&
2665 clipCullInnerArraySize == internalInnerArraySize &&
2666 clipCullOuterArraySize == internalOuterArraySize &&
2667 clipCullVectorSize == internalVectorSize) {
2668
2669 if (isOutput)
2670 clipCullAssign = intermediate.addAssign(op, clipCullSym, internalNode, loc);
2671 else
2672 clipCullAssign = intermediate.addAssign(op, internalNode, clipCullSym, loc);
2673
2674 assignList = intermediate.growAggregate(assignList, clipCullAssign);
2675 assignList->setOperator(EOpSequence);
2676
2677 return assignList;
2678 }
2679
2680 // We are going to copy each component of the internal (per array element if indicated) to sequential
2681 // array elements of the clipCullSym. This tracks the lhs element we're writing to as we go along.
2682 // We may be starting in the middle - e.g, for a non-zero semantic ID calculated above.
2683 int clipCullInnerArrayPos = semanticOffset[semanticId];
2684 int clipCullOuterArrayPos = 0;
2685
2686 // Lambda to add an index to a node, set the type of the result, and return the new node.
2687 const auto addIndex = [this, &loc](TIntermTyped* node, int pos) -> TIntermTyped* {
2688 const TType derefType(node->getType(), 0);
2689 node = intermediate.addIndex(EOpIndexDirect, node, intermediate.addConstantUnion(pos, loc), loc);
2690 node->setType(derefType);
2691 return node;
2692 };
2693
2694 // Loop through every component of every element of the internal, and copy to or from the matching external.
2695 for (int internalOuterArrayPos = 0; internalOuterArrayPos < internalOuterArraySize; ++internalOuterArrayPos) {
2696 for (int internalInnerArrayPos = 0; internalInnerArrayPos < internalInnerArraySize; ++internalInnerArrayPos) {
2697 for (int internalComponent = 0; internalComponent < internalVectorSize; ++internalComponent) {
2698 // clip/cull array member to read from / write to:
2699 TIntermTyped* clipCullMember = clipCullSym;
2700
2701 // If implicitly arrayed, there is an outer array dimension involved
2702 if (isImplicitlyArrayed)
2703 clipCullMember = addIndex(clipCullMember, clipCullOuterArrayPos);
2704
2705 // Index into proper array position for clip cull member
2706 clipCullMember = addIndex(clipCullMember, clipCullInnerArrayPos++);
2707
2708 // if needed, start over with next outer array slice.
2709 if (isImplicitlyArrayed && clipCullInnerArrayPos >= clipCullInnerArraySize) {
2710 clipCullInnerArrayPos = semanticOffset[semanticId];
2711 ++clipCullOuterArrayPos;
2712 }
2713
2714 // internal member to read from / write to:
2715 TIntermTyped* internalMember = internalNode;
2716
2717 // If internal node has outer array dimension, index appropriately.
2718 if (internalArrayDims > 1)
2719 internalMember = addIndex(internalMember, internalOuterArrayPos);
2720
2721 // If internal node has inner array dimension, index appropriately.
2722 if (internalArrayDims > 0)
2723 internalMember = addIndex(internalMember, internalInnerArrayPos);
2724
2725 // If internal node is a vector, extract the component of interest.
2726 if (internalNode->getType().isVector())
2727 internalMember = addIndex(internalMember, internalComponent);
2728
2729 // Create an assignment: output from internal to clip cull, or input from clip cull to internal.
2730 if (isOutput)
2731 clipCullAssign = intermediate.addAssign(op, clipCullMember, internalMember, loc);
2732 else
2733 clipCullAssign = intermediate.addAssign(op, internalMember, clipCullMember, loc);
2734
2735 // Track assignment in the sequence.
2736 assignList = intermediate.growAggregate(assignList, clipCullAssign);
2737 }
2738 }
2739 }
2740
2741 assert(assignList != nullptr);
2742 assignList->setOperator(EOpSequence);
2743
2744 return assignList;
2745 }
2746
2747 // Some simple source assignments need to be flattened to a sequence
2748 // of AST assignments. Catch these and flatten, otherwise, pass through
2749 // to intermediate.addAssign().
2750 //
2751 // Also, assignment to matrix swizzles requires multiple component assignments,
2752 // intercept those as well.
handleAssign(const TSourceLoc & loc,TOperator op,TIntermTyped * left,TIntermTyped * right)2753 TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op, TIntermTyped* left,
2754 TIntermTyped* right)
2755 {
2756 if (left == nullptr || right == nullptr)
2757 return nullptr;
2758
2759 // writing to opaques will require fixing transforms
2760 if (left->getType().containsOpaque())
2761 intermediate.setNeedsLegalization();
2762
2763 if (left->getAsOperator() && left->getAsOperator()->getOp() == EOpMatrixSwizzle)
2764 return handleAssignToMatrixSwizzle(loc, op, left, right);
2765
2766 // Return true if the given node is an index operation into a split variable.
2767 const auto indexesSplit = [this](const TIntermTyped* node) -> bool {
2768 const TIntermBinary* binaryNode = node->getAsBinaryNode();
2769
2770 if (binaryNode == nullptr)
2771 return false;
2772
2773 return (binaryNode->getOp() == EOpIndexDirect || binaryNode->getOp() == EOpIndexIndirect) &&
2774 wasSplit(binaryNode->getLeft());
2775 };
2776
2777 // Return symbol if node is symbol or index ref
2778 const auto getSymbol = [](const TIntermTyped* node) -> const TIntermSymbol* {
2779 const TIntermSymbol* symbolNode = node->getAsSymbolNode();
2780 if (symbolNode != nullptr)
2781 return symbolNode;
2782
2783 const TIntermBinary* binaryNode = node->getAsBinaryNode();
2784 if (binaryNode != nullptr && (binaryNode->getOp() == EOpIndexDirect || binaryNode->getOp() == EOpIndexIndirect))
2785 return binaryNode->getLeft()->getAsSymbolNode();
2786
2787 return nullptr;
2788 };
2789
2790 // Return true if this stage assigns clip position with potentially inverted Y
2791 const auto assignsClipPos = [this](const TIntermTyped* node) -> bool {
2792 return node->getType().getQualifier().builtIn == EbvPosition &&
2793 (language == EShLangVertex || language == EShLangGeometry || language == EShLangTessEvaluation);
2794 };
2795
2796 const TIntermSymbol* leftSymbol = getSymbol(left);
2797 const TIntermSymbol* rightSymbol = getSymbol(right);
2798
2799 const bool isSplitLeft = wasSplit(left) || indexesSplit(left);
2800 const bool isSplitRight = wasSplit(right) || indexesSplit(right);
2801
2802 const bool isFlattenLeft = wasFlattened(leftSymbol);
2803 const bool isFlattenRight = wasFlattened(rightSymbol);
2804
2805 // OK to do a single assign if neither side is split or flattened. Otherwise,
2806 // fall through to a member-wise copy.
2807 if (!isFlattenLeft && !isFlattenRight && !isSplitLeft && !isSplitRight) {
2808 // Clip and cull distance requires more processing. See comment above assignClipCullDistance.
2809 if (isClipOrCullDistance(left->getType()) || isClipOrCullDistance(right->getType())) {
2810 const bool isOutput = isClipOrCullDistance(left->getType());
2811
2812 const int semanticId = (isOutput ? left : right)->getType().getQualifier().layoutLocation;
2813 return assignClipCullDistance(loc, op, semanticId, left, right);
2814 } else if (assignsClipPos(left)) {
2815 // Position can require special handling: see comment above assignPosition
2816 return assignPosition(loc, op, left, right);
2817 } else if (left->getQualifier().builtIn == EbvSampleMask) {
2818 // Certain builtins are required to be arrayed outputs in SPIR-V, but may internally be scalars
2819 // in the shader. Copy the scalar RHS into the LHS array element zero, if that happens.
2820 if (left->isArray() && !right->isArray()) {
2821 const TType derefType(left->getType(), 0);
2822 left = intermediate.addIndex(EOpIndexDirect, left, intermediate.addConstantUnion(0, loc), loc);
2823 left->setType(derefType);
2824 // Fall through to add assign.
2825 }
2826 }
2827
2828 return intermediate.addAssign(op, left, right, loc);
2829 }
2830
2831 TIntermAggregate* assignList = nullptr;
2832 const TVector<TVariable*>* leftVariables = nullptr;
2833 const TVector<TVariable*>* rightVariables = nullptr;
2834
2835 // A temporary to store the right node's value, so we don't keep indirecting into it
2836 // if it's not a simple symbol.
2837 TVariable* rhsTempVar = nullptr;
2838
2839 // If the RHS is a simple symbol node, we'll copy it for each member.
2840 TIntermSymbol* cloneSymNode = nullptr;
2841
2842 int memberCount = 0;
2843
2844 // Track how many items there are to copy.
2845 if (left->getType().isStruct())
2846 memberCount = (int)left->getType().getStruct()->size();
2847 if (left->getType().isArray())
2848 memberCount = left->getType().getCumulativeArraySize();
2849
2850 if (isFlattenLeft)
2851 leftVariables = &flattenMap.find(leftSymbol->getId())->second.members;
2852
2853 if (isFlattenRight) {
2854 rightVariables = &flattenMap.find(rightSymbol->getId())->second.members;
2855 } else {
2856 // The RHS is not flattened. There are several cases:
2857 // 1. 1 item to copy: Use the RHS directly.
2858 // 2. >1 item, simple symbol RHS: we'll create a new TIntermSymbol node for each, but no assign to temp.
2859 // 3. >1 item, complex RHS: assign it to a new temp variable, and create a TIntermSymbol for each member.
2860
2861 if (memberCount <= 1) {
2862 // case 1: we'll use the symbol directly below. Nothing to do.
2863 } else {
2864 if (right->getAsSymbolNode() != nullptr) {
2865 // case 2: we'll copy the symbol per iteration below.
2866 cloneSymNode = right->getAsSymbolNode();
2867 } else {
2868 // case 3: assign to a temp, and indirect into that.
2869 rhsTempVar = makeInternalVariable("flattenTemp", right->getType());
2870 rhsTempVar->getWritableType().getQualifier().makeTemporary();
2871 TIntermTyped* noFlattenRHS = intermediate.addSymbol(*rhsTempVar, loc);
2872
2873 // Add this to the aggregate being built.
2874 assignList = intermediate.growAggregate(assignList,
2875 intermediate.addAssign(op, noFlattenRHS, right, loc), loc);
2876 }
2877 }
2878 }
2879
2880 // When dealing with split arrayed structures of built-ins, the arrayness is moved to the extracted built-in
2881 // variables, which is awkward when copying between split and unsplit structures. This variable tracks
2882 // array indirections so they can be percolated from outer structs to inner variables.
2883 std::vector <int> arrayElement;
2884
2885 TStorageQualifier leftStorage = left->getType().getQualifier().storage;
2886 TStorageQualifier rightStorage = right->getType().getQualifier().storage;
2887
2888 int leftOffsetStart = findSubtreeOffset(*left);
2889 int rightOffsetStart = findSubtreeOffset(*right);
2890 int leftOffset = leftOffsetStart;
2891 int rightOffset = rightOffsetStart;
2892
2893 const auto getMember = [&](bool isLeft, const TType& type, int member, TIntermTyped* splitNode, int splitMember,
2894 bool flattened)
2895 -> TIntermTyped * {
2896 const bool split = isLeft ? isSplitLeft : isSplitRight;
2897
2898 TIntermTyped* subTree;
2899 const TType derefType(type, member);
2900 const TVariable* builtInVar = nullptr;
2901 if ((flattened || split) && derefType.isBuiltIn()) {
2902 auto splitPair = splitBuiltIns.find(HlslParseContext::tInterstageIoData(
2903 derefType.getQualifier().builtIn,
2904 isLeft ? leftStorage : rightStorage));
2905 if (splitPair != splitBuiltIns.end())
2906 builtInVar = splitPair->second;
2907 }
2908 if (builtInVar != nullptr) {
2909 // copy from interstage IO built-in if needed
2910 subTree = intermediate.addSymbol(*builtInVar);
2911
2912 if (subTree->getType().isArray()) {
2913 // Arrayness of builtIn symbols isn't handled by the normal recursion:
2914 // it's been extracted and moved to the built-in.
2915 if (!arrayElement.empty()) {
2916 const TType splitDerefType(subTree->getType(), arrayElement.back());
2917 subTree = intermediate.addIndex(EOpIndexDirect, subTree,
2918 intermediate.addConstantUnion(arrayElement.back(), loc), loc);
2919 subTree->setType(splitDerefType);
2920 } else if (splitNode->getAsOperator() != nullptr && (splitNode->getAsOperator()->getOp() == EOpIndexIndirect)) {
2921 // This might also be a stage with arrayed outputs, in which case there's an index
2922 // operation we should transfer to the output builtin.
2923
2924 const TType splitDerefType(subTree->getType(), 0);
2925 subTree = intermediate.addIndex(splitNode->getAsOperator()->getOp(), subTree,
2926 splitNode->getAsBinaryNode()->getRight(), loc);
2927 subTree->setType(splitDerefType);
2928 }
2929 }
2930 } else if (flattened && !shouldFlatten(derefType, isLeft ? leftStorage : rightStorage, false)) {
2931 if (isLeft) {
2932 // offset will cycle through variables for arrayed io
2933 if (leftOffset >= static_cast<int>(leftVariables->size()))
2934 leftOffset = leftOffsetStart;
2935 subTree = intermediate.addSymbol(*(*leftVariables)[leftOffset++]);
2936 } else {
2937 // offset will cycle through variables for arrayed io
2938 if (rightOffset >= static_cast<int>(rightVariables->size()))
2939 rightOffset = rightOffsetStart;
2940 subTree = intermediate.addSymbol(*(*rightVariables)[rightOffset++]);
2941 }
2942
2943 // arrayed io
2944 if (subTree->getType().isArray()) {
2945 if (!arrayElement.empty()) {
2946 const TType derefType(subTree->getType(), arrayElement.front());
2947 subTree = intermediate.addIndex(EOpIndexDirect, subTree,
2948 intermediate.addConstantUnion(arrayElement.front(), loc), loc);
2949 subTree->setType(derefType);
2950 } else {
2951 // There's an index operation we should transfer to the output builtin.
2952 assert(splitNode->getAsOperator() != nullptr &&
2953 splitNode->getAsOperator()->getOp() == EOpIndexIndirect);
2954 const TType splitDerefType(subTree->getType(), 0);
2955 subTree = intermediate.addIndex(splitNode->getAsOperator()->getOp(), subTree,
2956 splitNode->getAsBinaryNode()->getRight(), loc);
2957 subTree->setType(splitDerefType);
2958 }
2959 }
2960 } else {
2961 // Index operator if it's an aggregate, else EOpNull
2962 const TOperator accessOp = type.isArray() ? EOpIndexDirect
2963 : type.isStruct() ? EOpIndexDirectStruct
2964 : EOpNull;
2965 if (accessOp == EOpNull) {
2966 subTree = splitNode;
2967 } else {
2968 subTree = intermediate.addIndex(accessOp, splitNode, intermediate.addConstantUnion(splitMember, loc),
2969 loc);
2970 const TType splitDerefType(splitNode->getType(), splitMember);
2971 subTree->setType(splitDerefType);
2972 }
2973 }
2974
2975 return subTree;
2976 };
2977
2978 // Use the proper RHS node: a new symbol from a TVariable, copy
2979 // of an TIntermSymbol node, or sometimes the right node directly.
2980 right = rhsTempVar != nullptr ? intermediate.addSymbol(*rhsTempVar, loc) :
2981 cloneSymNode != nullptr ? intermediate.addSymbol(*cloneSymNode) :
2982 right;
2983
2984 // Cannot use auto here, because this is recursive, and auto can't work out the type without seeing the
2985 // whole thing. So, we'll resort to an explicit type via std::function.
2986 const std::function<void(TIntermTyped* left, TIntermTyped* right, TIntermTyped* splitLeft, TIntermTyped* splitRight,
2987 bool topLevel)>
2988 traverse = [&](TIntermTyped* left, TIntermTyped* right, TIntermTyped* splitLeft, TIntermTyped* splitRight,
2989 bool topLevel) -> void {
2990 // If we get here, we are assigning to or from a whole array or struct that must be
2991 // flattened, so have to do member-by-member assignment:
2992
2993 bool shouldFlattenSubsetLeft = isFlattenLeft && shouldFlatten(left->getType(), leftStorage, topLevel);
2994 bool shouldFlattenSubsetRight = isFlattenRight && shouldFlatten(right->getType(), rightStorage, topLevel);
2995
2996 if ((left->getType().isArray() || right->getType().isArray()) &&
2997 (shouldFlattenSubsetLeft || isSplitLeft ||
2998 shouldFlattenSubsetRight || isSplitRight)) {
2999 const int elementsL = left->getType().isArray() ? left->getType().getOuterArraySize() : 1;
3000 const int elementsR = right->getType().isArray() ? right->getType().getOuterArraySize() : 1;
3001
3002 // The arrays might not be the same size,
3003 // e.g., if the size has been forced for EbvTessLevelInner/Outer.
3004 const int elementsToCopy = std::min(elementsL, elementsR);
3005
3006 // array case
3007 for (int element = 0; element < elementsToCopy; ++element) {
3008 arrayElement.push_back(element);
3009
3010 // Add a new AST symbol node if we have a temp variable holding a complex RHS.
3011 TIntermTyped* subLeft = getMember(true, left->getType(), element, left, element,
3012 shouldFlattenSubsetLeft);
3013 TIntermTyped* subRight = getMember(false, right->getType(), element, right, element,
3014 shouldFlattenSubsetRight);
3015
3016 TIntermTyped* subSplitLeft = isSplitLeft ? getMember(true, left->getType(), element, splitLeft,
3017 element, shouldFlattenSubsetLeft)
3018 : subLeft;
3019 TIntermTyped* subSplitRight = isSplitRight ? getMember(false, right->getType(), element, splitRight,
3020 element, shouldFlattenSubsetRight)
3021 : subRight;
3022
3023 traverse(subLeft, subRight, subSplitLeft, subSplitRight, false);
3024
3025 arrayElement.pop_back();
3026 }
3027 } else if (left->getType().isStruct() && (shouldFlattenSubsetLeft || isSplitLeft ||
3028 shouldFlattenSubsetRight || isSplitRight)) {
3029 // struct case
3030 const auto& membersL = *left->getType().getStruct();
3031 const auto& membersR = *right->getType().getStruct();
3032
3033 // These track the members in the split structures corresponding to the same in the unsplit structures,
3034 // which we traverse in parallel.
3035 int memberL = 0;
3036 int memberR = 0;
3037
3038 // Handle empty structure assignment
3039 if (int(membersL.size()) == 0 && int(membersR.size()) == 0)
3040 assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, left, right, loc), loc);
3041
3042 for (int member = 0; member < int(membersL.size()); ++member) {
3043 const TType& typeL = *membersL[member].type;
3044 const TType& typeR = *membersR[member].type;
3045
3046 TIntermTyped* subLeft = getMember(true, left->getType(), member, left, member,
3047 shouldFlattenSubsetLeft);
3048 TIntermTyped* subRight = getMember(false, right->getType(), member, right, member,
3049 shouldFlattenSubsetRight);
3050
3051 // If there is no splitting, use the same values to avoid inefficiency.
3052 TIntermTyped* subSplitLeft = isSplitLeft ? getMember(true, left->getType(), member, splitLeft,
3053 memberL, shouldFlattenSubsetLeft)
3054 : subLeft;
3055 TIntermTyped* subSplitRight = isSplitRight ? getMember(false, right->getType(), member, splitRight,
3056 memberR, shouldFlattenSubsetRight)
3057 : subRight;
3058
3059 if (isClipOrCullDistance(subSplitLeft->getType()) || isClipOrCullDistance(subSplitRight->getType())) {
3060 // Clip and cull distance built-in assignment is complex in its own right, and is handled in
3061 // a separate function dedicated to that task. See comment above assignClipCullDistance;
3062
3063 const bool isOutput = isClipOrCullDistance(subSplitLeft->getType());
3064
3065 // Since all clip/cull semantics boil down to the same built-in type, we need to get the
3066 // semantic ID from the dereferenced type's layout location, to avoid an N-1 mapping.
3067 const TType derefType((isOutput ? left : right)->getType(), member);
3068 const int semanticId = derefType.getQualifier().layoutLocation;
3069
3070 TIntermAggregate* clipCullAssign = assignClipCullDistance(loc, op, semanticId,
3071 subSplitLeft, subSplitRight);
3072
3073 assignList = intermediate.growAggregate(assignList, clipCullAssign, loc);
3074 } else if (assignsClipPos(subSplitLeft)) {
3075 // Position can require special handling: see comment above assignPosition
3076 TIntermTyped* positionAssign = assignPosition(loc, op, subSplitLeft, subSplitRight);
3077 assignList = intermediate.growAggregate(assignList, positionAssign, loc);
3078 } else if (!shouldFlattenSubsetLeft && !shouldFlattenSubsetRight &&
3079 !typeL.containsBuiltIn() && !typeR.containsBuiltIn()) {
3080 // If this is the final flattening (no nested types below to flatten)
3081 // we'll copy the member, else recurse into the type hierarchy.
3082 // However, if splitting the struct, that means we can copy a whole
3083 // subtree here IFF it does not itself contain any interstage built-in
3084 // IO variables, so we only have to recurse into it if there's something
3085 // for splitting to do. That can save a lot of AST verbosity for
3086 // a bunch of memberwise copies.
3087
3088 assignList = intermediate.growAggregate(assignList,
3089 intermediate.addAssign(op, subSplitLeft, subSplitRight, loc),
3090 loc);
3091 } else {
3092 traverse(subLeft, subRight, subSplitLeft, subSplitRight, false);
3093 }
3094
3095 memberL += (typeL.isBuiltIn() ? 0 : 1);
3096 memberR += (typeR.isBuiltIn() ? 0 : 1);
3097 }
3098 } else {
3099 // Member copy
3100 assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, left, right, loc), loc);
3101 }
3102
3103 };
3104
3105 TIntermTyped* splitLeft = left;
3106 TIntermTyped* splitRight = right;
3107
3108 // If either left or right was a split structure, we must read or write it, but still have to
3109 // parallel-recurse through the unsplit structure to identify the built-in IO vars.
3110 // The left can be either a symbol, or an index into a symbol (e.g, array reference)
3111 if (isSplitLeft) {
3112 if (indexesSplit(left)) {
3113 // Index case: Refer to the indexed symbol, if the left is an index operator.
3114 const TIntermSymbol* symNode = left->getAsBinaryNode()->getLeft()->getAsSymbolNode();
3115
3116 TIntermTyped* splitLeftNonIo = intermediate.addSymbol(*getSplitNonIoVar(symNode->getId()), loc);
3117
3118 splitLeft = intermediate.addIndex(left->getAsBinaryNode()->getOp(), splitLeftNonIo,
3119 left->getAsBinaryNode()->getRight(), loc);
3120
3121 const TType derefType(splitLeftNonIo->getType(), 0);
3122 splitLeft->setType(derefType);
3123 } else {
3124 // Symbol case: otherwise, if not indexed, we have the symbol directly.
3125 const TIntermSymbol* symNode = left->getAsSymbolNode();
3126 splitLeft = intermediate.addSymbol(*getSplitNonIoVar(symNode->getId()), loc);
3127 }
3128 }
3129
3130 if (isSplitRight)
3131 splitRight = intermediate.addSymbol(*getSplitNonIoVar(right->getAsSymbolNode()->getId()), loc);
3132
3133 // This makes the whole assignment, recursing through subtypes as needed.
3134 traverse(left, right, splitLeft, splitRight, true);
3135
3136 assert(assignList != nullptr);
3137 assignList->setOperator(EOpSequence);
3138
3139 return assignList;
3140 }
3141
3142 // An assignment to matrix swizzle must be decomposed into individual assignments.
3143 // These must be selected component-wise from the RHS and stored component-wise
3144 // into the LHS.
handleAssignToMatrixSwizzle(const TSourceLoc & loc,TOperator op,TIntermTyped * left,TIntermTyped * right)3145 TIntermTyped* HlslParseContext::handleAssignToMatrixSwizzle(const TSourceLoc& loc, TOperator op, TIntermTyped* left,
3146 TIntermTyped* right)
3147 {
3148 assert(left->getAsOperator() && left->getAsOperator()->getOp() == EOpMatrixSwizzle);
3149
3150 if (op != EOpAssign)
3151 error(loc, "only simple assignment to non-simple matrix swizzle is supported", "assign", "");
3152
3153 // isolate the matrix and swizzle nodes
3154 TIntermTyped* matrix = left->getAsBinaryNode()->getLeft()->getAsTyped();
3155 const TIntermSequence& swizzle = left->getAsBinaryNode()->getRight()->getAsAggregate()->getSequence();
3156
3157 // if the RHS isn't already a simple vector, let's store into one
3158 TIntermSymbol* vector = right->getAsSymbolNode();
3159 TIntermTyped* vectorAssign = nullptr;
3160 if (vector == nullptr) {
3161 // create a new intermediate vector variable to assign to
3162 TType vectorType(matrix->getBasicType(), EvqTemporary, matrix->getQualifier().precision, (int)swizzle.size()/2);
3163 vector = intermediate.addSymbol(*makeInternalVariable("intermVec", vectorType), loc);
3164
3165 // assign the right to the new vector
3166 vectorAssign = handleAssign(loc, op, vector, right);
3167 }
3168
3169 // Assign the vector components to the matrix components.
3170 // Store this as a sequence, so a single aggregate node represents this
3171 // entire operation.
3172 TIntermAggregate* result = intermediate.makeAggregate(vectorAssign);
3173 TType columnType(matrix->getType(), 0);
3174 TType componentType(columnType, 0);
3175 TType indexType(EbtInt);
3176 for (int i = 0; i < (int)swizzle.size(); i += 2) {
3177 // the right component, single index into the RHS vector
3178 TIntermTyped* rightComp = intermediate.addIndex(EOpIndexDirect, vector,
3179 intermediate.addConstantUnion(i/2, loc), loc);
3180
3181 // the left component, double index into the LHS matrix
3182 TIntermTyped* leftComp = intermediate.addIndex(EOpIndexDirect, matrix,
3183 intermediate.addConstantUnion(swizzle[i]->getAsConstantUnion()->getConstArray(),
3184 indexType, loc),
3185 loc);
3186 leftComp->setType(columnType);
3187 leftComp = intermediate.addIndex(EOpIndexDirect, leftComp,
3188 intermediate.addConstantUnion(swizzle[i+1]->getAsConstantUnion()->getConstArray(),
3189 indexType, loc),
3190 loc);
3191 leftComp->setType(componentType);
3192
3193 // Add the assignment to the aggregate
3194 result = intermediate.growAggregate(result, intermediate.addAssign(op, leftComp, rightComp, loc));
3195 }
3196
3197 result->setOp(EOpSequence);
3198
3199 return result;
3200 }
3201
3202 //
3203 // HLSL atomic operations have slightly different arguments than
3204 // GLSL/AST/SPIRV. The semantics are converted below in decomposeIntrinsic.
3205 // This provides the post-decomposition equivalent opcode.
3206 //
mapAtomicOp(const TSourceLoc & loc,TOperator op,bool isImage)3207 TOperator HlslParseContext::mapAtomicOp(const TSourceLoc& loc, TOperator op, bool isImage)
3208 {
3209 switch (op) {
3210 case EOpInterlockedAdd: return isImage ? EOpImageAtomicAdd : EOpAtomicAdd;
3211 case EOpInterlockedAnd: return isImage ? EOpImageAtomicAnd : EOpAtomicAnd;
3212 case EOpInterlockedCompareExchange: return isImage ? EOpImageAtomicCompSwap : EOpAtomicCompSwap;
3213 case EOpInterlockedMax: return isImage ? EOpImageAtomicMax : EOpAtomicMax;
3214 case EOpInterlockedMin: return isImage ? EOpImageAtomicMin : EOpAtomicMin;
3215 case EOpInterlockedOr: return isImage ? EOpImageAtomicOr : EOpAtomicOr;
3216 case EOpInterlockedXor: return isImage ? EOpImageAtomicXor : EOpAtomicXor;
3217 case EOpInterlockedExchange: return isImage ? EOpImageAtomicExchange : EOpAtomicExchange;
3218 case EOpInterlockedCompareStore: // TODO: ...
3219 default:
3220 error(loc, "unknown atomic operation", "unknown op", "");
3221 return EOpNull;
3222 }
3223 }
3224
3225 //
3226 // Create a combined sampler/texture from separate sampler and texture.
3227 //
handleSamplerTextureCombine(const TSourceLoc & loc,TIntermTyped * argTex,TIntermTyped * argSampler)3228 TIntermAggregate* HlslParseContext::handleSamplerTextureCombine(const TSourceLoc& loc, TIntermTyped* argTex,
3229 TIntermTyped* argSampler)
3230 {
3231 TIntermAggregate* txcombine = new TIntermAggregate(EOpConstructTextureSampler);
3232
3233 txcombine->getSequence().push_back(argTex);
3234 txcombine->getSequence().push_back(argSampler);
3235
3236 TSampler samplerType = argTex->getType().getSampler();
3237 samplerType.combined = true;
3238
3239 // TODO:
3240 // This block exists until the spec no longer requires shadow modes on texture objects.
3241 // It can be deleted after that, along with the shadowTextureVariant member.
3242 {
3243 const bool shadowMode = argSampler->getType().getSampler().shadow;
3244
3245 TIntermSymbol* texSymbol = argTex->getAsSymbolNode();
3246
3247 if (texSymbol == nullptr)
3248 texSymbol = argTex->getAsBinaryNode()->getLeft()->getAsSymbolNode();
3249
3250 if (texSymbol == nullptr) {
3251 error(loc, "unable to find texture symbol", "", "");
3252 return nullptr;
3253 }
3254
3255 // This forces the texture's shadow state to be the sampler's
3256 // shadow state. This depends on downstream optimization to
3257 // DCE one variant in [shadow, nonshadow] if both are present,
3258 // or the SPIR-V module would be invalid.
3259 int newId = texSymbol->getId();
3260
3261 // Check to see if this texture has been given a shadow mode already.
3262 // If so, look up the one we already have.
3263 const auto textureShadowEntry = textureShadowVariant.find(texSymbol->getId());
3264
3265 if (textureShadowEntry != textureShadowVariant.end())
3266 newId = textureShadowEntry->second->get(shadowMode);
3267 else
3268 textureShadowVariant[texSymbol->getId()] = NewPoolObject(tShadowTextureSymbols(), 1);
3269
3270 // Sometimes we have to create another symbol (if this texture has been seen before,
3271 // and we haven't created the form for this shadow mode).
3272 if (newId == -1) {
3273 TType texType;
3274 texType.shallowCopy(argTex->getType());
3275 texType.getSampler().shadow = shadowMode; // set appropriate shadow mode.
3276 globalQualifierFix(loc, texType.getQualifier());
3277
3278 TVariable* newTexture = makeInternalVariable(texSymbol->getName(), texType);
3279
3280 trackLinkage(*newTexture);
3281
3282 newId = newTexture->getUniqueId();
3283 }
3284
3285 assert(newId != -1);
3286
3287 if (textureShadowVariant.find(newId) == textureShadowVariant.end())
3288 textureShadowVariant[newId] = textureShadowVariant[texSymbol->getId()];
3289
3290 textureShadowVariant[newId]->set(shadowMode, newId);
3291
3292 // Remember this shadow mode in the texture and the merged type.
3293 argTex->getWritableType().getSampler().shadow = shadowMode;
3294 samplerType.shadow = shadowMode;
3295
3296 texSymbol->switchId(newId);
3297 }
3298
3299 txcombine->setType(TType(samplerType, EvqTemporary));
3300 txcombine->setLoc(loc);
3301
3302 return txcombine;
3303 }
3304
3305 // Return true if this a buffer type that has an associated counter buffer.
hasStructBuffCounter(const TType & type) const3306 bool HlslParseContext::hasStructBuffCounter(const TType& type) const
3307 {
3308 switch (type.getQualifier().declaredBuiltIn) {
3309 case EbvAppendConsume: // fall through...
3310 case EbvRWStructuredBuffer: // ...
3311 return true;
3312 default:
3313 return false; // the other structuredbuffer types do not have a counter.
3314 }
3315 }
3316
counterBufferType(const TSourceLoc & loc,TType & type)3317 void HlslParseContext::counterBufferType(const TSourceLoc& loc, TType& type)
3318 {
3319 // Counter type
3320 TType* counterType = new TType(EbtUint, EvqBuffer);
3321 counterType->setFieldName(intermediate.implicitCounterName);
3322
3323 TTypeList* blockStruct = new TTypeList;
3324 TTypeLoc member = { counterType, loc };
3325 blockStruct->push_back(member);
3326
3327 TType blockType(blockStruct, "", counterType->getQualifier());
3328 blockType.getQualifier().storage = EvqBuffer;
3329
3330 type.shallowCopy(blockType);
3331 shareStructBufferType(type);
3332 }
3333
3334 // declare counter for a structured buffer type
declareStructBufferCounter(const TSourceLoc & loc,const TType & bufferType,const TString & name)3335 void HlslParseContext::declareStructBufferCounter(const TSourceLoc& loc, const TType& bufferType, const TString& name)
3336 {
3337 // Bail out if not a struct buffer
3338 if (! isStructBufferType(bufferType))
3339 return;
3340
3341 if (! hasStructBuffCounter(bufferType))
3342 return;
3343
3344 TType blockType;
3345 counterBufferType(loc, blockType);
3346
3347 TString* blockName = NewPoolTString(intermediate.addCounterBufferName(name).c_str());
3348
3349 // Counter buffer is not yet in use
3350 structBufferCounter[*blockName] = false;
3351
3352 shareStructBufferType(blockType);
3353 declareBlock(loc, blockType, blockName);
3354 }
3355
3356 // return the counter that goes with a given structuredbuffer
getStructBufferCounter(const TSourceLoc & loc,TIntermTyped * buffer)3357 TIntermTyped* HlslParseContext::getStructBufferCounter(const TSourceLoc& loc, TIntermTyped* buffer)
3358 {
3359 // Bail out if not a struct buffer
3360 if (buffer == nullptr || ! isStructBufferType(buffer->getType()))
3361 return nullptr;
3362
3363 const TString counterBlockName(intermediate.addCounterBufferName(buffer->getAsSymbolNode()->getName()));
3364
3365 // Mark the counter as being used
3366 structBufferCounter[counterBlockName] = true;
3367
3368 TIntermTyped* counterVar = handleVariable(loc, &counterBlockName); // find the block structure
3369 TIntermTyped* index = intermediate.addConstantUnion(0, loc); // index to counter inside block struct
3370
3371 TIntermTyped* counterMember = intermediate.addIndex(EOpIndexDirectStruct, counterVar, index, loc);
3372 counterMember->setType(TType(EbtUint));
3373 return counterMember;
3374 }
3375
3376 //
3377 // Decompose structure buffer methods into AST
3378 //
decomposeStructBufferMethods(const TSourceLoc & loc,TIntermTyped * & node,TIntermNode * arguments)3379 void HlslParseContext::decomposeStructBufferMethods(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
3380 {
3381 if (node == nullptr || node->getAsOperator() == nullptr || arguments == nullptr)
3382 return;
3383
3384 const TOperator op = node->getAsOperator()->getOp();
3385 TIntermAggregate* argAggregate = arguments->getAsAggregate();
3386
3387 // Buffer is the object upon which method is called, so always arg 0
3388 TIntermTyped* bufferObj = nullptr;
3389
3390 // The parameters can be an aggregate, or just a the object as a symbol if there are no fn params.
3391 if (argAggregate) {
3392 if (argAggregate->getSequence().empty())
3393 return;
3394 if (argAggregate->getSequence()[0])
3395 bufferObj = argAggregate->getSequence()[0]->getAsTyped();
3396 } else {
3397 bufferObj = arguments->getAsSymbolNode();
3398 }
3399
3400 if (bufferObj == nullptr || bufferObj->getAsSymbolNode() == nullptr)
3401 return;
3402
3403 // Some methods require a hidden internal counter, obtained via getStructBufferCounter().
3404 // This lambda adds something to it and returns the old value.
3405 const auto incDecCounter = [&](int incval) -> TIntermTyped* {
3406 TIntermTyped* incrementValue = intermediate.addConstantUnion(static_cast<unsigned int>(incval), loc, true);
3407 TIntermTyped* counter = getStructBufferCounter(loc, bufferObj); // obtain the counter member
3408
3409 if (counter == nullptr)
3410 return nullptr;
3411
3412 TIntermAggregate* counterIncrement = new TIntermAggregate(EOpAtomicAdd);
3413 counterIncrement->setType(TType(EbtUint, EvqTemporary));
3414 counterIncrement->setLoc(loc);
3415 counterIncrement->getSequence().push_back(counter);
3416 counterIncrement->getSequence().push_back(incrementValue);
3417
3418 return counterIncrement;
3419 };
3420
3421 // Index to obtain the runtime sized array out of the buffer.
3422 TIntermTyped* argArray = indexStructBufferContent(loc, bufferObj);
3423 if (argArray == nullptr)
3424 return; // It might not be a struct buffer method.
3425
3426 switch (op) {
3427 case EOpMethodLoad:
3428 {
3429 TIntermTyped* argIndex = makeIntegerIndex(argAggregate->getSequence()[1]->getAsTyped()); // index
3430
3431 const TType& bufferType = bufferObj->getType();
3432
3433 const TBuiltInVariable builtInType = bufferType.getQualifier().declaredBuiltIn;
3434
3435 // Byte address buffers index in bytes (only multiples of 4 permitted... not so much a byte address
3436 // buffer then, but that's what it calls itself.
3437 const bool isByteAddressBuffer = (builtInType == EbvByteAddressBuffer ||
3438 builtInType == EbvRWByteAddressBuffer);
3439
3440
3441 if (isByteAddressBuffer)
3442 argIndex = intermediate.addBinaryNode(EOpRightShift, argIndex,
3443 intermediate.addConstantUnion(2, loc, true),
3444 loc, TType(EbtInt));
3445
3446 // Index into the array to find the item being loaded.
3447 const TOperator idxOp = (argIndex->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect;
3448
3449 node = intermediate.addIndex(idxOp, argArray, argIndex, loc);
3450
3451 const TType derefType(argArray->getType(), 0);
3452 node->setType(derefType);
3453 }
3454
3455 break;
3456
3457 case EOpMethodLoad2:
3458 case EOpMethodLoad3:
3459 case EOpMethodLoad4:
3460 {
3461 TIntermTyped* argIndex = makeIntegerIndex(argAggregate->getSequence()[1]->getAsTyped()); // index
3462
3463 TOperator constructOp = EOpNull;
3464 int size = 0;
3465
3466 switch (op) {
3467 case EOpMethodLoad2: size = 2; constructOp = EOpConstructVec2; break;
3468 case EOpMethodLoad3: size = 3; constructOp = EOpConstructVec3; break;
3469 case EOpMethodLoad4: size = 4; constructOp = EOpConstructVec4; break;
3470 default: assert(0);
3471 }
3472
3473 TIntermTyped* body = nullptr;
3474
3475 // First, we'll store the address in a variable to avoid multiple shifts
3476 // (we must convert the byte address to an item address)
3477 TIntermTyped* byteAddrIdx = intermediate.addBinaryNode(EOpRightShift, argIndex,
3478 intermediate.addConstantUnion(2, loc, true),
3479 loc, TType(EbtInt));
3480
3481 TVariable* byteAddrSym = makeInternalVariable("byteAddrTemp", TType(EbtInt, EvqTemporary));
3482 TIntermTyped* byteAddrIdxVar = intermediate.addSymbol(*byteAddrSym, loc);
3483
3484 body = intermediate.growAggregate(body, intermediate.addAssign(EOpAssign, byteAddrIdxVar, byteAddrIdx, loc));
3485
3486 TIntermTyped* vec = nullptr;
3487
3488 // These are only valid on (rw)byteaddressbuffers, so we can always perform the >>2
3489 // address conversion.
3490 for (int idx=0; idx<size; ++idx) {
3491 TIntermTyped* offsetIdx = byteAddrIdxVar;
3492
3493 // add index offset
3494 if (idx != 0)
3495 offsetIdx = intermediate.addBinaryNode(EOpAdd, offsetIdx,
3496 intermediate.addConstantUnion(idx, loc, true),
3497 loc, TType(EbtInt));
3498
3499 const TOperator idxOp = (offsetIdx->getQualifier().storage == EvqConst) ? EOpIndexDirect
3500 : EOpIndexIndirect;
3501
3502 TIntermTyped* indexVal = intermediate.addIndex(idxOp, argArray, offsetIdx, loc);
3503
3504 TType derefType(argArray->getType(), 0);
3505 derefType.getQualifier().makeTemporary();
3506 indexVal->setType(derefType);
3507
3508 vec = intermediate.growAggregate(vec, indexVal);
3509 }
3510
3511 vec->setType(TType(argArray->getBasicType(), EvqTemporary, size));
3512 vec->getAsAggregate()->setOperator(constructOp);
3513
3514 body = intermediate.growAggregate(body, vec);
3515 body->setType(vec->getType());
3516 body->getAsAggregate()->setOperator(EOpSequence);
3517
3518 node = body;
3519 }
3520
3521 break;
3522
3523 case EOpMethodStore:
3524 case EOpMethodStore2:
3525 case EOpMethodStore3:
3526 case EOpMethodStore4:
3527 {
3528 TIntermTyped* argIndex = makeIntegerIndex(argAggregate->getSequence()[1]->getAsTyped()); // index
3529 TIntermTyped* argValue = argAggregate->getSequence()[2]->getAsTyped(); // value
3530
3531 // Index into the array to find the item being loaded.
3532 // Byte address buffers index in bytes (only multiples of 4 permitted... not so much a byte address
3533 // buffer then, but that's what it calls itself).
3534
3535 int size = 0;
3536
3537 switch (op) {
3538 case EOpMethodStore: size = 1; break;
3539 case EOpMethodStore2: size = 2; break;
3540 case EOpMethodStore3: size = 3; break;
3541 case EOpMethodStore4: size = 4; break;
3542 default: assert(0);
3543 }
3544
3545 TIntermAggregate* body = nullptr;
3546
3547 // First, we'll store the address in a variable to avoid multiple shifts
3548 // (we must convert the byte address to an item address)
3549 TIntermTyped* byteAddrIdx = intermediate.addBinaryNode(EOpRightShift, argIndex,
3550 intermediate.addConstantUnion(2, loc, true), loc, TType(EbtInt));
3551
3552 TVariable* byteAddrSym = makeInternalVariable("byteAddrTemp", TType(EbtInt, EvqTemporary));
3553 TIntermTyped* byteAddrIdxVar = intermediate.addSymbol(*byteAddrSym, loc);
3554
3555 body = intermediate.growAggregate(body, intermediate.addAssign(EOpAssign, byteAddrIdxVar, byteAddrIdx, loc));
3556
3557 for (int idx=0; idx<size; ++idx) {
3558 TIntermTyped* offsetIdx = byteAddrIdxVar;
3559 TIntermTyped* idxConst = intermediate.addConstantUnion(idx, loc, true);
3560
3561 // add index offset
3562 if (idx != 0)
3563 offsetIdx = intermediate.addBinaryNode(EOpAdd, offsetIdx, idxConst, loc, TType(EbtInt));
3564
3565 const TOperator idxOp = (offsetIdx->getQualifier().storage == EvqConst) ? EOpIndexDirect
3566 : EOpIndexIndirect;
3567
3568 TIntermTyped* lValue = intermediate.addIndex(idxOp, argArray, offsetIdx, loc);
3569 const TType derefType(argArray->getType(), 0);
3570 lValue->setType(derefType);
3571
3572 TIntermTyped* rValue;
3573 if (size == 1) {
3574 rValue = argValue;
3575 } else {
3576 rValue = intermediate.addIndex(EOpIndexDirect, argValue, idxConst, loc);
3577 const TType indexType(argValue->getType(), 0);
3578 rValue->setType(indexType);
3579 }
3580
3581 TIntermTyped* assign = intermediate.addAssign(EOpAssign, lValue, rValue, loc);
3582
3583 body = intermediate.growAggregate(body, assign);
3584 }
3585
3586 body->setOperator(EOpSequence);
3587 node = body;
3588 }
3589
3590 break;
3591
3592 case EOpMethodGetDimensions:
3593 {
3594 const int numArgs = (int)argAggregate->getSequence().size();
3595 TIntermTyped* argNumItems = argAggregate->getSequence()[1]->getAsTyped(); // out num items
3596 TIntermTyped* argStride = numArgs > 2 ? argAggregate->getSequence()[2]->getAsTyped() : nullptr; // out stride
3597
3598 TIntermAggregate* body = nullptr;
3599
3600 // Length output:
3601 if (argArray->getType().isSizedArray()) {
3602 const int length = argArray->getType().getOuterArraySize();
3603 TIntermTyped* assign = intermediate.addAssign(EOpAssign, argNumItems,
3604 intermediate.addConstantUnion(length, loc, true), loc);
3605 body = intermediate.growAggregate(body, assign, loc);
3606 } else {
3607 TIntermTyped* lengthCall = intermediate.addBuiltInFunctionCall(loc, EOpArrayLength, true, argArray,
3608 argNumItems->getType());
3609 TIntermTyped* assign = intermediate.addAssign(EOpAssign, argNumItems, lengthCall, loc);
3610 body = intermediate.growAggregate(body, assign, loc);
3611 }
3612
3613 // Stride output:
3614 if (argStride != nullptr) {
3615 int size;
3616 int stride;
3617 intermediate.getMemberAlignment(argArray->getType(), size, stride, argArray->getType().getQualifier().layoutPacking,
3618 argArray->getType().getQualifier().layoutMatrix == ElmRowMajor);
3619
3620 TIntermTyped* assign = intermediate.addAssign(EOpAssign, argStride,
3621 intermediate.addConstantUnion(stride, loc, true), loc);
3622
3623 body = intermediate.growAggregate(body, assign);
3624 }
3625
3626 body->setOperator(EOpSequence);
3627 node = body;
3628 }
3629
3630 break;
3631
3632 case EOpInterlockedAdd:
3633 case EOpInterlockedAnd:
3634 case EOpInterlockedExchange:
3635 case EOpInterlockedMax:
3636 case EOpInterlockedMin:
3637 case EOpInterlockedOr:
3638 case EOpInterlockedXor:
3639 case EOpInterlockedCompareExchange:
3640 case EOpInterlockedCompareStore:
3641 {
3642 // We'll replace the first argument with the block dereference, and let
3643 // downstream decomposition handle the rest.
3644
3645 TIntermSequence& sequence = argAggregate->getSequence();
3646
3647 TIntermTyped* argIndex = makeIntegerIndex(sequence[1]->getAsTyped()); // index
3648 argIndex = intermediate.addBinaryNode(EOpRightShift, argIndex, intermediate.addConstantUnion(2, loc, true),
3649 loc, TType(EbtInt));
3650
3651 const TOperator idxOp = (argIndex->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect;
3652 TIntermTyped* element = intermediate.addIndex(idxOp, argArray, argIndex, loc);
3653
3654 const TType derefType(argArray->getType(), 0);
3655 element->setType(derefType);
3656
3657 // Replace the numeric byte offset parameter with array reference.
3658 sequence[1] = element;
3659 sequence.erase(sequence.begin(), sequence.begin()+1);
3660 }
3661 break;
3662
3663 case EOpMethodIncrementCounter:
3664 {
3665 node = incDecCounter(1);
3666 break;
3667 }
3668
3669 case EOpMethodDecrementCounter:
3670 {
3671 TIntermTyped* preIncValue = incDecCounter(-1); // result is original value
3672 node = intermediate.addBinaryNode(EOpAdd, preIncValue, intermediate.addConstantUnion(-1, loc, true), loc,
3673 preIncValue->getType());
3674 break;
3675 }
3676
3677 case EOpMethodAppend:
3678 {
3679 TIntermTyped* oldCounter = incDecCounter(1);
3680
3681 TIntermTyped* lValue = intermediate.addIndex(EOpIndexIndirect, argArray, oldCounter, loc);
3682 TIntermTyped* rValue = argAggregate->getSequence()[1]->getAsTyped();
3683
3684 const TType derefType(argArray->getType(), 0);
3685 lValue->setType(derefType);
3686
3687 node = intermediate.addAssign(EOpAssign, lValue, rValue, loc);
3688
3689 break;
3690 }
3691
3692 case EOpMethodConsume:
3693 {
3694 TIntermTyped* oldCounter = incDecCounter(-1);
3695
3696 TIntermTyped* newCounter = intermediate.addBinaryNode(EOpAdd, oldCounter,
3697 intermediate.addConstantUnion(-1, loc, true), loc,
3698 oldCounter->getType());
3699
3700 node = intermediate.addIndex(EOpIndexIndirect, argArray, newCounter, loc);
3701
3702 const TType derefType(argArray->getType(), 0);
3703 node->setType(derefType);
3704
3705 break;
3706 }
3707
3708 default:
3709 break; // most pass through unchanged
3710 }
3711 }
3712
3713 // Create array of standard sample positions for given sample count.
3714 // TODO: remove when a real method to query sample pos exists in SPIR-V.
getSamplePosArray(int count)3715 TIntermConstantUnion* HlslParseContext::getSamplePosArray(int count)
3716 {
3717 struct tSamplePos { float x, y; };
3718
3719 static const tSamplePos pos1[] = {
3720 { 0.0/16.0, 0.0/16.0 },
3721 };
3722
3723 // standard sample positions for 2, 4, 8, and 16 samples.
3724 static const tSamplePos pos2[] = {
3725 { 4.0/16.0, 4.0/16.0 }, {-4.0/16.0, -4.0/16.0 },
3726 };
3727
3728 static const tSamplePos pos4[] = {
3729 {-2.0/16.0, -6.0/16.0 }, { 6.0/16.0, -2.0/16.0 }, {-6.0/16.0, 2.0/16.0 }, { 2.0/16.0, 6.0/16.0 },
3730 };
3731
3732 static const tSamplePos pos8[] = {
3733 { 1.0/16.0, -3.0/16.0 }, {-1.0/16.0, 3.0/16.0 }, { 5.0/16.0, 1.0/16.0 }, {-3.0/16.0, -5.0/16.0 },
3734 {-5.0/16.0, 5.0/16.0 }, {-7.0/16.0, -1.0/16.0 }, { 3.0/16.0, 7.0/16.0 }, { 7.0/16.0, -7.0/16.0 },
3735 };
3736
3737 static const tSamplePos pos16[] = {
3738 { 1.0/16.0, 1.0/16.0 }, {-1.0/16.0, -3.0/16.0 }, {-3.0/16.0, 2.0/16.0 }, { 4.0/16.0, -1.0/16.0 },
3739 {-5.0/16.0, -2.0/16.0 }, { 2.0/16.0, 5.0/16.0 }, { 5.0/16.0, 3.0/16.0 }, { 3.0/16.0, -5.0/16.0 },
3740 {-2.0/16.0, 6.0/16.0 }, { 0.0/16.0, -7.0/16.0 }, {-4.0/16.0, -6.0/16.0 }, {-6.0/16.0, 4.0/16.0 },
3741 {-8.0/16.0, 0.0/16.0 }, { 7.0/16.0, -4.0/16.0 }, { 6.0/16.0, 7.0/16.0 }, {-7.0/16.0, -8.0/16.0 },
3742 };
3743
3744 const tSamplePos* sampleLoc = nullptr;
3745 int numSamples = count;
3746
3747 switch (count) {
3748 case 2: sampleLoc = pos2; break;
3749 case 4: sampleLoc = pos4; break;
3750 case 8: sampleLoc = pos8; break;
3751 case 16: sampleLoc = pos16; break;
3752 default:
3753 sampleLoc = pos1;
3754 numSamples = 1;
3755 }
3756
3757 TConstUnionArray* values = new TConstUnionArray(numSamples*2);
3758
3759 for (int pos=0; pos<count; ++pos) {
3760 TConstUnion x, y;
3761 x.setDConst(sampleLoc[pos].x);
3762 y.setDConst(sampleLoc[pos].y);
3763
3764 (*values)[pos*2+0] = x;
3765 (*values)[pos*2+1] = y;
3766 }
3767
3768 TType retType(EbtFloat, EvqConst, 2);
3769
3770 if (numSamples != 1) {
3771 TArraySizes* arraySizes = new TArraySizes;
3772 arraySizes->addInnerSize(numSamples);
3773 retType.transferArraySizes(arraySizes);
3774 }
3775
3776 return new TIntermConstantUnion(*values, retType);
3777 }
3778
3779 //
3780 // Decompose DX9 and DX10 sample intrinsics & object methods into AST
3781 //
decomposeSampleMethods(const TSourceLoc & loc,TIntermTyped * & node,TIntermNode * arguments)3782 void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
3783 {
3784 if (node == nullptr || !node->getAsOperator())
3785 return;
3786
3787 // Sampler return must always be a vec4, but we can construct a shorter vector or a structure from it.
3788 const auto convertReturn = [&loc, &node, this](TIntermTyped* result, const TSampler& sampler) -> TIntermTyped* {
3789 result->setType(TType(node->getType().getBasicType(), EvqTemporary, node->getVectorSize()));
3790
3791 TIntermTyped* convertedResult = nullptr;
3792
3793 TType retType;
3794 getTextureReturnType(sampler, retType);
3795
3796 if (retType.isStruct()) {
3797 // For type convenience, conversionAggregate points to the convertedResult (we know it's an aggregate here)
3798 TIntermAggregate* conversionAggregate = new TIntermAggregate;
3799 convertedResult = conversionAggregate;
3800
3801 // Convert vector output to return structure. We will need a temp symbol to copy the results to.
3802 TVariable* structVar = makeInternalVariable("@sampleStructTemp", retType);
3803
3804 // We also need a temp symbol to hold the result of the texture. We don't want to re-fetch the
3805 // sample each time we'll index into the result, so we'll copy to this, and index into the copy.
3806 TVariable* sampleShadow = makeInternalVariable("@sampleResultShadow", result->getType());
3807
3808 // Initial copy from texture to our sample result shadow.
3809 TIntermTyped* shadowCopy = intermediate.addAssign(EOpAssign, intermediate.addSymbol(*sampleShadow, loc),
3810 result, loc);
3811
3812 conversionAggregate->getSequence().push_back(shadowCopy);
3813
3814 unsigned vec4Pos = 0;
3815
3816 for (unsigned m = 0; m < unsigned(retType.getStruct()->size()); ++m) {
3817 const TType memberType(retType, m); // dereferenced type of the member we're about to assign.
3818
3819 // Check for bad struct members. This should have been caught upstream. Complain, because
3820 // wwe don't know what to do with it. This algorithm could be generalized to handle
3821 // other things, e.g, sub-structures, but HLSL doesn't allow them.
3822 if (!memberType.isVector() && !memberType.isScalar()) {
3823 error(loc, "expected: scalar or vector type in texture structure", "", "");
3824 return nullptr;
3825 }
3826
3827 // Index into the struct variable to find the member to assign.
3828 TIntermTyped* structMember = intermediate.addIndex(EOpIndexDirectStruct,
3829 intermediate.addSymbol(*structVar, loc),
3830 intermediate.addConstantUnion(m, loc), loc);
3831
3832 structMember->setType(memberType);
3833
3834 // Assign each component of (possible) vector in struct member.
3835 for (int component = 0; component < memberType.getVectorSize(); ++component) {
3836 TIntermTyped* vec4Member = intermediate.addIndex(EOpIndexDirect,
3837 intermediate.addSymbol(*sampleShadow, loc),
3838 intermediate.addConstantUnion(vec4Pos++, loc), loc);
3839 vec4Member->setType(TType(memberType.getBasicType(), EvqTemporary, 1));
3840
3841 TIntermTyped* memberAssign = nullptr;
3842
3843 if (memberType.isVector()) {
3844 // Vector member: we need to create an access chain to the vector component.
3845
3846 TIntermTyped* structVecComponent = intermediate.addIndex(EOpIndexDirect, structMember,
3847 intermediate.addConstantUnion(component, loc), loc);
3848
3849 memberAssign = intermediate.addAssign(EOpAssign, structVecComponent, vec4Member, loc);
3850 } else {
3851 // Scalar member: we can assign to it directly.
3852 memberAssign = intermediate.addAssign(EOpAssign, structMember, vec4Member, loc);
3853 }
3854
3855
3856 conversionAggregate->getSequence().push_back(memberAssign);
3857 }
3858 }
3859
3860 // Add completed variable so the expression results in the whole struct value we just built.
3861 conversionAggregate->getSequence().push_back(intermediate.addSymbol(*structVar, loc));
3862
3863 // Make it a sequence.
3864 intermediate.setAggregateOperator(conversionAggregate, EOpSequence, retType, loc);
3865 } else {
3866 // vector clamp the output if template vector type is smaller than sample result.
3867 if (retType.getVectorSize() < node->getVectorSize()) {
3868 // Too many components. Construct shorter vector from it.
3869 const TOperator op = intermediate.mapTypeToConstructorOp(retType);
3870
3871 convertedResult = constructBuiltIn(retType, op, result, loc, false);
3872 } else {
3873 // Enough components. Use directly.
3874 convertedResult = result;
3875 }
3876 }
3877
3878 convertedResult->setLoc(loc);
3879 return convertedResult;
3880 };
3881
3882 const TOperator op = node->getAsOperator()->getOp();
3883 const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
3884
3885 // Bail out if not a sampler method.
3886 // Note though this is odd to do before checking the op, because the op
3887 // could be something that takes the arguments, and the function in question
3888 // takes the result of the op. So, this is not the final word.
3889 if (arguments != nullptr) {
3890 if (argAggregate == nullptr) {
3891 if (arguments->getAsTyped()->getBasicType() != EbtSampler)
3892 return;
3893 } else {
3894 if (argAggregate->getSequence().size() == 0 ||
3895 argAggregate->getSequence()[0] == nullptr ||
3896 argAggregate->getSequence()[0]->getAsTyped()->getBasicType() != EbtSampler)
3897 return;
3898 }
3899 }
3900
3901 switch (op) {
3902 // **** DX9 intrinsics: ****
3903 case EOpTexture:
3904 {
3905 // Texture with ddx & ddy is really gradient form in HLSL
3906 if (argAggregate->getSequence().size() == 4)
3907 node->getAsAggregate()->setOperator(EOpTextureGrad);
3908
3909 break;
3910 }
3911 case EOpTextureLod: //is almost EOpTextureBias (only args & operations are different)
3912 {
3913 TIntermTyped *argSamp = argAggregate->getSequence()[0]->getAsTyped(); // sampler
3914 TIntermTyped *argCoord = argAggregate->getSequence()[1]->getAsTyped(); // coord
3915
3916 assert(argCoord->getVectorSize() == 4);
3917 TIntermTyped *w = intermediate.addConstantUnion(3, loc, true);
3918 TIntermTyped *argLod = intermediate.addIndex(EOpIndexDirect, argCoord, w, loc);
3919
3920 TOperator constructOp = EOpNull;
3921 const TSampler &sampler = argSamp->getType().getSampler();
3922 int coordSize = 0;
3923
3924 switch (sampler.dim)
3925 {
3926 case Esd1D: constructOp = EOpConstructFloat; coordSize = 1; break; // 1D
3927 case Esd2D: constructOp = EOpConstructVec2; coordSize = 2; break; // 2D
3928 case Esd3D: constructOp = EOpConstructVec3; coordSize = 3; break; // 3D
3929 case EsdCube: constructOp = EOpConstructVec3; coordSize = 3; break; // also 3D
3930 default:
3931 error(loc, "unhandled DX9 texture LoD dimension", "", "");
3932 break;
3933 }
3934
3935 TIntermAggregate *constructCoord = new TIntermAggregate(constructOp);
3936 constructCoord->getSequence().push_back(argCoord);
3937 constructCoord->setLoc(loc);
3938 constructCoord->setType(TType(argCoord->getBasicType(), EvqTemporary, coordSize));
3939
3940 TIntermAggregate *tex = new TIntermAggregate(EOpTextureLod);
3941 tex->getSequence().push_back(argSamp); // sampler
3942 tex->getSequence().push_back(constructCoord); // coordinate
3943 tex->getSequence().push_back(argLod); // lod
3944
3945 node = convertReturn(tex, sampler);
3946
3947 break;
3948 }
3949
3950 case EOpTextureBias:
3951 {
3952 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // sampler
3953 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // coord
3954
3955 // HLSL puts bias in W component of coordinate. We extract it and add it to
3956 // the argument list, instead
3957 TIntermTyped* w = intermediate.addConstantUnion(3, loc, true);
3958 TIntermTyped* bias = intermediate.addIndex(EOpIndexDirect, arg1, w, loc);
3959
3960 TOperator constructOp = EOpNull;
3961 const TSampler& sampler = arg0->getType().getSampler();
3962
3963 switch (sampler.dim) {
3964 case Esd1D: constructOp = EOpConstructFloat; break; // 1D
3965 case Esd2D: constructOp = EOpConstructVec2; break; // 2D
3966 case Esd3D: constructOp = EOpConstructVec3; break; // 3D
3967 case EsdCube: constructOp = EOpConstructVec3; break; // also 3D
3968 default:
3969 error(loc, "unhandled DX9 texture bias dimension", "", "");
3970 break;
3971 }
3972
3973 TIntermAggregate* constructCoord = new TIntermAggregate(constructOp);
3974 constructCoord->getSequence().push_back(arg1);
3975 constructCoord->setLoc(loc);
3976
3977 // The input vector should never be less than 2, since there's always a bias.
3978 // The max is for safety, and should be a no-op.
3979 constructCoord->setType(TType(arg1->getBasicType(), EvqTemporary, std::max(arg1->getVectorSize() - 1, 0)));
3980
3981 TIntermAggregate* tex = new TIntermAggregate(EOpTexture);
3982 tex->getSequence().push_back(arg0); // sampler
3983 tex->getSequence().push_back(constructCoord); // coordinate
3984 tex->getSequence().push_back(bias); // bias
3985
3986 node = convertReturn(tex, sampler);
3987
3988 break;
3989 }
3990
3991 // **** DX10 methods: ****
3992 case EOpMethodSample: // fall through
3993 case EOpMethodSampleBias: // ...
3994 {
3995 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
3996 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
3997 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
3998 TIntermTyped* argBias = nullptr;
3999 TIntermTyped* argOffset = nullptr;
4000 const TSampler& sampler = argTex->getType().getSampler();
4001
4002 int nextArg = 3;
4003
4004 if (op == EOpMethodSampleBias) // SampleBias has a bias arg
4005 argBias = argAggregate->getSequence()[nextArg++]->getAsTyped();
4006
4007 TOperator textureOp = EOpTexture;
4008
4009 if ((int)argAggregate->getSequence().size() == (nextArg+1)) { // last parameter is offset form
4010 textureOp = EOpTextureOffset;
4011 argOffset = argAggregate->getSequence()[nextArg++]->getAsTyped();
4012 }
4013
4014 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4015
4016 TIntermAggregate* txsample = new TIntermAggregate(textureOp);
4017 txsample->getSequence().push_back(txcombine);
4018 txsample->getSequence().push_back(argCoord);
4019
4020 if (argBias != nullptr)
4021 txsample->getSequence().push_back(argBias);
4022
4023 if (argOffset != nullptr)
4024 txsample->getSequence().push_back(argOffset);
4025
4026 node = convertReturn(txsample, sampler);
4027
4028 break;
4029 }
4030
4031 case EOpMethodSampleGrad: // ...
4032 {
4033 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4034 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
4035 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
4036 TIntermTyped* argDDX = argAggregate->getSequence()[3]->getAsTyped();
4037 TIntermTyped* argDDY = argAggregate->getSequence()[4]->getAsTyped();
4038 TIntermTyped* argOffset = nullptr;
4039 const TSampler& sampler = argTex->getType().getSampler();
4040
4041 TOperator textureOp = EOpTextureGrad;
4042
4043 if (argAggregate->getSequence().size() == 6) { // last parameter is offset form
4044 textureOp = EOpTextureGradOffset;
4045 argOffset = argAggregate->getSequence()[5]->getAsTyped();
4046 }
4047
4048 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4049
4050 TIntermAggregate* txsample = new TIntermAggregate(textureOp);
4051 txsample->getSequence().push_back(txcombine);
4052 txsample->getSequence().push_back(argCoord);
4053 txsample->getSequence().push_back(argDDX);
4054 txsample->getSequence().push_back(argDDY);
4055
4056 if (argOffset != nullptr)
4057 txsample->getSequence().push_back(argOffset);
4058
4059 node = convertReturn(txsample, sampler);
4060
4061 break;
4062 }
4063
4064 case EOpMethodGetDimensions:
4065 {
4066 // AST returns a vector of results, which we break apart component-wise into
4067 // separate values to assign to the HLSL method's outputs, ala:
4068 // tx . GetDimensions(width, height);
4069 // float2 sizeQueryTemp = EOpTextureQuerySize
4070 // width = sizeQueryTemp.X;
4071 // height = sizeQueryTemp.Y;
4072
4073 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4074 const TType& texType = argTex->getType();
4075
4076 assert(texType.getBasicType() == EbtSampler);
4077
4078 const TSampler& sampler = texType.getSampler();
4079 const TSamplerDim dim = sampler.dim;
4080 const bool isImage = sampler.isImage();
4081 const bool isMs = sampler.isMultiSample();
4082 const int numArgs = (int)argAggregate->getSequence().size();
4083
4084 int numDims = 0;
4085
4086 switch (dim) {
4087 case Esd1D: numDims = 1; break; // W
4088 case Esd2D: numDims = 2; break; // W, H
4089 case Esd3D: numDims = 3; break; // W, H, D
4090 case EsdCube: numDims = 2; break; // W, H (cube)
4091 case EsdBuffer: numDims = 1; break; // W (buffers)
4092 case EsdRect: numDims = 2; break; // W, H (rect)
4093 default:
4094 error(loc, "unhandled DX10 MethodGet dimension", "", "");
4095 break;
4096 }
4097
4098 // Arrayed adds another dimension for the number of array elements
4099 if (sampler.isArrayed())
4100 ++numDims;
4101
4102 // Establish whether the method itself is querying mip levels. This can be false even
4103 // if the underlying query requires a MIP level, due to the available HLSL method overloads.
4104 const bool mipQuery = (numArgs > (numDims + 1 + (isMs ? 1 : 0)));
4105
4106 // Establish whether we must use the LOD form of query (even if the method did not supply a mip level to query).
4107 // True if:
4108 // 1. 1D/2D/3D/Cube AND multisample==0 AND NOT image (those can be sent to the non-LOD query)
4109 // or,
4110 // 2. There is a LOD (because the non-LOD query cannot be used in that case, per spec)
4111 const bool mipRequired =
4112 ((dim == Esd1D || dim == Esd2D || dim == Esd3D || dim == EsdCube) && !isMs && !isImage) || // 1...
4113 mipQuery; // 2...
4114
4115 // AST assumes integer return. Will be converted to float if required.
4116 TIntermAggregate* sizeQuery = new TIntermAggregate(isImage ? EOpImageQuerySize : EOpTextureQuerySize);
4117 sizeQuery->getSequence().push_back(argTex);
4118
4119 // If we're building an LOD query, add the LOD.
4120 if (mipRequired) {
4121 // If the base HLSL query had no MIP level given, use level 0.
4122 TIntermTyped* queryLod = mipQuery ? argAggregate->getSequence()[1]->getAsTyped() :
4123 intermediate.addConstantUnion(0, loc, true);
4124 sizeQuery->getSequence().push_back(queryLod);
4125 }
4126
4127 sizeQuery->setType(TType(EbtUint, EvqTemporary, numDims));
4128 sizeQuery->setLoc(loc);
4129
4130 // Return value from size query
4131 TVariable* tempArg = makeInternalVariable("sizeQueryTemp", sizeQuery->getType());
4132 tempArg->getWritableType().getQualifier().makeTemporary();
4133 TIntermTyped* sizeQueryAssign = intermediate.addAssign(EOpAssign,
4134 intermediate.addSymbol(*tempArg, loc),
4135 sizeQuery, loc);
4136
4137 // Compound statement for assigning outputs
4138 TIntermAggregate* compoundStatement = intermediate.makeAggregate(sizeQueryAssign, loc);
4139 // Index of first output parameter
4140 const int outParamBase = mipQuery ? 2 : 1;
4141
4142 for (int compNum = 0; compNum < numDims; ++compNum) {
4143 TIntermTyped* indexedOut = nullptr;
4144 TIntermSymbol* sizeQueryReturn = intermediate.addSymbol(*tempArg, loc);
4145
4146 if (numDims > 1) {
4147 TIntermTyped* component = intermediate.addConstantUnion(compNum, loc, true);
4148 indexedOut = intermediate.addIndex(EOpIndexDirect, sizeQueryReturn, component, loc);
4149 indexedOut->setType(TType(EbtUint, EvqTemporary, 1));
4150 indexedOut->setLoc(loc);
4151 } else {
4152 indexedOut = sizeQueryReturn;
4153 }
4154
4155 TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + compNum]->getAsTyped();
4156 TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, indexedOut, loc);
4157
4158 compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
4159 }
4160
4161 // handle mip level parameter
4162 if (mipQuery) {
4163 TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + numDims]->getAsTyped();
4164
4165 TIntermAggregate* levelsQuery = new TIntermAggregate(EOpTextureQueryLevels);
4166 levelsQuery->getSequence().push_back(argTex);
4167 levelsQuery->setType(TType(EbtUint, EvqTemporary, 1));
4168 levelsQuery->setLoc(loc);
4169
4170 TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, levelsQuery, loc);
4171 compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
4172 }
4173
4174 // 2DMS formats query # samples, which needs a different query op
4175 if (sampler.isMultiSample()) {
4176 TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + numDims]->getAsTyped();
4177
4178 TIntermAggregate* samplesQuery = new TIntermAggregate(EOpImageQuerySamples);
4179 samplesQuery->getSequence().push_back(argTex);
4180 samplesQuery->setType(TType(EbtUint, EvqTemporary, 1));
4181 samplesQuery->setLoc(loc);
4182
4183 TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, samplesQuery, loc);
4184 compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
4185 }
4186
4187 compoundStatement->setOperator(EOpSequence);
4188 compoundStatement->setLoc(loc);
4189 compoundStatement->setType(TType(EbtVoid));
4190
4191 node = compoundStatement;
4192
4193 break;
4194 }
4195
4196 case EOpMethodSampleCmp: // fall through...
4197 case EOpMethodSampleCmpLevelZero:
4198 {
4199 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4200 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
4201 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
4202 TIntermTyped* argCmpVal = argAggregate->getSequence()[3]->getAsTyped();
4203 TIntermTyped* argOffset = nullptr;
4204
4205 // Sampler argument should be a sampler.
4206 if (argSamp->getType().getBasicType() != EbtSampler) {
4207 error(loc, "expected: sampler type", "", "");
4208 return;
4209 }
4210
4211 // Sampler should be a SamplerComparisonState
4212 if (! argSamp->getType().getSampler().isShadow()) {
4213 error(loc, "expected: SamplerComparisonState", "", "");
4214 return;
4215 }
4216
4217 // optional offset value
4218 if (argAggregate->getSequence().size() > 4)
4219 argOffset = argAggregate->getSequence()[4]->getAsTyped();
4220
4221 const int coordDimWithCmpVal = argCoord->getType().getVectorSize() + 1; // +1 for cmp
4222
4223 // AST wants comparison value as one of the texture coordinates
4224 TOperator constructOp = EOpNull;
4225 switch (coordDimWithCmpVal) {
4226 // 1D can't happen: there's always at least 1 coordinate dimension + 1 cmp val
4227 case 2: constructOp = EOpConstructVec2; break;
4228 case 3: constructOp = EOpConstructVec3; break;
4229 case 4: constructOp = EOpConstructVec4; break;
4230 case 5: constructOp = EOpConstructVec4; break; // cubeArrayShadow, cmp value is separate arg.
4231 default:
4232 error(loc, "unhandled DX10 MethodSample dimension", "", "");
4233 break;
4234 }
4235
4236 TIntermAggregate* coordWithCmp = new TIntermAggregate(constructOp);
4237 coordWithCmp->getSequence().push_back(argCoord);
4238 if (coordDimWithCmpVal != 5) // cube array shadow is special.
4239 coordWithCmp->getSequence().push_back(argCmpVal);
4240 coordWithCmp->setLoc(loc);
4241 coordWithCmp->setType(TType(argCoord->getBasicType(), EvqTemporary, std::min(coordDimWithCmpVal, 4)));
4242
4243 TOperator textureOp = (op == EOpMethodSampleCmpLevelZero ? EOpTextureLod : EOpTexture);
4244 if (argOffset != nullptr)
4245 textureOp = (op == EOpMethodSampleCmpLevelZero ? EOpTextureLodOffset : EOpTextureOffset);
4246
4247 // Create combined sampler & texture op
4248 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4249 TIntermAggregate* txsample = new TIntermAggregate(textureOp);
4250 txsample->getSequence().push_back(txcombine);
4251 txsample->getSequence().push_back(coordWithCmp);
4252
4253 if (coordDimWithCmpVal == 5) // cube array shadow is special: cmp val follows coord.
4254 txsample->getSequence().push_back(argCmpVal);
4255
4256 // the LevelZero form uses 0 as an explicit LOD
4257 if (op == EOpMethodSampleCmpLevelZero)
4258 txsample->getSequence().push_back(intermediate.addConstantUnion(0.0, EbtFloat, loc, true));
4259
4260 // Add offset if present
4261 if (argOffset != nullptr)
4262 txsample->getSequence().push_back(argOffset);
4263
4264 txsample->setType(node->getType());
4265 txsample->setLoc(loc);
4266 node = txsample;
4267
4268 break;
4269 }
4270
4271 case EOpMethodLoad:
4272 {
4273 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4274 TIntermTyped* argCoord = argAggregate->getSequence()[1]->getAsTyped();
4275 TIntermTyped* argOffset = nullptr;
4276 TIntermTyped* lodComponent = nullptr;
4277 TIntermTyped* coordSwizzle = nullptr;
4278
4279 const TSampler& sampler = argTex->getType().getSampler();
4280 const bool isMS = sampler.isMultiSample();
4281 const bool isBuffer = sampler.dim == EsdBuffer;
4282 const bool isImage = sampler.isImage();
4283 const TBasicType coordBaseType = argCoord->getType().getBasicType();
4284
4285 // Last component of coordinate is the mip level, for non-MS. we separate them here:
4286 if (isMS || isBuffer || isImage) {
4287 // MS, Buffer, and Image have no LOD
4288 coordSwizzle = argCoord;
4289 } else {
4290 // Extract coordinate
4291 int swizzleSize = argCoord->getType().getVectorSize() - (isMS ? 0 : 1);
4292 TSwizzleSelectors<TVectorSelector> coordFields;
4293 for (int i = 0; i < swizzleSize; ++i)
4294 coordFields.push_back(i);
4295 TIntermTyped* coordIdx = intermediate.addSwizzle(coordFields, loc);
4296 coordSwizzle = intermediate.addIndex(EOpVectorSwizzle, argCoord, coordIdx, loc);
4297 coordSwizzle->setType(TType(coordBaseType, EvqTemporary, coordFields.size()));
4298
4299 // Extract LOD
4300 TIntermTyped* lodIdx = intermediate.addConstantUnion(coordFields.size(), loc, true);
4301 lodComponent = intermediate.addIndex(EOpIndexDirect, argCoord, lodIdx, loc);
4302 lodComponent->setType(TType(coordBaseType, EvqTemporary, 1));
4303 }
4304
4305 const int numArgs = (int)argAggregate->getSequence().size();
4306 const bool hasOffset = ((!isMS && numArgs == 3) || (isMS && numArgs == 4));
4307
4308 // Create texel fetch
4309 const TOperator fetchOp = (isImage ? EOpImageLoad :
4310 hasOffset ? EOpTextureFetchOffset :
4311 EOpTextureFetch);
4312 TIntermAggregate* txfetch = new TIntermAggregate(fetchOp);
4313
4314 // Build up the fetch
4315 txfetch->getSequence().push_back(argTex);
4316 txfetch->getSequence().push_back(coordSwizzle);
4317
4318 if (isMS) {
4319 // add 2DMS sample index
4320 TIntermTyped* argSampleIdx = argAggregate->getSequence()[2]->getAsTyped();
4321 txfetch->getSequence().push_back(argSampleIdx);
4322 } else if (isBuffer) {
4323 // Nothing else to do for buffers.
4324 } else if (isImage) {
4325 // Nothing else to do for images.
4326 } else {
4327 // 2DMS and buffer have no LOD, but everything else does.
4328 txfetch->getSequence().push_back(lodComponent);
4329 }
4330
4331 // Obtain offset arg, if there is one.
4332 if (hasOffset) {
4333 const int offsetPos = (isMS ? 3 : 2);
4334 argOffset = argAggregate->getSequence()[offsetPos]->getAsTyped();
4335 txfetch->getSequence().push_back(argOffset);
4336 }
4337
4338 node = convertReturn(txfetch, sampler);
4339
4340 break;
4341 }
4342
4343 case EOpMethodSampleLevel:
4344 {
4345 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4346 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
4347 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
4348 TIntermTyped* argLod = argAggregate->getSequence()[3]->getAsTyped();
4349 TIntermTyped* argOffset = nullptr;
4350 const TSampler& sampler = argTex->getType().getSampler();
4351
4352 const int numArgs = (int)argAggregate->getSequence().size();
4353
4354 if (numArgs == 5) // offset, if present
4355 argOffset = argAggregate->getSequence()[4]->getAsTyped();
4356
4357 const TOperator textureOp = (argOffset == nullptr ? EOpTextureLod : EOpTextureLodOffset);
4358 TIntermAggregate* txsample = new TIntermAggregate(textureOp);
4359
4360 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4361
4362 txsample->getSequence().push_back(txcombine);
4363 txsample->getSequence().push_back(argCoord);
4364 txsample->getSequence().push_back(argLod);
4365
4366 if (argOffset != nullptr)
4367 txsample->getSequence().push_back(argOffset);
4368
4369 node = convertReturn(txsample, sampler);
4370
4371 break;
4372 }
4373
4374 case EOpMethodGather:
4375 {
4376 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4377 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
4378 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
4379 TIntermTyped* argOffset = nullptr;
4380
4381 // Offset is optional
4382 if (argAggregate->getSequence().size() > 3)
4383 argOffset = argAggregate->getSequence()[3]->getAsTyped();
4384
4385 const TOperator textureOp = (argOffset == nullptr ? EOpTextureGather : EOpTextureGatherOffset);
4386 TIntermAggregate* txgather = new TIntermAggregate(textureOp);
4387
4388 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4389
4390 txgather->getSequence().push_back(txcombine);
4391 txgather->getSequence().push_back(argCoord);
4392 // Offset if not given is implicitly channel 0 (red)
4393
4394 if (argOffset != nullptr)
4395 txgather->getSequence().push_back(argOffset);
4396
4397 txgather->setType(node->getType());
4398 txgather->setLoc(loc);
4399 node = txgather;
4400
4401 break;
4402 }
4403
4404 case EOpMethodGatherRed: // fall through...
4405 case EOpMethodGatherGreen: // ...
4406 case EOpMethodGatherBlue: // ...
4407 case EOpMethodGatherAlpha: // ...
4408 case EOpMethodGatherCmpRed: // ...
4409 case EOpMethodGatherCmpGreen: // ...
4410 case EOpMethodGatherCmpBlue: // ...
4411 case EOpMethodGatherCmpAlpha: // ...
4412 {
4413 int channel = 0; // the channel we are gathering
4414 int cmpValues = 0; // 1 if there is a compare value (handier than a bool below)
4415
4416 switch (op) {
4417 case EOpMethodGatherCmpRed: cmpValues = 1; // fall through
4418 case EOpMethodGatherRed: channel = 0; break;
4419 case EOpMethodGatherCmpGreen: cmpValues = 1; // fall through
4420 case EOpMethodGatherGreen: channel = 1; break;
4421 case EOpMethodGatherCmpBlue: cmpValues = 1; // fall through
4422 case EOpMethodGatherBlue: channel = 2; break;
4423 case EOpMethodGatherCmpAlpha: cmpValues = 1; // fall through
4424 case EOpMethodGatherAlpha: channel = 3; break;
4425 default: assert(0); break;
4426 }
4427
4428 // For now, we have nothing to map the component-wise comparison forms
4429 // to, because neither GLSL nor SPIR-V has such an opcode. Issue an
4430 // unimplemented error instead. Most of the machinery is here if that
4431 // should ever become available. However, red can be passed through
4432 // to OpImageDrefGather. G/B/A cannot, because that opcode does not
4433 // accept a component.
4434 if (cmpValues != 0 && op != EOpMethodGatherCmpRed) {
4435 error(loc, "unimplemented: component-level gather compare", "", "");
4436 return;
4437 }
4438
4439 int arg = 0;
4440
4441 TIntermTyped* argTex = argAggregate->getSequence()[arg++]->getAsTyped();
4442 TIntermTyped* argSamp = argAggregate->getSequence()[arg++]->getAsTyped();
4443 TIntermTyped* argCoord = argAggregate->getSequence()[arg++]->getAsTyped();
4444 TIntermTyped* argOffset = nullptr;
4445 TIntermTyped* argOffsets[4] = { nullptr, nullptr, nullptr, nullptr };
4446 // TIntermTyped* argStatus = nullptr; // TODO: residency
4447 TIntermTyped* argCmp = nullptr;
4448
4449 const TSamplerDim dim = argTex->getType().getSampler().dim;
4450
4451 const int argSize = (int)argAggregate->getSequence().size();
4452 bool hasStatus = (argSize == (5+cmpValues) || argSize == (8+cmpValues));
4453 bool hasOffset1 = false;
4454 bool hasOffset4 = false;
4455
4456 // Sampler argument should be a sampler.
4457 if (argSamp->getType().getBasicType() != EbtSampler) {
4458 error(loc, "expected: sampler type", "", "");
4459 return;
4460 }
4461
4462 // Cmp forms require SamplerComparisonState
4463 if (cmpValues > 0 && ! argSamp->getType().getSampler().isShadow()) {
4464 error(loc, "expected: SamplerComparisonState", "", "");
4465 return;
4466 }
4467
4468 // Only 2D forms can have offsets. Discover if we have 0, 1 or 4 offsets.
4469 if (dim == Esd2D) {
4470 hasOffset1 = (argSize == (4+cmpValues) || argSize == (5+cmpValues));
4471 hasOffset4 = (argSize == (7+cmpValues) || argSize == (8+cmpValues));
4472 }
4473
4474 assert(!(hasOffset1 && hasOffset4));
4475
4476 TOperator textureOp = EOpTextureGather;
4477
4478 // Compare forms have compare value
4479 if (cmpValues != 0)
4480 argCmp = argOffset = argAggregate->getSequence()[arg++]->getAsTyped();
4481
4482 // Some forms have single offset
4483 if (hasOffset1) {
4484 textureOp = EOpTextureGatherOffset; // single offset form
4485 argOffset = argAggregate->getSequence()[arg++]->getAsTyped();
4486 }
4487
4488 // Some forms have 4 gather offsets
4489 if (hasOffset4) {
4490 textureOp = EOpTextureGatherOffsets; // note plural, for 4 offset form
4491 for (int offsetNum = 0; offsetNum < 4; ++offsetNum)
4492 argOffsets[offsetNum] = argAggregate->getSequence()[arg++]->getAsTyped();
4493 }
4494
4495 // Residency status
4496 if (hasStatus) {
4497 // argStatus = argAggregate->getSequence()[arg++]->getAsTyped();
4498 error(loc, "unimplemented: residency status", "", "");
4499 return;
4500 }
4501
4502 TIntermAggregate* txgather = new TIntermAggregate(textureOp);
4503 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4504
4505 TIntermTyped* argChannel = intermediate.addConstantUnion(channel, loc, true);
4506
4507 txgather->getSequence().push_back(txcombine);
4508 txgather->getSequence().push_back(argCoord);
4509
4510 // AST wants an array of 4 offsets, where HLSL has separate args. Here
4511 // we construct an array from the separate args.
4512 if (hasOffset4) {
4513 TType arrayType(EbtInt, EvqTemporary, 2);
4514 TArraySizes* arraySizes = new TArraySizes;
4515 arraySizes->addInnerSize(4);
4516 arrayType.transferArraySizes(arraySizes);
4517
4518 TIntermAggregate* initList = new TIntermAggregate(EOpNull);
4519
4520 for (int offsetNum = 0; offsetNum < 4; ++offsetNum)
4521 initList->getSequence().push_back(argOffsets[offsetNum]);
4522
4523 argOffset = addConstructor(loc, initList, arrayType);
4524 }
4525
4526 // Add comparison value if we have one
4527 if (argCmp != nullptr)
4528 txgather->getSequence().push_back(argCmp);
4529
4530 // Add offset (either 1, or an array of 4) if we have one
4531 if (argOffset != nullptr)
4532 txgather->getSequence().push_back(argOffset);
4533
4534 // Add channel value if the sampler is not shadow
4535 if (! argSamp->getType().getSampler().isShadow())
4536 txgather->getSequence().push_back(argChannel);
4537
4538 txgather->setType(node->getType());
4539 txgather->setLoc(loc);
4540 node = txgather;
4541
4542 break;
4543 }
4544
4545 case EOpMethodCalculateLevelOfDetail:
4546 case EOpMethodCalculateLevelOfDetailUnclamped:
4547 {
4548 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4549 TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
4550 TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
4551
4552 TIntermAggregate* txquerylod = new TIntermAggregate(EOpTextureQueryLod);
4553
4554 TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
4555 txquerylod->getSequence().push_back(txcombine);
4556 txquerylod->getSequence().push_back(argCoord);
4557
4558 TIntermTyped* lodComponent = intermediate.addConstantUnion(
4559 op == EOpMethodCalculateLevelOfDetail ? 0 : 1,
4560 loc, true);
4561 TIntermTyped* lodComponentIdx = intermediate.addIndex(EOpIndexDirect, txquerylod, lodComponent, loc);
4562 lodComponentIdx->setType(TType(EbtFloat, EvqTemporary, 1));
4563 node = lodComponentIdx;
4564
4565 break;
4566 }
4567
4568 case EOpMethodGetSamplePosition:
4569 {
4570 // TODO: this entire decomposition exists because there is not yet a way to query
4571 // the sample position directly through SPIR-V. Instead, we return fixed sample
4572 // positions for common cases. *** If the sample positions are set differently,
4573 // this will be wrong. ***
4574
4575 TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
4576 TIntermTyped* argSampIdx = argAggregate->getSequence()[1]->getAsTyped();
4577
4578 TIntermAggregate* samplesQuery = new TIntermAggregate(EOpImageQuerySamples);
4579 samplesQuery->getSequence().push_back(argTex);
4580 samplesQuery->setType(TType(EbtUint, EvqTemporary, 1));
4581 samplesQuery->setLoc(loc);
4582
4583 TIntermAggregate* compoundStatement = nullptr;
4584
4585 TVariable* outSampleCount = makeInternalVariable("@sampleCount", TType(EbtUint));
4586 outSampleCount->getWritableType().getQualifier().makeTemporary();
4587 TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, intermediate.addSymbol(*outSampleCount, loc),
4588 samplesQuery, loc);
4589 compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
4590
4591 TIntermTyped* idxtest[4];
4592
4593 // Create tests against 2, 4, 8, and 16 sample values
4594 int count = 0;
4595 for (int val = 2; val <= 16; val *= 2)
4596 idxtest[count++] =
4597 intermediate.addBinaryNode(EOpEqual,
4598 intermediate.addSymbol(*outSampleCount, loc),
4599 intermediate.addConstantUnion(val, loc),
4600 loc, TType(EbtBool));
4601
4602 const TOperator idxOp = (argSampIdx->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect;
4603
4604 // Create index ops into position arrays given sample index.
4605 // TODO: should it be clamped?
4606 TIntermTyped* index[4];
4607 count = 0;
4608 for (int val = 2; val <= 16; val *= 2) {
4609 index[count] = intermediate.addIndex(idxOp, getSamplePosArray(val), argSampIdx, loc);
4610 index[count++]->setType(TType(EbtFloat, EvqTemporary, 2));
4611 }
4612
4613 // Create expression as:
4614 // (sampleCount == 2) ? pos2[idx] :
4615 // (sampleCount == 4) ? pos4[idx] :
4616 // (sampleCount == 8) ? pos8[idx] :
4617 // (sampleCount == 16) ? pos16[idx] : float2(0,0);
4618 TIntermTyped* test =
4619 intermediate.addSelection(idxtest[0], index[0],
4620 intermediate.addSelection(idxtest[1], index[1],
4621 intermediate.addSelection(idxtest[2], index[2],
4622 intermediate.addSelection(idxtest[3], index[3],
4623 getSamplePosArray(1), loc), loc), loc), loc);
4624
4625 compoundStatement = intermediate.growAggregate(compoundStatement, test);
4626 compoundStatement->setOperator(EOpSequence);
4627 compoundStatement->setLoc(loc);
4628 compoundStatement->setType(TType(EbtFloat, EvqTemporary, 2));
4629
4630 node = compoundStatement;
4631
4632 break;
4633 }
4634
4635 case EOpSubpassLoad:
4636 {
4637 const TIntermTyped* argSubpass =
4638 argAggregate ? argAggregate->getSequence()[0]->getAsTyped() :
4639 arguments->getAsTyped();
4640
4641 const TSampler& sampler = argSubpass->getType().getSampler();
4642
4643 // subpass load: the multisample form is overloaded. Here, we convert that to
4644 // the EOpSubpassLoadMS opcode.
4645 if (argAggregate != nullptr && argAggregate->getSequence().size() > 1)
4646 node->getAsOperator()->setOp(EOpSubpassLoadMS);
4647
4648 node = convertReturn(node, sampler);
4649
4650 break;
4651 }
4652
4653
4654 default:
4655 break; // most pass through unchanged
4656 }
4657 }
4658
4659 //
4660 // Decompose geometry shader methods
4661 //
decomposeGeometryMethods(const TSourceLoc & loc,TIntermTyped * & node,TIntermNode * arguments)4662 void HlslParseContext::decomposeGeometryMethods(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
4663 {
4664 if (node == nullptr || !node->getAsOperator())
4665 return;
4666
4667 const TOperator op = node->getAsOperator()->getOp();
4668 const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
4669
4670 switch (op) {
4671 case EOpMethodAppend:
4672 if (argAggregate) {
4673 // Don't emit these for non-GS stage, since we won't have the gsStreamOutput symbol.
4674 if (language != EShLangGeometry) {
4675 node = nullptr;
4676 return;
4677 }
4678
4679 TIntermAggregate* sequence = nullptr;
4680 TIntermAggregate* emit = new TIntermAggregate(EOpEmitVertex);
4681
4682 emit->setLoc(loc);
4683 emit->setType(TType(EbtVoid));
4684
4685 TIntermTyped* data = argAggregate->getSequence()[1]->getAsTyped();
4686
4687 // This will be patched in finalization during finalizeAppendMethods()
4688 sequence = intermediate.growAggregate(sequence, data, loc);
4689 sequence = intermediate.growAggregate(sequence, emit);
4690
4691 sequence->setOperator(EOpSequence);
4692 sequence->setLoc(loc);
4693 sequence->setType(TType(EbtVoid));
4694
4695 gsAppends.push_back({sequence, loc});
4696
4697 node = sequence;
4698 }
4699 break;
4700
4701 case EOpMethodRestartStrip:
4702 {
4703 // Don't emit these for non-GS stage, since we won't have the gsStreamOutput symbol.
4704 if (language != EShLangGeometry) {
4705 node = nullptr;
4706 return;
4707 }
4708
4709 TIntermAggregate* cut = new TIntermAggregate(EOpEndPrimitive);
4710 cut->setLoc(loc);
4711 cut->setType(TType(EbtVoid));
4712 node = cut;
4713 }
4714 break;
4715
4716 default:
4717 break; // most pass through unchanged
4718 }
4719 }
4720
4721 //
4722 // Optionally decompose intrinsics to AST opcodes.
4723 //
decomposeIntrinsic(const TSourceLoc & loc,TIntermTyped * & node,TIntermNode * arguments)4724 void HlslParseContext::decomposeIntrinsic(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
4725 {
4726 // Helper to find image data for image atomics:
4727 // OpImageLoad(image[idx])
4728 // We take the image load apart and add its params to the atomic op aggregate node
4729 const auto imageAtomicParams = [this, &loc, &node](TIntermAggregate* atomic, TIntermTyped* load) {
4730 TIntermAggregate* loadOp = load->getAsAggregate();
4731 if (loadOp == nullptr) {
4732 error(loc, "unknown image type in atomic operation", "", "");
4733 node = nullptr;
4734 return;
4735 }
4736
4737 atomic->getSequence().push_back(loadOp->getSequence()[0]);
4738 atomic->getSequence().push_back(loadOp->getSequence()[1]);
4739 };
4740
4741 // Return true if this is an imageLoad, which we will change to an image atomic.
4742 const auto isImageParam = [](TIntermTyped* image) -> bool {
4743 TIntermAggregate* imageAggregate = image->getAsAggregate();
4744 return imageAggregate != nullptr && imageAggregate->getOp() == EOpImageLoad;
4745 };
4746
4747 const auto lookupBuiltinVariable = [&](const char* name, TBuiltInVariable builtin, TType& type) -> TIntermTyped* {
4748 TSymbol* symbol = symbolTable.find(name);
4749 if (nullptr == symbol) {
4750 type.getQualifier().builtIn = builtin;
4751
4752 TVariable* variable = new TVariable(NewPoolTString(name), type);
4753
4754 symbolTable.insert(*variable);
4755
4756 symbol = symbolTable.find(name);
4757 assert(symbol && "Inserted symbol could not be found!");
4758 }
4759
4760 return intermediate.addSymbol(*(symbol->getAsVariable()), loc);
4761 };
4762
4763 // HLSL intrinsics can be pass through to native AST opcodes, or decomposed here to existing AST
4764 // opcodes for compatibility with existing software stacks.
4765 static const bool decomposeHlslIntrinsics = true;
4766
4767 if (!decomposeHlslIntrinsics || !node || !node->getAsOperator())
4768 return;
4769
4770 const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
4771 TIntermUnary* fnUnary = node->getAsUnaryNode();
4772 const TOperator op = node->getAsOperator()->getOp();
4773
4774 switch (op) {
4775 case EOpGenMul:
4776 {
4777 // mul(a,b) -> MatrixTimesMatrix, MatrixTimesVector, MatrixTimesScalar, VectorTimesScalar, Dot, Mul
4778 // Since we are treating HLSL rows like GLSL columns (the first matrix indirection),
4779 // we must reverse the operand order here. Hence, arg0 gets sequence[1], etc.
4780 TIntermTyped* arg0 = argAggregate->getSequence()[1]->getAsTyped();
4781 TIntermTyped* arg1 = argAggregate->getSequence()[0]->getAsTyped();
4782
4783 if (arg0->isVector() && arg1->isVector()) { // vec * vec
4784 node->getAsAggregate()->setOperator(EOpDot);
4785 } else {
4786 node = handleBinaryMath(loc, "mul", EOpMul, arg0, arg1);
4787 }
4788
4789 break;
4790 }
4791
4792 case EOpRcp:
4793 {
4794 // rcp(a) -> 1 / a
4795 TIntermTyped* arg0 = fnUnary->getOperand();
4796 TBasicType type0 = arg0->getBasicType();
4797 TIntermTyped* one = intermediate.addConstantUnion(1, type0, loc, true);
4798 node = handleBinaryMath(loc, "rcp", EOpDiv, one, arg0);
4799
4800 break;
4801 }
4802
4803 case EOpAny: // fall through
4804 case EOpAll:
4805 {
4806 TIntermTyped* typedArg = arguments->getAsTyped();
4807
4808 // HLSL allows float/etc types here, and the SPIR-V opcode requires a bool.
4809 // We'll convert here. Note that for efficiency, we could add a smarter
4810 // decomposition for some type cases, e.g, maybe by decomposing a dot product.
4811 if (typedArg->getType().getBasicType() != EbtBool) {
4812 const TType boolType(EbtBool, EvqTemporary,
4813 typedArg->getVectorSize(),
4814 typedArg->getMatrixCols(),
4815 typedArg->getMatrixRows(),
4816 typedArg->isVector());
4817
4818 typedArg = intermediate.addConversion(EOpConstructBool, boolType, typedArg);
4819 node->getAsUnaryNode()->setOperand(typedArg);
4820 }
4821
4822 break;
4823 }
4824
4825 case EOpSaturate:
4826 {
4827 // saturate(a) -> clamp(a,0,1)
4828 TIntermTyped* arg0 = fnUnary->getOperand();
4829 TBasicType type0 = arg0->getBasicType();
4830 TIntermAggregate* clamp = new TIntermAggregate(EOpClamp);
4831
4832 clamp->getSequence().push_back(arg0);
4833 clamp->getSequence().push_back(intermediate.addConstantUnion(0, type0, loc, true));
4834 clamp->getSequence().push_back(intermediate.addConstantUnion(1, type0, loc, true));
4835 clamp->setLoc(loc);
4836 clamp->setType(node->getType());
4837 clamp->getWritableType().getQualifier().makeTemporary();
4838 node = clamp;
4839
4840 break;
4841 }
4842
4843 case EOpSinCos:
4844 {
4845 // sincos(a,b,c) -> b = sin(a), c = cos(a)
4846 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
4847 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
4848 TIntermTyped* arg2 = argAggregate->getSequence()[2]->getAsTyped();
4849
4850 TIntermTyped* sinStatement = handleUnaryMath(loc, "sin", EOpSin, arg0);
4851 TIntermTyped* cosStatement = handleUnaryMath(loc, "cos", EOpCos, arg0);
4852 TIntermTyped* sinAssign = intermediate.addAssign(EOpAssign, arg1, sinStatement, loc);
4853 TIntermTyped* cosAssign = intermediate.addAssign(EOpAssign, arg2, cosStatement, loc);
4854
4855 TIntermAggregate* compoundStatement = intermediate.makeAggregate(sinAssign, loc);
4856 compoundStatement = intermediate.growAggregate(compoundStatement, cosAssign);
4857 compoundStatement->setOperator(EOpSequence);
4858 compoundStatement->setLoc(loc);
4859 compoundStatement->setType(TType(EbtVoid));
4860
4861 node = compoundStatement;
4862
4863 break;
4864 }
4865
4866 case EOpClip:
4867 {
4868 // clip(a) -> if (any(a<0)) discard;
4869 TIntermTyped* arg0 = fnUnary->getOperand();
4870 TBasicType type0 = arg0->getBasicType();
4871 TIntermTyped* compareNode = nullptr;
4872
4873 // For non-scalars: per experiment with FXC compiler, discard if any component < 0.
4874 if (!arg0->isScalar()) {
4875 // component-wise compare: a < 0
4876 TIntermAggregate* less = new TIntermAggregate(EOpLessThan);
4877 less->getSequence().push_back(arg0);
4878 less->setLoc(loc);
4879
4880 // make vec or mat of bool matching dimensions of input
4881 less->setType(TType(EbtBool, EvqTemporary,
4882 arg0->getType().getVectorSize(),
4883 arg0->getType().getMatrixCols(),
4884 arg0->getType().getMatrixRows(),
4885 arg0->getType().isVector()));
4886
4887 // calculate # of components for comparison const
4888 const int constComponentCount =
4889 std::max(arg0->getType().getVectorSize(), 1) *
4890 std::max(arg0->getType().getMatrixCols(), 1) *
4891 std::max(arg0->getType().getMatrixRows(), 1);
4892
4893 TConstUnion zero;
4894 if (arg0->getType().isIntegerDomain())
4895 zero.setDConst(0);
4896 else
4897 zero.setDConst(0.0);
4898 TConstUnionArray zeros(constComponentCount, zero);
4899
4900 less->getSequence().push_back(intermediate.addConstantUnion(zeros, arg0->getType(), loc, true));
4901
4902 compareNode = intermediate.addBuiltInFunctionCall(loc, EOpAny, true, less, TType(EbtBool));
4903 } else {
4904 TIntermTyped* zero;
4905 if (arg0->getType().isIntegerDomain())
4906 zero = intermediate.addConstantUnion(0, loc, true);
4907 else
4908 zero = intermediate.addConstantUnion(0.0, type0, loc, true);
4909 compareNode = handleBinaryMath(loc, "clip", EOpLessThan, arg0, zero);
4910 }
4911
4912 TIntermBranch* killNode = intermediate.addBranch(EOpKill, loc);
4913
4914 node = new TIntermSelection(compareNode, killNode, nullptr);
4915 node->setLoc(loc);
4916
4917 break;
4918 }
4919
4920 case EOpLog10:
4921 {
4922 // log10(a) -> log2(a) * 0.301029995663981 (== 1/log2(10))
4923 TIntermTyped* arg0 = fnUnary->getOperand();
4924 TIntermTyped* log2 = handleUnaryMath(loc, "log2", EOpLog2, arg0);
4925 TIntermTyped* base = intermediate.addConstantUnion(0.301029995663981f, EbtFloat, loc, true);
4926
4927 node = handleBinaryMath(loc, "mul", EOpMul, log2, base);
4928
4929 break;
4930 }
4931
4932 case EOpDst:
4933 {
4934 // dest.x = 1;
4935 // dest.y = src0.y * src1.y;
4936 // dest.z = src0.z;
4937 // dest.w = src1.w;
4938
4939 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
4940 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
4941
4942 TIntermTyped* y = intermediate.addConstantUnion(1, loc, true);
4943 TIntermTyped* z = intermediate.addConstantUnion(2, loc, true);
4944 TIntermTyped* w = intermediate.addConstantUnion(3, loc, true);
4945
4946 TIntermTyped* src0y = intermediate.addIndex(EOpIndexDirect, arg0, y, loc);
4947 TIntermTyped* src1y = intermediate.addIndex(EOpIndexDirect, arg1, y, loc);
4948 TIntermTyped* src0z = intermediate.addIndex(EOpIndexDirect, arg0, z, loc);
4949 TIntermTyped* src1w = intermediate.addIndex(EOpIndexDirect, arg1, w, loc);
4950
4951 TIntermAggregate* dst = new TIntermAggregate(EOpConstructVec4);
4952
4953 dst->getSequence().push_back(intermediate.addConstantUnion(1.0, EbtFloat, loc, true));
4954 dst->getSequence().push_back(handleBinaryMath(loc, "mul", EOpMul, src0y, src1y));
4955 dst->getSequence().push_back(src0z);
4956 dst->getSequence().push_back(src1w);
4957 dst->setType(TType(EbtFloat, EvqTemporary, 4));
4958 dst->setLoc(loc);
4959 node = dst;
4960
4961 break;
4962 }
4963
4964 case EOpInterlockedAdd: // optional last argument (if present) is assigned from return value
4965 case EOpInterlockedMin: // ...
4966 case EOpInterlockedMax: // ...
4967 case EOpInterlockedAnd: // ...
4968 case EOpInterlockedOr: // ...
4969 case EOpInterlockedXor: // ...
4970 case EOpInterlockedExchange: // always has output arg
4971 {
4972 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // dest
4973 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // value
4974 TIntermTyped* arg2 = nullptr;
4975
4976 if (argAggregate->getSequence().size() > 2)
4977 arg2 = argAggregate->getSequence()[2]->getAsTyped();
4978
4979 const bool isImage = isImageParam(arg0);
4980 const TOperator atomicOp = mapAtomicOp(loc, op, isImage);
4981 TIntermAggregate* atomic = new TIntermAggregate(atomicOp);
4982 atomic->setType(arg0->getType());
4983 atomic->getWritableType().getQualifier().makeTemporary();
4984 atomic->setLoc(loc);
4985
4986 if (isImage) {
4987 // orig_value = imageAtomicOp(image, loc, data)
4988 imageAtomicParams(atomic, arg0);
4989 atomic->getSequence().push_back(arg1);
4990
4991 if (argAggregate->getSequence().size() > 2) {
4992 node = intermediate.addAssign(EOpAssign, arg2, atomic, loc);
4993 } else {
4994 node = atomic; // no assignment needed, as there was no out var.
4995 }
4996 } else {
4997 // Normal memory variable:
4998 // arg0 = mem, arg1 = data, arg2(optional,out) = orig_value
4999 if (argAggregate->getSequence().size() > 2) {
5000 // optional output param is present. return value goes to arg2.
5001 atomic->getSequence().push_back(arg0);
5002 atomic->getSequence().push_back(arg1);
5003
5004 node = intermediate.addAssign(EOpAssign, arg2, atomic, loc);
5005 } else {
5006 // Set the matching operator. Since output is absent, this is all we need to do.
5007 node->getAsAggregate()->setOperator(atomicOp);
5008 node->setType(atomic->getType());
5009 }
5010 }
5011
5012 break;
5013 }
5014
5015 case EOpInterlockedCompareExchange:
5016 {
5017 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // dest
5018 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // cmp
5019 TIntermTyped* arg2 = argAggregate->getSequence()[2]->getAsTyped(); // value
5020 TIntermTyped* arg3 = argAggregate->getSequence()[3]->getAsTyped(); // orig
5021
5022 const bool isImage = isImageParam(arg0);
5023 TIntermAggregate* atomic = new TIntermAggregate(mapAtomicOp(loc, op, isImage));
5024 atomic->setLoc(loc);
5025 atomic->setType(arg2->getType());
5026 atomic->getWritableType().getQualifier().makeTemporary();
5027
5028 if (isImage) {
5029 imageAtomicParams(atomic, arg0);
5030 } else {
5031 atomic->getSequence().push_back(arg0);
5032 }
5033
5034 atomic->getSequence().push_back(arg1);
5035 atomic->getSequence().push_back(arg2);
5036 node = intermediate.addAssign(EOpAssign, arg3, atomic, loc);
5037
5038 break;
5039 }
5040
5041 case EOpEvaluateAttributeSnapped:
5042 {
5043 // SPIR-V InterpolateAtOffset uses float vec2 offset in pixels
5044 // HLSL uses int2 offset on a 16x16 grid in [-8..7] on x & y:
5045 // iU = (iU<<28)>>28
5046 // fU = ((float)iU)/16
5047 // Targets might handle this natively, in which case they can disable
5048 // decompositions.
5049
5050 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // value
5051 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // offset
5052
5053 TIntermTyped* i28 = intermediate.addConstantUnion(28, loc, true);
5054 TIntermTyped* iU = handleBinaryMath(loc, ">>", EOpRightShift,
5055 handleBinaryMath(loc, "<<", EOpLeftShift, arg1, i28),
5056 i28);
5057
5058 TIntermTyped* recip16 = intermediate.addConstantUnion((1.0/16.0), EbtFloat, loc, true);
5059 TIntermTyped* floatOffset = handleBinaryMath(loc, "mul", EOpMul,
5060 intermediate.addConversion(EOpConstructFloat,
5061 TType(EbtFloat, EvqTemporary, 2), iU),
5062 recip16);
5063
5064 TIntermAggregate* interp = new TIntermAggregate(EOpInterpolateAtOffset);
5065 interp->getSequence().push_back(arg0);
5066 interp->getSequence().push_back(floatOffset);
5067 interp->setLoc(loc);
5068 interp->setType(arg0->getType());
5069 interp->getWritableType().getQualifier().makeTemporary();
5070
5071 node = interp;
5072
5073 break;
5074 }
5075
5076 case EOpLit:
5077 {
5078 TIntermTyped* n_dot_l = argAggregate->getSequence()[0]->getAsTyped();
5079 TIntermTyped* n_dot_h = argAggregate->getSequence()[1]->getAsTyped();
5080 TIntermTyped* m = argAggregate->getSequence()[2]->getAsTyped();
5081
5082 TIntermAggregate* dst = new TIntermAggregate(EOpConstructVec4);
5083
5084 // Ambient
5085 dst->getSequence().push_back(intermediate.addConstantUnion(1.0, EbtFloat, loc, true));
5086
5087 // Diffuse:
5088 TIntermTyped* zero = intermediate.addConstantUnion(0.0, EbtFloat, loc, true);
5089 TIntermAggregate* diffuse = new TIntermAggregate(EOpMax);
5090 diffuse->getSequence().push_back(n_dot_l);
5091 diffuse->getSequence().push_back(zero);
5092 diffuse->setLoc(loc);
5093 diffuse->setType(TType(EbtFloat));
5094 dst->getSequence().push_back(diffuse);
5095
5096 // Specular:
5097 TIntermAggregate* min_ndot = new TIntermAggregate(EOpMin);
5098 min_ndot->getSequence().push_back(n_dot_l);
5099 min_ndot->getSequence().push_back(n_dot_h);
5100 min_ndot->setLoc(loc);
5101 min_ndot->setType(TType(EbtFloat));
5102
5103 TIntermTyped* compare = handleBinaryMath(loc, "<", EOpLessThan, min_ndot, zero);
5104 TIntermTyped* n_dot_h_m = handleBinaryMath(loc, "mul", EOpMul, n_dot_h, m); // n_dot_h * m
5105
5106 dst->getSequence().push_back(intermediate.addSelection(compare, zero, n_dot_h_m, loc));
5107
5108 // One:
5109 dst->getSequence().push_back(intermediate.addConstantUnion(1.0, EbtFloat, loc, true));
5110
5111 dst->setLoc(loc);
5112 dst->setType(TType(EbtFloat, EvqTemporary, 4));
5113 node = dst;
5114 break;
5115 }
5116
5117 case EOpAsDouble:
5118 {
5119 // asdouble accepts two 32 bit ints. we can use EOpUint64BitsToDouble, but must
5120 // first construct a uint64.
5121 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
5122 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
5123
5124 if (arg0->getType().isVector()) { // TODO: ...
5125 error(loc, "double2 conversion not implemented", "asdouble", "");
5126 break;
5127 }
5128
5129 TIntermAggregate* uint64 = new TIntermAggregate(EOpConstructUVec2);
5130
5131 uint64->getSequence().push_back(arg0);
5132 uint64->getSequence().push_back(arg1);
5133 uint64->setType(TType(EbtUint, EvqTemporary, 2)); // convert 2 uints to a uint2
5134 uint64->setLoc(loc);
5135
5136 // bitcast uint2 to a double
5137 TIntermTyped* convert = new TIntermUnary(EOpUint64BitsToDouble);
5138 convert->getAsUnaryNode()->setOperand(uint64);
5139 convert->setLoc(loc);
5140 convert->setType(TType(EbtDouble, EvqTemporary));
5141 node = convert;
5142
5143 break;
5144 }
5145
5146 case EOpF16tof32:
5147 {
5148 // input uvecN with low 16 bits of each component holding a float16. convert to float32.
5149 TIntermTyped* argValue = node->getAsUnaryNode()->getOperand();
5150 TIntermTyped* zero = intermediate.addConstantUnion(0, loc, true);
5151 const int vecSize = argValue->getType().getVectorSize();
5152
5153 TOperator constructOp = EOpNull;
5154 switch (vecSize) {
5155 case 1: constructOp = EOpNull; break; // direct use, no construct needed
5156 case 2: constructOp = EOpConstructVec2; break;
5157 case 3: constructOp = EOpConstructVec3; break;
5158 case 4: constructOp = EOpConstructVec4; break;
5159 default: assert(0); break;
5160 }
5161
5162 // For scalar case, we don't need to construct another type.
5163 TIntermAggregate* result = (vecSize > 1) ? new TIntermAggregate(constructOp) : nullptr;
5164
5165 if (result) {
5166 result->setType(TType(EbtFloat, EvqTemporary, vecSize));
5167 result->setLoc(loc);
5168 }
5169
5170 for (int idx = 0; idx < vecSize; ++idx) {
5171 TIntermTyped* idxConst = intermediate.addConstantUnion(idx, loc, true);
5172 TIntermTyped* component = argValue->getType().isVector() ?
5173 intermediate.addIndex(EOpIndexDirect, argValue, idxConst, loc) : argValue;
5174
5175 if (component != argValue)
5176 component->setType(TType(argValue->getBasicType(), EvqTemporary));
5177
5178 TIntermTyped* unpackOp = new TIntermUnary(EOpUnpackHalf2x16);
5179 unpackOp->setType(TType(EbtFloat, EvqTemporary, 2));
5180 unpackOp->getAsUnaryNode()->setOperand(component);
5181 unpackOp->setLoc(loc);
5182
5183 TIntermTyped* lowOrder = intermediate.addIndex(EOpIndexDirect, unpackOp, zero, loc);
5184
5185 if (result != nullptr) {
5186 result->getSequence().push_back(lowOrder);
5187 node = result;
5188 } else {
5189 node = lowOrder;
5190 }
5191 }
5192
5193 break;
5194 }
5195
5196 case EOpF32tof16:
5197 {
5198 // input floatN converted to 16 bit float in low order bits of each component of uintN
5199 TIntermTyped* argValue = node->getAsUnaryNode()->getOperand();
5200
5201 TIntermTyped* zero = intermediate.addConstantUnion(0.0, EbtFloat, loc, true);
5202 const int vecSize = argValue->getType().getVectorSize();
5203
5204 TOperator constructOp = EOpNull;
5205 switch (vecSize) {
5206 case 1: constructOp = EOpNull; break; // direct use, no construct needed
5207 case 2: constructOp = EOpConstructUVec2; break;
5208 case 3: constructOp = EOpConstructUVec3; break;
5209 case 4: constructOp = EOpConstructUVec4; break;
5210 default: assert(0); break;
5211 }
5212
5213 // For scalar case, we don't need to construct another type.
5214 TIntermAggregate* result = (vecSize > 1) ? new TIntermAggregate(constructOp) : nullptr;
5215
5216 if (result) {
5217 result->setType(TType(EbtUint, EvqTemporary, vecSize));
5218 result->setLoc(loc);
5219 }
5220
5221 for (int idx = 0; idx < vecSize; ++idx) {
5222 TIntermTyped* idxConst = intermediate.addConstantUnion(idx, loc, true);
5223 TIntermTyped* component = argValue->getType().isVector() ?
5224 intermediate.addIndex(EOpIndexDirect, argValue, idxConst, loc) : argValue;
5225
5226 if (component != argValue)
5227 component->setType(TType(argValue->getBasicType(), EvqTemporary));
5228
5229 TIntermAggregate* vec2ComponentAndZero = new TIntermAggregate(EOpConstructVec2);
5230 vec2ComponentAndZero->getSequence().push_back(component);
5231 vec2ComponentAndZero->getSequence().push_back(zero);
5232 vec2ComponentAndZero->setType(TType(EbtFloat, EvqTemporary, 2));
5233 vec2ComponentAndZero->setLoc(loc);
5234
5235 TIntermTyped* packOp = new TIntermUnary(EOpPackHalf2x16);
5236 packOp->getAsUnaryNode()->setOperand(vec2ComponentAndZero);
5237 packOp->setLoc(loc);
5238 packOp->setType(TType(EbtUint, EvqTemporary));
5239
5240 if (result != nullptr) {
5241 result->getSequence().push_back(packOp);
5242 node = result;
5243 } else {
5244 node = packOp;
5245 }
5246 }
5247
5248 break;
5249 }
5250
5251 case EOpD3DCOLORtoUBYTE4:
5252 {
5253 // ivec4 ( x.zyxw * 255.001953 );
5254 TIntermTyped* arg0 = node->getAsUnaryNode()->getOperand();
5255 TSwizzleSelectors<TVectorSelector> selectors;
5256 selectors.push_back(2);
5257 selectors.push_back(1);
5258 selectors.push_back(0);
5259 selectors.push_back(3);
5260 TIntermTyped* swizzleIdx = intermediate.addSwizzle(selectors, loc);
5261 TIntermTyped* swizzled = intermediate.addIndex(EOpVectorSwizzle, arg0, swizzleIdx, loc);
5262 swizzled->setType(arg0->getType());
5263 swizzled->getWritableType().getQualifier().makeTemporary();
5264
5265 TIntermTyped* conversion = intermediate.addConstantUnion(255.001953f, EbtFloat, loc, true);
5266 TIntermTyped* rangeConverted = handleBinaryMath(loc, "mul", EOpMul, conversion, swizzled);
5267 rangeConverted->setType(arg0->getType());
5268 rangeConverted->getWritableType().getQualifier().makeTemporary();
5269
5270 node = intermediate.addConversion(EOpConstructInt, TType(EbtInt, EvqTemporary, 4), rangeConverted);
5271 node->setLoc(loc);
5272 node->setType(TType(EbtInt, EvqTemporary, 4));
5273 break;
5274 }
5275
5276 case EOpIsFinite:
5277 {
5278 // Since OPIsFinite in SPIR-V is only supported with the Kernel capability, we translate
5279 // it to !isnan && !isinf
5280
5281 TIntermTyped* arg0 = node->getAsUnaryNode()->getOperand();
5282
5283 // We'll make a temporary in case the RHS is cmoplex
5284 TVariable* tempArg = makeInternalVariable("@finitetmp", arg0->getType());
5285 tempArg->getWritableType().getQualifier().makeTemporary();
5286
5287 TIntermTyped* tmpArgAssign = intermediate.addAssign(EOpAssign,
5288 intermediate.addSymbol(*tempArg, loc),
5289 arg0, loc);
5290
5291 TIntermAggregate* compoundStatement = intermediate.makeAggregate(tmpArgAssign, loc);
5292
5293 const TType boolType(EbtBool, EvqTemporary, arg0->getVectorSize(), arg0->getMatrixCols(),
5294 arg0->getMatrixRows());
5295
5296 TIntermTyped* isnan = handleUnaryMath(loc, "isnan", EOpIsNan, intermediate.addSymbol(*tempArg, loc));
5297 isnan->setType(boolType);
5298
5299 TIntermTyped* notnan = handleUnaryMath(loc, "!", EOpLogicalNot, isnan);
5300 notnan->setType(boolType);
5301
5302 TIntermTyped* isinf = handleUnaryMath(loc, "isinf", EOpIsInf, intermediate.addSymbol(*tempArg, loc));
5303 isinf->setType(boolType);
5304
5305 TIntermTyped* notinf = handleUnaryMath(loc, "!", EOpLogicalNot, isinf);
5306 notinf->setType(boolType);
5307
5308 TIntermTyped* andNode = handleBinaryMath(loc, "and", EOpLogicalAnd, notnan, notinf);
5309 andNode->setType(boolType);
5310
5311 compoundStatement = intermediate.growAggregate(compoundStatement, andNode);
5312 compoundStatement->setOperator(EOpSequence);
5313 compoundStatement->setLoc(loc);
5314 compoundStatement->setType(boolType);
5315
5316 node = compoundStatement;
5317
5318 break;
5319 }
5320 case EOpWaveGetLaneCount:
5321 {
5322 // Mapped to gl_SubgroupSize builtin (We preprend @ to the symbol
5323 // so that it inhabits the symbol table, but has a user-invalid name
5324 // in-case some source HLSL defined the symbol also).
5325 TType type(EbtUint, EvqVaryingIn);
5326 node = lookupBuiltinVariable("@gl_SubgroupSize", EbvSubgroupSize2, type);
5327 break;
5328 }
5329 case EOpWaveGetLaneIndex:
5330 {
5331 // Mapped to gl_SubgroupInvocationID builtin (We preprend @ to the
5332 // symbol so that it inhabits the symbol table, but has a
5333 // user-invalid name in-case some source HLSL defined the symbol
5334 // also).
5335 TType type(EbtUint, EvqVaryingIn);
5336 node = lookupBuiltinVariable("@gl_SubgroupInvocationID", EbvSubgroupInvocation2, type);
5337 break;
5338 }
5339 case EOpWaveActiveCountBits:
5340 {
5341 // Mapped to subgroupBallotBitCount(subgroupBallot()) builtin
5342
5343 // uvec4 type.
5344 TType uvec4Type(EbtUint, EvqTemporary, 4);
5345
5346 // Get the uvec4 return from subgroupBallot().
5347 TIntermTyped* res = intermediate.addBuiltInFunctionCall(loc,
5348 EOpSubgroupBallot, true, arguments, uvec4Type);
5349
5350 // uint type.
5351 TType uintType(EbtUint, EvqTemporary);
5352
5353 node = intermediate.addBuiltInFunctionCall(loc,
5354 EOpSubgroupBallotBitCount, true, res, uintType);
5355
5356 break;
5357 }
5358 case EOpWavePrefixCountBits:
5359 {
5360 // Mapped to subgroupBallotInclusiveBitCount(subgroupBallot())
5361 // builtin
5362
5363 // uvec4 type.
5364 TType uvec4Type(EbtUint, EvqTemporary, 4);
5365
5366 // Get the uvec4 return from subgroupBallot().
5367 TIntermTyped* res = intermediate.addBuiltInFunctionCall(loc,
5368 EOpSubgroupBallot, true, arguments, uvec4Type);
5369
5370 // uint type.
5371 TType uintType(EbtUint, EvqTemporary);
5372
5373 node = intermediate.addBuiltInFunctionCall(loc,
5374 EOpSubgroupBallotInclusiveBitCount, true, res, uintType);
5375
5376 break;
5377 }
5378
5379 default:
5380 break; // most pass through unchanged
5381 }
5382 }
5383
5384 //
5385 // Handle seeing function call syntax in the grammar, which could be any of
5386 // - .length() method
5387 // - constructor
5388 // - a call to a built-in function mapped to an operator
5389 // - a call to a built-in function that will remain a function call (e.g., texturing)
5390 // - user function
5391 // - subroutine call (not implemented yet)
5392 //
handleFunctionCall(const TSourceLoc & loc,TFunction * function,TIntermTyped * arguments)5393 TIntermTyped* HlslParseContext::handleFunctionCall(const TSourceLoc& loc, TFunction* function, TIntermTyped* arguments)
5394 {
5395 TIntermTyped* result = nullptr;
5396
5397 TOperator op = function->getBuiltInOp();
5398 if (op != EOpNull) {
5399 //
5400 // Then this should be a constructor.
5401 // Don't go through the symbol table for constructors.
5402 // Their parameters will be verified algorithmically.
5403 //
5404 TType type(EbtVoid); // use this to get the type back
5405 if (! constructorError(loc, arguments, *function, op, type)) {
5406 //
5407 // It's a constructor, of type 'type'.
5408 //
5409 result = handleConstructor(loc, arguments, type);
5410 if (result == nullptr) {
5411 error(loc, "cannot construct with these arguments", type.getCompleteString().c_str(), "");
5412 return nullptr;
5413 }
5414 }
5415 } else {
5416 //
5417 // Find it in the symbol table.
5418 //
5419 const TFunction* fnCandidate = nullptr;
5420 bool builtIn = false;
5421 int thisDepth = 0;
5422
5423 // For mat mul, the situation is unusual: we have to compare vector sizes to mat row or col sizes,
5424 // and clamp the opposite arg. Since that's complex, we farm it off to a separate method.
5425 // It doesn't naturally fall out of processing an argument at a time in isolation.
5426 if (function->getName() == "mul")
5427 addGenMulArgumentConversion(loc, *function, arguments);
5428
5429 TIntermAggregate* aggregate = arguments ? arguments->getAsAggregate() : nullptr;
5430
5431 // TODO: this needs improvement: there's no way at present to look up a signature in
5432 // the symbol table for an arbitrary type. This is a temporary hack until that ability exists.
5433 // It will have false positives, since it doesn't check arg counts or types.
5434 if (arguments) {
5435 // Check if first argument is struct buffer type. It may be an aggregate or a symbol, so we
5436 // look for either case.
5437
5438 TIntermTyped* arg0 = nullptr;
5439
5440 if (aggregate && aggregate->getSequence().size() > 0 && aggregate->getSequence()[0])
5441 arg0 = aggregate->getSequence()[0]->getAsTyped();
5442 else if (arguments->getAsSymbolNode())
5443 arg0 = arguments->getAsSymbolNode();
5444
5445 if (arg0 != nullptr && isStructBufferType(arg0->getType())) {
5446 static const int methodPrefixSize = sizeof(BUILTIN_PREFIX)-1;
5447
5448 if (function->getName().length() > methodPrefixSize &&
5449 isStructBufferMethod(function->getName().substr(methodPrefixSize))) {
5450 const TString mangle = function->getName() + "(";
5451 TSymbol* symbol = symbolTable.find(mangle, &builtIn);
5452
5453 if (symbol)
5454 fnCandidate = symbol->getAsFunction();
5455 }
5456 }
5457 }
5458
5459 if (fnCandidate == nullptr)
5460 fnCandidate = findFunction(loc, *function, builtIn, thisDepth, arguments);
5461
5462 if (fnCandidate) {
5463 // This is a declared function that might map to
5464 // - a built-in operator,
5465 // - a built-in function not mapped to an operator, or
5466 // - a user function.
5467
5468 // turn an implicit member-function resolution into an explicit call
5469 TString callerName;
5470 if (thisDepth == 0)
5471 callerName = fnCandidate->getMangledName();
5472 else {
5473 // get the explicit (full) name of the function
5474 callerName = currentTypePrefix[currentTypePrefix.size() - thisDepth];
5475 callerName += fnCandidate->getMangledName();
5476 // insert the implicit calling argument
5477 pushFrontArguments(intermediate.addSymbol(*getImplicitThis(thisDepth)), arguments);
5478 }
5479
5480 // Convert 'in' arguments, so that types match.
5481 // However, skip those that need expansion, that is covered next.
5482 if (arguments)
5483 addInputArgumentConversions(*fnCandidate, arguments);
5484
5485 // Expand arguments. Some arguments must physically expand to a different set
5486 // than what the shader declared and passes.
5487 if (arguments && !builtIn)
5488 expandArguments(loc, *fnCandidate, arguments);
5489
5490 // Expansion may have changed the form of arguments
5491 aggregate = arguments ? arguments->getAsAggregate() : nullptr;
5492
5493 op = fnCandidate->getBuiltInOp();
5494 if (builtIn && op != EOpNull) {
5495 // A function call mapped to a built-in operation.
5496 result = intermediate.addBuiltInFunctionCall(loc, op, fnCandidate->getParamCount() == 1, arguments,
5497 fnCandidate->getType());
5498 if (result == nullptr) {
5499 error(arguments->getLoc(), " wrong operand type", "Internal Error",
5500 "built in unary operator function. Type: %s",
5501 static_cast<TIntermTyped*>(arguments)->getCompleteString().c_str());
5502 } else if (result->getAsOperator()) {
5503 builtInOpCheck(loc, *fnCandidate, *result->getAsOperator());
5504 }
5505 } else {
5506 // This is a function call not mapped to built-in operator.
5507 // It could still be a built-in function, but only if PureOperatorBuiltins == false.
5508 result = intermediate.setAggregateOperator(arguments, EOpFunctionCall, fnCandidate->getType(), loc);
5509 TIntermAggregate* call = result->getAsAggregate();
5510 call->setName(callerName);
5511
5512 // this is how we know whether the given function is a built-in function or a user-defined function
5513 // if builtIn == false, it's a userDefined -> could be an overloaded built-in function also
5514 // if builtIn == true, it's definitely a built-in function with EOpNull
5515 if (! builtIn) {
5516 call->setUserDefined();
5517 intermediate.addToCallGraph(infoSink, currentCaller, callerName);
5518 }
5519 }
5520
5521 // for decompositions, since we want to operate on the function node, not the aggregate holding
5522 // output conversions.
5523 const TIntermTyped* fnNode = result;
5524
5525 decomposeStructBufferMethods(loc, result, arguments); // HLSL->AST struct buffer method decompositions
5526 decomposeIntrinsic(loc, result, arguments); // HLSL->AST intrinsic decompositions
5527 decomposeSampleMethods(loc, result, arguments); // HLSL->AST sample method decompositions
5528 decomposeGeometryMethods(loc, result, arguments); // HLSL->AST geometry method decompositions
5529
5530 // Create the qualifier list, carried in the AST for the call.
5531 // Because some arguments expand to multiple arguments, the qualifier list will
5532 // be longer than the formal parameter list.
5533 if (result == fnNode && result->getAsAggregate()) {
5534 TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
5535 for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
5536 TStorageQualifier qual = (*fnCandidate)[i].type->getQualifier().storage;
5537 if (hasStructBuffCounter(*(*fnCandidate)[i].type)) {
5538 // add buffer and counter buffer argument qualifier
5539 qualifierList.push_back(qual);
5540 qualifierList.push_back(qual);
5541 } else if (shouldFlatten(*(*fnCandidate)[i].type, (*fnCandidate)[i].type->getQualifier().storage,
5542 true)) {
5543 // add structure member expansion
5544 for (int memb = 0; memb < (int)(*fnCandidate)[i].type->getStruct()->size(); ++memb)
5545 qualifierList.push_back(qual);
5546 } else {
5547 // Normal 1:1 case
5548 qualifierList.push_back(qual);
5549 }
5550 }
5551 }
5552
5553 // Convert 'out' arguments. If it was a constant folded built-in, it won't be an aggregate anymore.
5554 // Built-ins with a single argument aren't called with an aggregate, but they also don't have an output.
5555 // Also, build the qualifier list for user function calls, which are always called with an aggregate.
5556 // We don't do this is if there has been a decomposition, which will have added its own conversions
5557 // for output parameters.
5558 if (result == fnNode && result->getAsAggregate())
5559 result = addOutputArgumentConversions(*fnCandidate, *result->getAsOperator());
5560 }
5561 }
5562
5563 // generic error recovery
5564 // TODO: simplification: localize all the error recoveries that look like this, and taking type into account to
5565 // reduce cascades
5566 if (result == nullptr)
5567 result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
5568
5569 return result;
5570 }
5571
5572 // An initial argument list is difficult: it can be null, or a single node,
5573 // or an aggregate if more than one argument. Add one to the front, maintaining
5574 // this lack of uniformity.
pushFrontArguments(TIntermTyped * front,TIntermTyped * & arguments)5575 void HlslParseContext::pushFrontArguments(TIntermTyped* front, TIntermTyped*& arguments)
5576 {
5577 if (arguments == nullptr)
5578 arguments = front;
5579 else if (arguments->getAsAggregate() != nullptr)
5580 arguments->getAsAggregate()->getSequence().insert(arguments->getAsAggregate()->getSequence().begin(), front);
5581 else
5582 arguments = intermediate.growAggregate(front, arguments);
5583 }
5584
5585 //
5586 // HLSL allows mismatched dimensions on vec*mat, mat*vec, vec*vec, and mat*mat. This is a
5587 // situation not well suited to resolution in intrinsic selection, but we can do so here, since we
5588 // can look at both arguments insert explicit shape changes if required.
5589 //
addGenMulArgumentConversion(const TSourceLoc & loc,TFunction & call,TIntermTyped * & args)5590 void HlslParseContext::addGenMulArgumentConversion(const TSourceLoc& loc, TFunction& call, TIntermTyped*& args)
5591 {
5592 TIntermAggregate* argAggregate = args ? args->getAsAggregate() : nullptr;
5593
5594 if (argAggregate == nullptr || argAggregate->getSequence().size() != 2) {
5595 // It really ought to have two arguments.
5596 error(loc, "expected: mul arguments", "", "");
5597 return;
5598 }
5599
5600 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
5601 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
5602
5603 if (arg0->isVector() && arg1->isVector()) {
5604 // For:
5605 // vec * vec: it's handled during intrinsic selection, so while we could do it here,
5606 // we can also ignore it, which is easier.
5607 } else if (arg0->isVector() && arg1->isMatrix()) {
5608 // vec * mat: we clamp the vec if the mat col is smaller, else clamp the mat col.
5609 if (arg0->getVectorSize() < arg1->getMatrixCols()) {
5610 // vec is smaller, so truncate larger mat dimension
5611 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5612 0, arg0->getVectorSize(), arg1->getMatrixRows());
5613 arg1 = addConstructor(loc, arg1, truncType);
5614 } else if (arg0->getVectorSize() > arg1->getMatrixCols()) {
5615 // vec is larger, so truncate vec to mat size
5616 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5617 arg1->getMatrixCols());
5618 arg0 = addConstructor(loc, arg0, truncType);
5619 }
5620 } else if (arg0->isMatrix() && arg1->isVector()) {
5621 // mat * vec: we clamp the vec if the mat col is smaller, else clamp the mat col.
5622 if (arg1->getVectorSize() < arg0->getMatrixRows()) {
5623 // vec is smaller, so truncate larger mat dimension
5624 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5625 0, arg0->getMatrixCols(), arg1->getVectorSize());
5626 arg0 = addConstructor(loc, arg0, truncType);
5627 } else if (arg1->getVectorSize() > arg0->getMatrixRows()) {
5628 // vec is larger, so truncate vec to mat size
5629 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5630 arg0->getMatrixRows());
5631 arg1 = addConstructor(loc, arg1, truncType);
5632 }
5633 } else if (arg0->isMatrix() && arg1->isMatrix()) {
5634 // mat * mat: we clamp the smaller inner dimension to match the other matrix size.
5635 // Remember, HLSL Mrc = GLSL/SPIRV Mcr.
5636 if (arg0->getMatrixRows() > arg1->getMatrixCols()) {
5637 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5638 0, arg0->getMatrixCols(), arg1->getMatrixCols());
5639 arg0 = addConstructor(loc, arg0, truncType);
5640 } else if (arg0->getMatrixRows() < arg1->getMatrixCols()) {
5641 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5642 0, arg0->getMatrixRows(), arg1->getMatrixRows());
5643 arg1 = addConstructor(loc, arg1, truncType);
5644 }
5645 } else {
5646 // It's something with scalars: we'll just leave it alone. Function selection will handle it
5647 // downstream.
5648 }
5649
5650 // Warn if we altered one of the arguments
5651 if (arg0 != argAggregate->getSequence()[0] || arg1 != argAggregate->getSequence()[1])
5652 warn(loc, "mul() matrix size mismatch", "", "");
5653
5654 // Put arguments back. (They might be unchanged, in which case this is harmless).
5655 argAggregate->getSequence()[0] = arg0;
5656 argAggregate->getSequence()[1] = arg1;
5657
5658 call[0].type = &arg0->getWritableType();
5659 call[1].type = &arg1->getWritableType();
5660 }
5661
5662 //
5663 // Add any needed implicit conversions for function-call arguments to input parameters.
5664 //
addInputArgumentConversions(const TFunction & function,TIntermTyped * & arguments)5665 void HlslParseContext::addInputArgumentConversions(const TFunction& function, TIntermTyped*& arguments)
5666 {
5667 TIntermAggregate* aggregate = arguments->getAsAggregate();
5668
5669 // Replace a single argument with a single argument.
5670 const auto setArg = [&](int paramNum, TIntermTyped* arg) {
5671 if (function.getParamCount() == 1)
5672 arguments = arg;
5673 else {
5674 if (aggregate == nullptr)
5675 arguments = arg;
5676 else
5677 aggregate->getSequence()[paramNum] = arg;
5678 }
5679 };
5680
5681 // Process each argument's conversion
5682 for (int param = 0; param < function.getParamCount(); ++param) {
5683 if (! function[param].type->getQualifier().isParamInput())
5684 continue;
5685
5686 // At this early point there is a slight ambiguity between whether an aggregate 'arguments'
5687 // is the single argument itself or its children are the arguments. Only one argument
5688 // means take 'arguments' itself as the one argument.
5689 TIntermTyped* arg = function.getParamCount() == 1
5690 ? arguments->getAsTyped()
5691 : (aggregate ?
5692 aggregate->getSequence()[param]->getAsTyped() :
5693 arguments->getAsTyped());
5694 if (*function[param].type != arg->getType()) {
5695 // In-qualified arguments just need an extra node added above the argument to
5696 // convert to the correct type.
5697 TIntermTyped* convArg = intermediate.addConversion(EOpFunctionCall, *function[param].type, arg);
5698 if (convArg != nullptr)
5699 convArg = intermediate.addUniShapeConversion(EOpFunctionCall, *function[param].type, convArg);
5700 if (convArg != nullptr)
5701 setArg(param, convArg);
5702 else
5703 error(arg->getLoc(), "cannot convert input argument, argument", "", "%d", param);
5704 } else {
5705 if (wasFlattened(arg)) {
5706 // If both formal and calling arg are to be flattened, leave that to argument
5707 // expansion, not conversion.
5708 if (!shouldFlatten(*function[param].type, function[param].type->getQualifier().storage, true)) {
5709 // Will make a two-level subtree.
5710 // The deepest will copy member-by-member to build the structure to pass.
5711 // The level above that will be a two-operand EOpComma sequence that follows the copy by the
5712 // object itself.
5713 TVariable* internalAggregate = makeInternalVariable("aggShadow", *function[param].type);
5714 internalAggregate->getWritableType().getQualifier().makeTemporary();
5715 TIntermSymbol* internalSymbolNode = new TIntermSymbol(internalAggregate->getUniqueId(),
5716 internalAggregate->getName(),
5717 internalAggregate->getType());
5718 internalSymbolNode->setLoc(arg->getLoc());
5719 // This makes the deepest level, the member-wise copy
5720 TIntermAggregate* assignAgg = handleAssign(arg->getLoc(), EOpAssign,
5721 internalSymbolNode, arg)->getAsAggregate();
5722
5723 // Now, pair that with the resulting aggregate.
5724 assignAgg = intermediate.growAggregate(assignAgg, internalSymbolNode, arg->getLoc());
5725 assignAgg->setOperator(EOpComma);
5726 assignAgg->setType(internalAggregate->getType());
5727 setArg(param, assignAgg);
5728 }
5729 }
5730 }
5731 }
5732 }
5733
5734 //
5735 // Add any needed implicit expansion of calling arguments from what the shader listed to what's
5736 // internally needed for the AST (given the constraints downstream).
5737 //
expandArguments(const TSourceLoc & loc,const TFunction & function,TIntermTyped * & arguments)5738 void HlslParseContext::expandArguments(const TSourceLoc& loc, const TFunction& function, TIntermTyped*& arguments)
5739 {
5740 TIntermAggregate* aggregate = arguments->getAsAggregate();
5741 int functionParamNumberOffset = 0;
5742
5743 // Replace a single argument with a single argument.
5744 const auto setArg = [&](int paramNum, TIntermTyped* arg) {
5745 if (function.getParamCount() + functionParamNumberOffset == 1)
5746 arguments = arg;
5747 else {
5748 if (aggregate == nullptr)
5749 arguments = arg;
5750 else
5751 aggregate->getSequence()[paramNum] = arg;
5752 }
5753 };
5754
5755 // Replace a single argument with a list of arguments
5756 const auto setArgList = [&](int paramNum, const TVector<TIntermTyped*>& args) {
5757 if (args.size() == 1)
5758 setArg(paramNum, args.front());
5759 else if (args.size() > 1) {
5760 if (function.getParamCount() + functionParamNumberOffset == 1) {
5761 arguments = intermediate.makeAggregate(args.front());
5762 std::for_each(args.begin() + 1, args.end(),
5763 [&](TIntermTyped* arg) {
5764 arguments = intermediate.growAggregate(arguments, arg);
5765 });
5766 } else {
5767 auto it = aggregate->getSequence().erase(aggregate->getSequence().begin() + paramNum);
5768 aggregate->getSequence().insert(it, args.begin(), args.end());
5769 }
5770 functionParamNumberOffset += (int)(args.size() - 1);
5771 }
5772 };
5773
5774 // Process each argument's conversion
5775 for (int param = 0; param < function.getParamCount(); ++param) {
5776 // At this early point there is a slight ambiguity between whether an aggregate 'arguments'
5777 // is the single argument itself or its children are the arguments. Only one argument
5778 // means take 'arguments' itself as the one argument.
5779 TIntermTyped* arg = function.getParamCount() == 1
5780 ? arguments->getAsTyped()
5781 : (aggregate ?
5782 aggregate->getSequence()[param + functionParamNumberOffset]->getAsTyped() :
5783 arguments->getAsTyped());
5784
5785 if (wasFlattened(arg) && shouldFlatten(*function[param].type, function[param].type->getQualifier().storage, true)) {
5786 // Need to pass the structure members instead of the structure.
5787 TVector<TIntermTyped*> memberArgs;
5788 for (int memb = 0; memb < (int)arg->getType().getStruct()->size(); ++memb)
5789 memberArgs.push_back(flattenAccess(arg, memb));
5790 setArgList(param + functionParamNumberOffset, memberArgs);
5791 }
5792 }
5793
5794 // TODO: if we need both hidden counter args (below) and struct expansion (above)
5795 // the two algorithms need to be merged: Each assumes the list starts out 1:1 between
5796 // parameters and arguments.
5797
5798 // If any argument is a pass-by-reference struct buffer with an associated counter
5799 // buffer, we have to add another hidden parameter for that counter.
5800 if (aggregate)
5801 addStructBuffArguments(loc, aggregate);
5802 }
5803
5804 //
5805 // Add any needed implicit output conversions for function-call arguments. This
5806 // can require a new tree topology, complicated further by whether the function
5807 // has a return value.
5808 //
5809 // Returns a node of a subtree that evaluates to the return value of the function.
5810 //
addOutputArgumentConversions(const TFunction & function,TIntermOperator & intermNode)5811 TIntermTyped* HlslParseContext::addOutputArgumentConversions(const TFunction& function, TIntermOperator& intermNode)
5812 {
5813 assert (intermNode.getAsAggregate() != nullptr || intermNode.getAsUnaryNode() != nullptr);
5814
5815 const TSourceLoc& loc = intermNode.getLoc();
5816
5817 TIntermSequence argSequence; // temp sequence for unary node args
5818
5819 if (intermNode.getAsUnaryNode())
5820 argSequence.push_back(intermNode.getAsUnaryNode()->getOperand());
5821
5822 TIntermSequence& arguments = argSequence.empty() ? intermNode.getAsAggregate()->getSequence() : argSequence;
5823
5824 const auto needsConversion = [&](int argNum) {
5825 return function[argNum].type->getQualifier().isParamOutput() &&
5826 (*function[argNum].type != arguments[argNum]->getAsTyped()->getType() ||
5827 shouldConvertLValue(arguments[argNum]) ||
5828 wasFlattened(arguments[argNum]->getAsTyped()));
5829 };
5830
5831 // Will there be any output conversions?
5832 bool outputConversions = false;
5833 for (int i = 0; i < function.getParamCount(); ++i) {
5834 if (needsConversion(i)) {
5835 outputConversions = true;
5836 break;
5837 }
5838 }
5839
5840 if (! outputConversions)
5841 return &intermNode;
5842
5843 // Setup for the new tree, if needed:
5844 //
5845 // Output conversions need a different tree topology.
5846 // Out-qualified arguments need a temporary of the correct type, with the call
5847 // followed by an assignment of the temporary to the original argument:
5848 // void: function(arg, ...) -> ( function(tempArg, ...), arg = tempArg, ...)
5849 // ret = function(arg, ...) -> ret = (tempRet = function(tempArg, ...), arg = tempArg, ..., tempRet)
5850 // Where the "tempArg" type needs no conversion as an argument, but will convert on assignment.
5851 TIntermTyped* conversionTree = nullptr;
5852 TVariable* tempRet = nullptr;
5853 if (intermNode.getBasicType() != EbtVoid) {
5854 // do the "tempRet = function(...), " bit from above
5855 tempRet = makeInternalVariable("tempReturn", intermNode.getType());
5856 TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, loc);
5857 conversionTree = intermediate.addAssign(EOpAssign, tempRetNode, &intermNode, loc);
5858 } else
5859 conversionTree = &intermNode;
5860
5861 conversionTree = intermediate.makeAggregate(conversionTree);
5862
5863 // Process each argument's conversion
5864 for (int i = 0; i < function.getParamCount(); ++i) {
5865 if (needsConversion(i)) {
5866 // Out-qualified arguments needing conversion need to use the topology setup above.
5867 // Do the " ...(tempArg, ...), arg = tempArg" bit from above.
5868
5869 // Make a temporary for what the function expects the argument to look like.
5870 TVariable* tempArg = makeInternalVariable("tempArg", *function[i].type);
5871 tempArg->getWritableType().getQualifier().makeTemporary();
5872 TIntermSymbol* tempArgNode = intermediate.addSymbol(*tempArg, loc);
5873
5874 // This makes the deepest level, the member-wise copy
5875 TIntermTyped* tempAssign = handleAssign(arguments[i]->getLoc(), EOpAssign, arguments[i]->getAsTyped(),
5876 tempArgNode);
5877 tempAssign = handleLvalue(arguments[i]->getLoc(), "assign", tempAssign);
5878 conversionTree = intermediate.growAggregate(conversionTree, tempAssign, arguments[i]->getLoc());
5879
5880 // replace the argument with another node for the same tempArg variable
5881 arguments[i] = intermediate.addSymbol(*tempArg, loc);
5882 }
5883 }
5884
5885 // Finalize the tree topology (see bigger comment above).
5886 if (tempRet) {
5887 // do the "..., tempRet" bit from above
5888 TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, loc);
5889 conversionTree = intermediate.growAggregate(conversionTree, tempRetNode, loc);
5890 }
5891
5892 conversionTree = intermediate.setAggregateOperator(conversionTree, EOpComma, intermNode.getType(), loc);
5893
5894 return conversionTree;
5895 }
5896
5897 //
5898 // Add any needed "hidden" counter buffer arguments for function calls.
5899 //
5900 // Modifies the 'aggregate' argument if needed. Otherwise, is no-op.
5901 //
addStructBuffArguments(const TSourceLoc & loc,TIntermAggregate * & aggregate)5902 void HlslParseContext::addStructBuffArguments(const TSourceLoc& loc, TIntermAggregate*& aggregate)
5903 {
5904 // See if there are any SB types with counters.
5905 const bool hasStructBuffArg =
5906 std::any_of(aggregate->getSequence().begin(),
5907 aggregate->getSequence().end(),
5908 [this](const TIntermNode* node) {
5909 return (node && node->getAsTyped() != nullptr) && hasStructBuffCounter(node->getAsTyped()->getType());
5910 });
5911
5912 // Nothing to do, if we didn't find one.
5913 if (! hasStructBuffArg)
5914 return;
5915
5916 TIntermSequence argsWithCounterBuffers;
5917
5918 for (int param = 0; param < int(aggregate->getSequence().size()); ++param) {
5919 argsWithCounterBuffers.push_back(aggregate->getSequence()[param]);
5920
5921 if (hasStructBuffCounter(aggregate->getSequence()[param]->getAsTyped()->getType())) {
5922 const TIntermSymbol* blockSym = aggregate->getSequence()[param]->getAsSymbolNode();
5923 if (blockSym != nullptr) {
5924 TType counterType;
5925 counterBufferType(loc, counterType);
5926
5927 const TString counterBlockName(intermediate.addCounterBufferName(blockSym->getName()));
5928
5929 TVariable* variable = makeInternalVariable(counterBlockName, counterType);
5930
5931 // Mark this buffer's counter block as being in use
5932 structBufferCounter[counterBlockName] = true;
5933
5934 TIntermSymbol* sym = intermediate.addSymbol(*variable, loc);
5935 argsWithCounterBuffers.push_back(sym);
5936 }
5937 }
5938 }
5939
5940 // Swap with the temp list we've built up.
5941 aggregate->getSequence().swap(argsWithCounterBuffers);
5942 }
5943
5944
5945 //
5946 // Do additional checking of built-in function calls that is not caught
5947 // by normal semantic checks on argument type, extension tagging, etc.
5948 //
5949 // Assumes there has been a semantically correct match to a built-in function prototype.
5950 //
builtInOpCheck(const TSourceLoc & loc,const TFunction & fnCandidate,TIntermOperator & callNode)5951 void HlslParseContext::builtInOpCheck(const TSourceLoc& loc, const TFunction& fnCandidate, TIntermOperator& callNode)
5952 {
5953 // Set up convenience accessors to the argument(s). There is almost always
5954 // multiple arguments for the cases below, but when there might be one,
5955 // check the unaryArg first.
5956 const TIntermSequence* argp = nullptr; // confusing to use [] syntax on a pointer, so this is to help get a reference
5957 const TIntermTyped* unaryArg = nullptr;
5958 const TIntermTyped* arg0 = nullptr;
5959 if (callNode.getAsAggregate()) {
5960 argp = &callNode.getAsAggregate()->getSequence();
5961 if (argp->size() > 0)
5962 arg0 = (*argp)[0]->getAsTyped();
5963 } else {
5964 assert(callNode.getAsUnaryNode());
5965 unaryArg = callNode.getAsUnaryNode()->getOperand();
5966 arg0 = unaryArg;
5967 }
5968 const TIntermSequence& aggArgs = *argp; // only valid when unaryArg is nullptr
5969
5970 switch (callNode.getOp()) {
5971 case EOpTextureGather:
5972 case EOpTextureGatherOffset:
5973 case EOpTextureGatherOffsets:
5974 {
5975 // Figure out which variants are allowed by what extensions,
5976 // and what arguments must be constant for which situations.
5977
5978 TString featureString = fnCandidate.getName() + "(...)";
5979 const char* feature = featureString.c_str();
5980 int compArg = -1; // track which argument, if any, is the constant component argument
5981 switch (callNode.getOp()) {
5982 case EOpTextureGather:
5983 // More than two arguments needs gpu_shader5, and rectangular or shadow needs gpu_shader5,
5984 // otherwise, need GL_ARB_texture_gather.
5985 if (fnCandidate.getParamCount() > 2 || fnCandidate[0].type->getSampler().dim == EsdRect ||
5986 fnCandidate[0].type->getSampler().shadow) {
5987 if (! fnCandidate[0].type->getSampler().shadow)
5988 compArg = 2;
5989 }
5990 break;
5991 case EOpTextureGatherOffset:
5992 // GL_ARB_texture_gather is good enough for 2D non-shadow textures with no component argument
5993 if (! fnCandidate[0].type->getSampler().shadow)
5994 compArg = 3;
5995 break;
5996 case EOpTextureGatherOffsets:
5997 if (! fnCandidate[0].type->getSampler().shadow)
5998 compArg = 3;
5999 break;
6000 default:
6001 break;
6002 }
6003
6004 if (compArg > 0 && compArg < fnCandidate.getParamCount()) {
6005 if (aggArgs[compArg]->getAsConstantUnion()) {
6006 int value = aggArgs[compArg]->getAsConstantUnion()->getConstArray()[0].getIConst();
6007 if (value < 0 || value > 3)
6008 error(loc, "must be 0, 1, 2, or 3:", feature, "component argument");
6009 } else
6010 error(loc, "must be a compile-time constant:", feature, "component argument");
6011 }
6012
6013 break;
6014 }
6015
6016 case EOpTextureOffset:
6017 case EOpTextureFetchOffset:
6018 case EOpTextureProjOffset:
6019 case EOpTextureLodOffset:
6020 case EOpTextureProjLodOffset:
6021 case EOpTextureGradOffset:
6022 case EOpTextureProjGradOffset:
6023 {
6024 // Handle texture-offset limits checking
6025 // Pick which argument has to hold constant offsets
6026 int arg = -1;
6027 switch (callNode.getOp()) {
6028 case EOpTextureOffset: arg = 2; break;
6029 case EOpTextureFetchOffset: arg = (arg0->getType().getSampler().dim != EsdRect) ? 3 : 2; break;
6030 case EOpTextureProjOffset: arg = 2; break;
6031 case EOpTextureLodOffset: arg = 3; break;
6032 case EOpTextureProjLodOffset: arg = 3; break;
6033 case EOpTextureGradOffset: arg = 4; break;
6034 case EOpTextureProjGradOffset: arg = 4; break;
6035 default:
6036 assert(0);
6037 break;
6038 }
6039
6040 if (arg > 0) {
6041 if (aggArgs[arg]->getAsConstantUnion() == nullptr)
6042 error(loc, "argument must be compile-time constant", "texel offset", "");
6043 else {
6044 const TType& type = aggArgs[arg]->getAsTyped()->getType();
6045 for (int c = 0; c < type.getVectorSize(); ++c) {
6046 int offset = aggArgs[arg]->getAsConstantUnion()->getConstArray()[c].getIConst();
6047 if (offset > resources.maxProgramTexelOffset || offset < resources.minProgramTexelOffset)
6048 error(loc, "value is out of range:", "texel offset",
6049 "[gl_MinProgramTexelOffset, gl_MaxProgramTexelOffset]");
6050 }
6051 }
6052 }
6053
6054 break;
6055 }
6056
6057 case EOpTextureQuerySamples:
6058 case EOpImageQuerySamples:
6059 break;
6060
6061 case EOpImageAtomicAdd:
6062 case EOpImageAtomicMin:
6063 case EOpImageAtomicMax:
6064 case EOpImageAtomicAnd:
6065 case EOpImageAtomicOr:
6066 case EOpImageAtomicXor:
6067 case EOpImageAtomicExchange:
6068 case EOpImageAtomicCompSwap:
6069 break;
6070
6071 case EOpInterpolateAtCentroid:
6072 case EOpInterpolateAtSample:
6073 case EOpInterpolateAtOffset:
6074 // Make sure the first argument is an interpolant, or an array element of an interpolant
6075 if (arg0->getType().getQualifier().storage != EvqVaryingIn) {
6076 // It might still be an array element.
6077 //
6078 // We could check more, but the semantics of the first argument are already met; the
6079 // only way to turn an array into a float/vec* is array dereference and swizzle.
6080 //
6081 // ES and desktop 4.3 and earlier: swizzles may not be used
6082 // desktop 4.4 and later: swizzles may be used
6083 const TIntermTyped* base = TIntermediate::findLValueBase(arg0, true);
6084 if (base == nullptr || base->getType().getQualifier().storage != EvqVaryingIn)
6085 error(loc, "first argument must be an interpolant, or interpolant-array element",
6086 fnCandidate.getName().c_str(), "");
6087 }
6088 break;
6089
6090 default:
6091 break;
6092 }
6093 }
6094
6095 //
6096 // Handle seeing something in a grammar production that can be done by calling
6097 // a constructor.
6098 //
6099 // The constructor still must be "handled" by handleFunctionCall(), which will
6100 // then call handleConstructor().
6101 //
makeConstructorCall(const TSourceLoc & loc,const TType & type)6102 TFunction* HlslParseContext::makeConstructorCall(const TSourceLoc& loc, const TType& type)
6103 {
6104 TOperator op = intermediate.mapTypeToConstructorOp(type);
6105
6106 if (op == EOpNull) {
6107 error(loc, "cannot construct this type", type.getBasicString(), "");
6108 return nullptr;
6109 }
6110
6111 TString empty("");
6112
6113 return new TFunction(&empty, type, op);
6114 }
6115
6116 //
6117 // Handle seeing a "COLON semantic" at the end of a type declaration,
6118 // by updating the type according to the semantic.
6119 //
handleSemantic(TSourceLoc loc,TQualifier & qualifier,TBuiltInVariable builtIn,const TString & upperCase)6120 void HlslParseContext::handleSemantic(TSourceLoc loc, TQualifier& qualifier, TBuiltInVariable builtIn,
6121 const TString& upperCase)
6122 {
6123 // Parse and return semantic number. If limit is 0, it will be ignored. Otherwise, if the parsed
6124 // semantic number is >= limit, errorMsg is issued and 0 is returned.
6125 // TODO: it would be nicer if limit and errorMsg had default parameters, but some compilers don't yet
6126 // accept those in lambda functions.
6127 const auto getSemanticNumber = [this, loc](const TString& semantic, unsigned int limit, const char* errorMsg) -> unsigned int {
6128 size_t pos = semantic.find_last_not_of("0123456789");
6129 if (pos == std::string::npos)
6130 return 0u;
6131
6132 unsigned int semanticNum = (unsigned int)atoi(semantic.c_str() + pos + 1);
6133
6134 if (limit != 0 && semanticNum >= limit) {
6135 error(loc, errorMsg, semantic.c_str(), "");
6136 return 0u;
6137 }
6138
6139 return semanticNum;
6140 };
6141
6142 if (builtIn == EbvNone && hlslDX9Compatible()) {
6143 if (language == EShLangVertex) {
6144 if (qualifier.isParamOutput()) {
6145 if (upperCase == "POSITION") {
6146 builtIn = EbvPosition;
6147 }
6148 if (upperCase == "PSIZE") {
6149 builtIn = EbvPointSize;
6150 }
6151 }
6152 } else if (language == EShLangFragment) {
6153 if (qualifier.isParamInput() && upperCase == "VPOS") {
6154 builtIn = EbvFragCoord;
6155 }
6156 if (qualifier.isParamOutput()) {
6157 if (upperCase.compare(0, 5, "COLOR") == 0) {
6158 qualifier.layoutLocation = getSemanticNumber(upperCase, 0, nullptr);
6159 nextOutLocation = std::max(nextOutLocation, qualifier.layoutLocation + 1u);
6160 }
6161 if (upperCase == "DEPTH") {
6162 builtIn = EbvFragDepth;
6163 }
6164 }
6165 }
6166 }
6167
6168 switch(builtIn) {
6169 case EbvNone:
6170 // Get location numbers from fragment outputs, instead of
6171 // auto-assigning them.
6172 if (language == EShLangFragment && upperCase.compare(0, 9, "SV_TARGET") == 0) {
6173 qualifier.layoutLocation = getSemanticNumber(upperCase, 0, nullptr);
6174 nextOutLocation = std::max(nextOutLocation, qualifier.layoutLocation + 1u);
6175 } else if (upperCase.compare(0, 15, "SV_CLIPDISTANCE") == 0) {
6176 builtIn = EbvClipDistance;
6177 qualifier.layoutLocation = getSemanticNumber(upperCase, maxClipCullRegs, "invalid clip semantic");
6178 } else if (upperCase.compare(0, 15, "SV_CULLDISTANCE") == 0) {
6179 builtIn = EbvCullDistance;
6180 qualifier.layoutLocation = getSemanticNumber(upperCase, maxClipCullRegs, "invalid cull semantic");
6181 }
6182 break;
6183 case EbvPosition:
6184 // adjust for stage in/out
6185 if (language == EShLangFragment)
6186 builtIn = EbvFragCoord;
6187 break;
6188 case EbvFragStencilRef:
6189 error(loc, "unimplemented; need ARB_shader_stencil_export", "SV_STENCILREF", "");
6190 break;
6191 case EbvTessLevelInner:
6192 case EbvTessLevelOuter:
6193 qualifier.patch = true;
6194 break;
6195 default:
6196 break;
6197 }
6198
6199 if (qualifier.builtIn == EbvNone)
6200 qualifier.builtIn = builtIn;
6201 qualifier.semanticName = intermediate.addSemanticName(upperCase);
6202 }
6203
6204 //
6205 // Handle seeing something like "PACKOFFSET LEFT_PAREN c[Subcomponent][.component] RIGHT_PAREN"
6206 //
6207 // 'location' has the "c[Subcomponent]" part.
6208 // 'component' points to the "component" part, or nullptr if not present.
6209 //
handlePackOffset(const TSourceLoc & loc,TQualifier & qualifier,const glslang::TString & location,const glslang::TString * component)6210 void HlslParseContext::handlePackOffset(const TSourceLoc& loc, TQualifier& qualifier, const glslang::TString& location,
6211 const glslang::TString* component)
6212 {
6213 if (location.size() == 0 || location[0] != 'c') {
6214 error(loc, "expected 'c'", "packoffset", "");
6215 return;
6216 }
6217 if (location.size() == 1)
6218 return;
6219 if (! isdigit(location[1])) {
6220 error(loc, "expected number after 'c'", "packoffset", "");
6221 return;
6222 }
6223
6224 qualifier.layoutOffset = 16 * atoi(location.substr(1, location.size()).c_str());
6225 if (component != nullptr) {
6226 int componentOffset = 0;
6227 switch ((*component)[0]) {
6228 case 'x': componentOffset = 0; break;
6229 case 'y': componentOffset = 4; break;
6230 case 'z': componentOffset = 8; break;
6231 case 'w': componentOffset = 12; break;
6232 default:
6233 componentOffset = -1;
6234 break;
6235 }
6236 if (componentOffset < 0 || component->size() > 1) {
6237 error(loc, "expected {x, y, z, w} for component", "packoffset", "");
6238 return;
6239 }
6240 qualifier.layoutOffset += componentOffset;
6241 }
6242 }
6243
6244 //
6245 // Handle seeing something like "REGISTER LEFT_PAREN [shader_profile,] Type# RIGHT_PAREN"
6246 //
6247 // 'profile' points to the shader_profile part, or nullptr if not present.
6248 // 'desc' is the type# part.
6249 //
handleRegister(const TSourceLoc & loc,TQualifier & qualifier,const glslang::TString * profile,const glslang::TString & desc,int subComponent,const glslang::TString * spaceDesc)6250 void HlslParseContext::handleRegister(const TSourceLoc& loc, TQualifier& qualifier, const glslang::TString* profile,
6251 const glslang::TString& desc, int subComponent, const glslang::TString* spaceDesc)
6252 {
6253 if (profile != nullptr)
6254 warn(loc, "ignoring shader_profile", "register", "");
6255
6256 if (desc.size() < 1) {
6257 error(loc, "expected register type", "register", "");
6258 return;
6259 }
6260
6261 int regNumber = 0;
6262 if (desc.size() > 1) {
6263 if (isdigit(desc[1]))
6264 regNumber = atoi(desc.substr(1, desc.size()).c_str());
6265 else {
6266 error(loc, "expected register number after register type", "register", "");
6267 return;
6268 }
6269 }
6270
6271 // more information about register types see
6272 // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-register
6273 const std::vector<std::string>& resourceInfo = intermediate.getResourceSetBinding();
6274 switch (std::tolower(desc[0])) {
6275 case 'c':
6276 // c register is the register slot in the global const buffer
6277 // each slot is a vector of 4 32 bit components
6278 qualifier.layoutOffset = regNumber * 4 * 4;
6279 break;
6280 // const buffer register slot
6281 case 'b':
6282 // textrues and structured buffers
6283 case 't':
6284 // samplers
6285 case 's':
6286 // uav resources
6287 case 'u':
6288 // if nothing else has set the binding, do so now
6289 // (other mechanisms override this one)
6290 if (!qualifier.hasBinding())
6291 qualifier.layoutBinding = regNumber + subComponent;
6292
6293 // This handles per-register layout sets numbers. For the global mode which sets
6294 // every symbol to the same value, see setLinkageLayoutSets().
6295 if ((resourceInfo.size() % 3) == 0) {
6296 // Apply per-symbol resource set and binding.
6297 for (auto it = resourceInfo.cbegin(); it != resourceInfo.cend(); it = it + 3) {
6298 if (strcmp(desc.c_str(), it[0].c_str()) == 0) {
6299 qualifier.layoutSet = atoi(it[1].c_str());
6300 qualifier.layoutBinding = atoi(it[2].c_str()) + subComponent;
6301 break;
6302 }
6303 }
6304 }
6305 break;
6306 default:
6307 warn(loc, "ignoring unrecognized register type", "register", "%c", desc[0]);
6308 break;
6309 }
6310
6311 // space
6312 unsigned int setNumber;
6313 const auto crackSpace = [&]() -> bool {
6314 const int spaceLen = 5;
6315 if (spaceDesc->size() < spaceLen + 1)
6316 return false;
6317 if (spaceDesc->compare(0, spaceLen, "space") != 0)
6318 return false;
6319 if (! isdigit((*spaceDesc)[spaceLen]))
6320 return false;
6321 setNumber = atoi(spaceDesc->substr(spaceLen, spaceDesc->size()).c_str());
6322 return true;
6323 };
6324
6325 // if nothing else has set the set, do so now
6326 // (other mechanisms override this one)
6327 if (spaceDesc && !qualifier.hasSet()) {
6328 if (! crackSpace()) {
6329 error(loc, "expected spaceN", "register", "");
6330 return;
6331 }
6332 qualifier.layoutSet = setNumber;
6333 }
6334 }
6335
6336 // Convert to a scalar boolean, or if not allowed by HLSL semantics,
6337 // report an error and return nullptr.
convertConditionalExpression(const TSourceLoc & loc,TIntermTyped * condition,bool mustBeScalar)6338 TIntermTyped* HlslParseContext::convertConditionalExpression(const TSourceLoc& loc, TIntermTyped* condition,
6339 bool mustBeScalar)
6340 {
6341 if (mustBeScalar && !condition->getType().isScalarOrVec1()) {
6342 error(loc, "requires a scalar", "conditional expression", "");
6343 return nullptr;
6344 }
6345
6346 return intermediate.addConversion(EOpConstructBool, TType(EbtBool, EvqTemporary, condition->getVectorSize()),
6347 condition);
6348 }
6349
6350 //
6351 // Same error message for all places assignments don't work.
6352 //
assignError(const TSourceLoc & loc,const char * op,TString left,TString right)6353 void HlslParseContext::assignError(const TSourceLoc& loc, const char* op, TString left, TString right)
6354 {
6355 error(loc, "", op, "cannot convert from '%s' to '%s'",
6356 right.c_str(), left.c_str());
6357 }
6358
6359 //
6360 // Same error message for all places unary operations don't work.
6361 //
unaryOpError(const TSourceLoc & loc,const char * op,TString operand)6362 void HlslParseContext::unaryOpError(const TSourceLoc& loc, const char* op, TString operand)
6363 {
6364 error(loc, " wrong operand type", op,
6365 "no operation '%s' exists that takes an operand of type %s (or there is no acceptable conversion)",
6366 op, operand.c_str());
6367 }
6368
6369 //
6370 // Same error message for all binary operations don't work.
6371 //
binaryOpError(const TSourceLoc & loc,const char * op,TString left,TString right)6372 void HlslParseContext::binaryOpError(const TSourceLoc& loc, const char* op, TString left, TString right)
6373 {
6374 error(loc, " wrong operand types:", op,
6375 "no operation '%s' exists that takes a left-hand operand of type '%s' and "
6376 "a right operand of type '%s' (or there is no acceptable conversion)",
6377 op, left.c_str(), right.c_str());
6378 }
6379
6380 //
6381 // A basic type of EbtVoid is a key that the name string was seen in the source, but
6382 // it was not found as a variable in the symbol table. If so, give the error
6383 // message and insert a dummy variable in the symbol table to prevent future errors.
6384 //
variableCheck(TIntermTyped * & nodePtr)6385 void HlslParseContext::variableCheck(TIntermTyped*& nodePtr)
6386 {
6387 TIntermSymbol* symbol = nodePtr->getAsSymbolNode();
6388 if (! symbol)
6389 return;
6390
6391 if (symbol->getType().getBasicType() == EbtVoid) {
6392 error(symbol->getLoc(), "undeclared identifier", symbol->getName().c_str(), "");
6393
6394 // Add to symbol table to prevent future error messages on the same name
6395 if (symbol->getName().size() > 0) {
6396 TVariable* fakeVariable = new TVariable(&symbol->getName(), TType(EbtFloat));
6397 symbolTable.insert(*fakeVariable);
6398
6399 // substitute a symbol node for this new variable
6400 nodePtr = intermediate.addSymbol(*fakeVariable, symbol->getLoc());
6401 }
6402 }
6403 }
6404
6405 //
6406 // Both test, and if necessary spit out an error, to see if the node is really
6407 // a constant.
6408 //
constantValueCheck(TIntermTyped * node,const char * token)6409 void HlslParseContext::constantValueCheck(TIntermTyped* node, const char* token)
6410 {
6411 if (node->getQualifier().storage != EvqConst)
6412 error(node->getLoc(), "constant expression required", token, "");
6413 }
6414
6415 //
6416 // Both test, and if necessary spit out an error, to see if the node is really
6417 // an integer.
6418 //
integerCheck(const TIntermTyped * node,const char * token)6419 void HlslParseContext::integerCheck(const TIntermTyped* node, const char* token)
6420 {
6421 if ((node->getBasicType() == EbtInt || node->getBasicType() == EbtUint) && node->isScalar())
6422 return;
6423
6424 error(node->getLoc(), "scalar integer expression required", token, "");
6425 }
6426
6427 //
6428 // Both test, and if necessary spit out an error, to see if we are currently
6429 // globally scoped.
6430 //
globalCheck(const TSourceLoc & loc,const char * token)6431 void HlslParseContext::globalCheck(const TSourceLoc& loc, const char* token)
6432 {
6433 if (! symbolTable.atGlobalLevel())
6434 error(loc, "not allowed in nested scope", token, "");
6435 }
6436
builtInName(const TString &)6437 bool HlslParseContext::builtInName(const TString& /*identifier*/)
6438 {
6439 return false;
6440 }
6441
6442 //
6443 // Make sure there is enough data and not too many arguments provided to the
6444 // constructor to build something of the type of the constructor. Also returns
6445 // the type of the constructor.
6446 //
6447 // Returns true if there was an error in construction.
6448 //
constructorError(const TSourceLoc & loc,TIntermNode * node,TFunction & function,TOperator op,TType & type)6449 bool HlslParseContext::constructorError(const TSourceLoc& loc, TIntermNode* node, TFunction& function,
6450 TOperator op, TType& type)
6451 {
6452 type.shallowCopy(function.getType());
6453
6454 bool constructingMatrix = false;
6455 switch (op) {
6456 case EOpConstructTextureSampler:
6457 error(loc, "unhandled texture constructor", "constructor", "");
6458 return true;
6459 case EOpConstructMat2x2:
6460 case EOpConstructMat2x3:
6461 case EOpConstructMat2x4:
6462 case EOpConstructMat3x2:
6463 case EOpConstructMat3x3:
6464 case EOpConstructMat3x4:
6465 case EOpConstructMat4x2:
6466 case EOpConstructMat4x3:
6467 case EOpConstructMat4x4:
6468 case EOpConstructDMat2x2:
6469 case EOpConstructDMat2x3:
6470 case EOpConstructDMat2x4:
6471 case EOpConstructDMat3x2:
6472 case EOpConstructDMat3x3:
6473 case EOpConstructDMat3x4:
6474 case EOpConstructDMat4x2:
6475 case EOpConstructDMat4x3:
6476 case EOpConstructDMat4x4:
6477 case EOpConstructIMat2x2:
6478 case EOpConstructIMat2x3:
6479 case EOpConstructIMat2x4:
6480 case EOpConstructIMat3x2:
6481 case EOpConstructIMat3x3:
6482 case EOpConstructIMat3x4:
6483 case EOpConstructIMat4x2:
6484 case EOpConstructIMat4x3:
6485 case EOpConstructIMat4x4:
6486 case EOpConstructUMat2x2:
6487 case EOpConstructUMat2x3:
6488 case EOpConstructUMat2x4:
6489 case EOpConstructUMat3x2:
6490 case EOpConstructUMat3x3:
6491 case EOpConstructUMat3x4:
6492 case EOpConstructUMat4x2:
6493 case EOpConstructUMat4x3:
6494 case EOpConstructUMat4x4:
6495 case EOpConstructBMat2x2:
6496 case EOpConstructBMat2x3:
6497 case EOpConstructBMat2x4:
6498 case EOpConstructBMat3x2:
6499 case EOpConstructBMat3x3:
6500 case EOpConstructBMat3x4:
6501 case EOpConstructBMat4x2:
6502 case EOpConstructBMat4x3:
6503 case EOpConstructBMat4x4:
6504 constructingMatrix = true;
6505 break;
6506 default:
6507 break;
6508 }
6509
6510 //
6511 // Walk the arguments for first-pass checks and collection of information.
6512 //
6513
6514 int size = 0;
6515 bool constType = true;
6516 bool full = false;
6517 bool overFull = false;
6518 bool matrixInMatrix = false;
6519 bool arrayArg = false;
6520 for (int arg = 0; arg < function.getParamCount(); ++arg) {
6521 if (function[arg].type->isArray()) {
6522 if (function[arg].type->isUnsizedArray()) {
6523 // Can't construct from an unsized array.
6524 error(loc, "array argument must be sized", "constructor", "");
6525 return true;
6526 }
6527 arrayArg = true;
6528 }
6529 if (constructingMatrix && function[arg].type->isMatrix())
6530 matrixInMatrix = true;
6531
6532 // 'full' will go to true when enough args have been seen. If we loop
6533 // again, there is an extra argument.
6534 if (full) {
6535 // For vectors and matrices, it's okay to have too many components
6536 // available, but not okay to have unused arguments.
6537 overFull = true;
6538 }
6539
6540 size += function[arg].type->computeNumComponents();
6541 if (op != EOpConstructStruct && ! type.isArray() && size >= type.computeNumComponents())
6542 full = true;
6543
6544 if (function[arg].type->getQualifier().storage != EvqConst)
6545 constType = false;
6546 }
6547
6548 if (constType)
6549 type.getQualifier().storage = EvqConst;
6550
6551 if (type.isArray()) {
6552 if (function.getParamCount() == 0) {
6553 error(loc, "array constructor must have at least one argument", "constructor", "");
6554 return true;
6555 }
6556
6557 if (type.isUnsizedArray()) {
6558 // auto adapt the constructor type to the number of arguments
6559 type.changeOuterArraySize(function.getParamCount());
6560 } else if (type.getOuterArraySize() != function.getParamCount() && type.computeNumComponents() > size) {
6561 error(loc, "array constructor needs one argument per array element", "constructor", "");
6562 return true;
6563 }
6564
6565 if (type.isArrayOfArrays()) {
6566 // Types have to match, but we're still making the type.
6567 // Finish making the type, and the comparison is done later
6568 // when checking for conversion.
6569 TArraySizes& arraySizes = *type.getArraySizes();
6570
6571 // At least the dimensionalities have to match.
6572 if (! function[0].type->isArray() ||
6573 arraySizes.getNumDims() != function[0].type->getArraySizes()->getNumDims() + 1) {
6574 error(loc, "array constructor argument not correct type to construct array element", "constructor", "");
6575 return true;
6576 }
6577
6578 if (arraySizes.isInnerUnsized()) {
6579 // "Arrays of arrays ..., and the size for any dimension is optional"
6580 // That means we need to adopt (from the first argument) the other array sizes into the type.
6581 for (int d = 1; d < arraySizes.getNumDims(); ++d) {
6582 if (arraySizes.getDimSize(d) == UnsizedArraySize) {
6583 arraySizes.setDimSize(d, function[0].type->getArraySizes()->getDimSize(d - 1));
6584 }
6585 }
6586 }
6587 }
6588 }
6589
6590 // Some array -> array type casts are okay
6591 if (arrayArg && function.getParamCount() == 1 && op != EOpConstructStruct && type.isArray() &&
6592 !type.isArrayOfArrays() && !function[0].type->isArrayOfArrays() &&
6593 type.getVectorSize() >= 1 && function[0].type->getVectorSize() >= 1)
6594 return false;
6595
6596 if (arrayArg && op != EOpConstructStruct && ! type.isArrayOfArrays()) {
6597 error(loc, "constructing non-array constituent from array argument", "constructor", "");
6598 return true;
6599 }
6600
6601 if (matrixInMatrix && ! type.isArray()) {
6602 return false;
6603 }
6604
6605 if (overFull) {
6606 error(loc, "too many arguments", "constructor", "");
6607 return true;
6608 }
6609
6610 if (op == EOpConstructStruct && ! type.isArray()) {
6611 if (isScalarConstructor(node))
6612 return false;
6613
6614 // Self-type construction: e.g, we can construct a struct from a single identically typed object.
6615 if (function.getParamCount() == 1 && type == *function[0].type)
6616 return false;
6617
6618 if ((int)type.getStruct()->size() != function.getParamCount()) {
6619 error(loc, "Number of constructor parameters does not match the number of structure fields", "constructor", "");
6620 return true;
6621 }
6622 }
6623
6624 if ((op != EOpConstructStruct && size != 1 && size < type.computeNumComponents()) ||
6625 (op == EOpConstructStruct && size < type.computeNumComponents())) {
6626 error(loc, "not enough data provided for construction", "constructor", "");
6627 return true;
6628 }
6629
6630 return false;
6631 }
6632
6633 // See if 'node', in the context of constructing aggregates, is a scalar argument
6634 // to a constructor.
6635 //
isScalarConstructor(const TIntermNode * node)6636 bool HlslParseContext::isScalarConstructor(const TIntermNode* node)
6637 {
6638 // Obviously, it must be a scalar, but an aggregate node might not be fully
6639 // completed yet: holding a sequence of initializers under an aggregate
6640 // would not yet be typed, so don't check it's type. This corresponds to
6641 // the aggregate operator also not being set yet. (An aggregate operation
6642 // that legitimately yields a scalar will have a getOp() of that operator,
6643 // not EOpNull.)
6644
6645 return node->getAsTyped() != nullptr &&
6646 node->getAsTyped()->isScalar() &&
6647 (node->getAsAggregate() == nullptr || node->getAsAggregate()->getOp() != EOpNull);
6648 }
6649
6650 // Checks to see if a void variable has been declared and raise an error message for such a case
6651 //
6652 // returns true in case of an error
6653 //
voidErrorCheck(const TSourceLoc & loc,const TString & identifier,const TBasicType basicType)6654 bool HlslParseContext::voidErrorCheck(const TSourceLoc& loc, const TString& identifier, const TBasicType basicType)
6655 {
6656 if (basicType == EbtVoid) {
6657 error(loc, "illegal use of type 'void'", identifier.c_str(), "");
6658 return true;
6659 }
6660
6661 return false;
6662 }
6663
6664 //
6665 // Fix just a full qualifier (no variables or types yet, but qualifier is complete) at global level.
6666 //
globalQualifierFix(const TSourceLoc &,TQualifier & qualifier)6667 void HlslParseContext::globalQualifierFix(const TSourceLoc&, TQualifier& qualifier)
6668 {
6669 // move from parameter/unknown qualifiers to pipeline in/out qualifiers
6670 switch (qualifier.storage) {
6671 case EvqIn:
6672 qualifier.storage = EvqVaryingIn;
6673 break;
6674 case EvqOut:
6675 qualifier.storage = EvqVaryingOut;
6676 break;
6677 default:
6678 break;
6679 }
6680 }
6681
6682 //
6683 // Merge characteristics of the 'src' qualifier into the 'dst'.
6684 // If there is duplication, issue error messages, unless 'force'
6685 // is specified, which means to just override default settings.
6686 //
6687 // Also, when force is false, it will be assumed that 'src' follows
6688 // 'dst', for the purpose of error checking order for versions
6689 // that require specific orderings of qualifiers.
6690 //
mergeQualifiers(TQualifier & dst,const TQualifier & src)6691 void HlslParseContext::mergeQualifiers(TQualifier& dst, const TQualifier& src)
6692 {
6693 // Storage qualification
6694 if (dst.storage == EvqTemporary || dst.storage == EvqGlobal)
6695 dst.storage = src.storage;
6696 else if ((dst.storage == EvqIn && src.storage == EvqOut) ||
6697 (dst.storage == EvqOut && src.storage == EvqIn))
6698 dst.storage = EvqInOut;
6699 else if ((dst.storage == EvqIn && src.storage == EvqConst) ||
6700 (dst.storage == EvqConst && src.storage == EvqIn))
6701 dst.storage = EvqConstReadOnly;
6702
6703 // Layout qualifiers
6704 mergeObjectLayoutQualifiers(dst, src, false);
6705
6706 // individual qualifiers
6707 bool repeated = false;
6708 #define MERGE_SINGLETON(field) repeated |= dst.field && src.field; dst.field |= src.field;
6709 MERGE_SINGLETON(invariant);
6710 MERGE_SINGLETON(noContraction);
6711 MERGE_SINGLETON(centroid);
6712 MERGE_SINGLETON(smooth);
6713 MERGE_SINGLETON(flat);
6714 MERGE_SINGLETON(nopersp);
6715 MERGE_SINGLETON(patch);
6716 MERGE_SINGLETON(sample);
6717 MERGE_SINGLETON(coherent);
6718 MERGE_SINGLETON(volatil);
6719 MERGE_SINGLETON(restrict);
6720 MERGE_SINGLETON(readonly);
6721 MERGE_SINGLETON(writeonly);
6722 MERGE_SINGLETON(specConstant);
6723 MERGE_SINGLETON(nonUniform);
6724 }
6725
6726 // used to flatten the sampler type space into a single dimension
6727 // correlates with the declaration of defaultSamplerPrecision[]
computeSamplerTypeIndex(TSampler & sampler)6728 int HlslParseContext::computeSamplerTypeIndex(TSampler& sampler)
6729 {
6730 int arrayIndex = sampler.arrayed ? 1 : 0;
6731 int shadowIndex = sampler.shadow ? 1 : 0;
6732 int externalIndex = sampler.external ? 1 : 0;
6733
6734 return EsdNumDims *
6735 (EbtNumTypes * (2 * (2 * arrayIndex + shadowIndex) + externalIndex) + sampler.type) + sampler.dim;
6736 }
6737
6738 //
6739 // Do size checking for an array type's size.
6740 //
arraySizeCheck(const TSourceLoc & loc,TIntermTyped * expr,TArraySize & sizePair)6741 void HlslParseContext::arraySizeCheck(const TSourceLoc& loc, TIntermTyped* expr, TArraySize& sizePair)
6742 {
6743 bool isConst = false;
6744 sizePair.size = 1;
6745 sizePair.node = nullptr;
6746
6747 TIntermConstantUnion* constant = expr->getAsConstantUnion();
6748 if (constant) {
6749 // handle true (non-specialization) constant
6750 sizePair.size = constant->getConstArray()[0].getIConst();
6751 isConst = true;
6752 } else {
6753 // see if it's a specialization constant instead
6754 if (expr->getQualifier().isSpecConstant()) {
6755 isConst = true;
6756 sizePair.node = expr;
6757 TIntermSymbol* symbol = expr->getAsSymbolNode();
6758 if (symbol && symbol->getConstArray().size() > 0)
6759 sizePair.size = symbol->getConstArray()[0].getIConst();
6760 }
6761 }
6762
6763 if (! isConst || (expr->getBasicType() != EbtInt && expr->getBasicType() != EbtUint)) {
6764 error(loc, "array size must be a constant integer expression", "", "");
6765 return;
6766 }
6767
6768 if (sizePair.size <= 0) {
6769 error(loc, "array size must be a positive integer", "", "");
6770 return;
6771 }
6772 }
6773
6774 //
6775 // Require array to be completely sized
6776 //
arraySizeRequiredCheck(const TSourceLoc & loc,const TArraySizes & arraySizes)6777 void HlslParseContext::arraySizeRequiredCheck(const TSourceLoc& loc, const TArraySizes& arraySizes)
6778 {
6779 if (arraySizes.hasUnsized())
6780 error(loc, "array size required", "", "");
6781 }
6782
structArrayCheck(const TSourceLoc &,const TType & type)6783 void HlslParseContext::structArrayCheck(const TSourceLoc& /*loc*/, const TType& type)
6784 {
6785 const TTypeList& structure = *type.getStruct();
6786 for (int m = 0; m < (int)structure.size(); ++m) {
6787 const TType& member = *structure[m].type;
6788 if (member.isArray())
6789 arraySizeRequiredCheck(structure[m].loc, *member.getArraySizes());
6790 }
6791 }
6792
6793 //
6794 // Do all the semantic checking for declaring or redeclaring an array, with and
6795 // without a size, and make the right changes to the symbol table.
6796 //
declareArray(const TSourceLoc & loc,const TString & identifier,const TType & type,TSymbol * & symbol,bool track)6797 void HlslParseContext::declareArray(const TSourceLoc& loc, const TString& identifier, const TType& type,
6798 TSymbol*& symbol, bool track)
6799 {
6800 if (symbol == nullptr) {
6801 bool currentScope;
6802 symbol = symbolTable.find(identifier, nullptr, ¤tScope);
6803
6804 if (symbol && builtInName(identifier) && ! symbolTable.atBuiltInLevel()) {
6805 // bad shader (errors already reported) trying to redeclare a built-in name as an array
6806 return;
6807 }
6808 if (symbol == nullptr || ! currentScope) {
6809 //
6810 // Successfully process a new definition.
6811 // (Redeclarations have to take place at the same scope; otherwise they are hiding declarations)
6812 //
6813 symbol = new TVariable(&identifier, type);
6814 symbolTable.insert(*symbol);
6815 if (track && symbolTable.atGlobalLevel())
6816 trackLinkage(*symbol);
6817
6818 return;
6819 }
6820 if (symbol->getAsAnonMember()) {
6821 error(loc, "cannot redeclare a user-block member array", identifier.c_str(), "");
6822 symbol = nullptr;
6823 return;
6824 }
6825 }
6826
6827 //
6828 // Process a redeclaration.
6829 //
6830
6831 if (symbol == nullptr) {
6832 error(loc, "array variable name expected", identifier.c_str(), "");
6833 return;
6834 }
6835
6836 // redeclareBuiltinVariable() should have already done the copyUp()
6837 TType& existingType = symbol->getWritableType();
6838
6839 if (existingType.isSizedArray()) {
6840 // be more lenient for input arrays to geometry shaders and tessellation control outputs,
6841 // where the redeclaration is the same size
6842 return;
6843 }
6844
6845 existingType.updateArraySizes(type);
6846 }
6847
6848 //
6849 // Enforce non-initializer type/qualifier rules.
6850 //
fixConstInit(const TSourceLoc & loc,const TString & identifier,TType & type,TIntermTyped * & initializer)6851 void HlslParseContext::fixConstInit(const TSourceLoc& loc, const TString& identifier, TType& type,
6852 TIntermTyped*& initializer)
6853 {
6854 //
6855 // Make the qualifier make sense, given that there is an initializer.
6856 //
6857 if (initializer == nullptr) {
6858 if (type.getQualifier().storage == EvqConst ||
6859 type.getQualifier().storage == EvqConstReadOnly) {
6860 initializer = intermediate.makeAggregate(loc);
6861 warn(loc, "variable with qualifier 'const' not initialized; zero initializing", identifier.c_str(), "");
6862 }
6863 }
6864 }
6865
6866 //
6867 // See if the identifier is a built-in symbol that can be redeclared, and if so,
6868 // copy the symbol table's read-only built-in variable to the current
6869 // global level, where it can be modified based on the passed in type.
6870 //
6871 // Returns nullptr if no redeclaration took place; meaning a normal declaration still
6872 // needs to occur for it, not necessarily an error.
6873 //
6874 // Returns a redeclared and type-modified variable if a redeclared occurred.
6875 //
redeclareBuiltinVariable(const TSourceLoc &,const TString & identifier,const TQualifier &,const TShaderQualifiers &)6876 TSymbol* HlslParseContext::redeclareBuiltinVariable(const TSourceLoc& /*loc*/, const TString& identifier,
6877 const TQualifier& /*qualifier*/,
6878 const TShaderQualifiers& /*publicType*/)
6879 {
6880 if (! builtInName(identifier) || symbolTable.atBuiltInLevel() || ! symbolTable.atGlobalLevel())
6881 return nullptr;
6882
6883 return nullptr;
6884 }
6885
6886 //
6887 // Generate index to the array element in a structure buffer (SSBO)
6888 //
indexStructBufferContent(const TSourceLoc & loc,TIntermTyped * buffer) const6889 TIntermTyped* HlslParseContext::indexStructBufferContent(const TSourceLoc& loc, TIntermTyped* buffer) const
6890 {
6891 // Bail out if not a struct buffer
6892 if (buffer == nullptr || ! isStructBufferType(buffer->getType()))
6893 return nullptr;
6894
6895 // Runtime sized array is always the last element.
6896 const TTypeList* bufferStruct = buffer->getType().getStruct();
6897 TIntermTyped* arrayPosition = intermediate.addConstantUnion(unsigned(bufferStruct->size()-1), loc);
6898
6899 TIntermTyped* argArray = intermediate.addIndex(EOpIndexDirectStruct, buffer, arrayPosition, loc);
6900 argArray->setType(*(*bufferStruct)[bufferStruct->size()-1].type);
6901
6902 return argArray;
6903 }
6904
6905 //
6906 // IFF type is a structuredbuffer/byteaddressbuffer type, return the content
6907 // (template) type. E.g, StructuredBuffer<MyType> -> MyType. Else return nullptr.
6908 //
getStructBufferContentType(const TType & type) const6909 TType* HlslParseContext::getStructBufferContentType(const TType& type) const
6910 {
6911 if (type.getBasicType() != EbtBlock || type.getQualifier().storage != EvqBuffer)
6912 return nullptr;
6913
6914 const int memberCount = (int)type.getStruct()->size();
6915 assert(memberCount > 0);
6916
6917 TType* contentType = (*type.getStruct())[memberCount-1].type;
6918
6919 return contentType->isUnsizedArray() ? contentType : nullptr;
6920 }
6921
6922 //
6923 // If an existing struct buffer has a sharable type, then share it.
6924 //
shareStructBufferType(TType & type)6925 void HlslParseContext::shareStructBufferType(TType& type)
6926 {
6927 // PackOffset must be equivalent to share types on a per-member basis.
6928 // Note: cannot use auto type due to recursion. Thus, this is a std::function.
6929 const std::function<bool(TType& lhs, TType& rhs)>
6930 compareQualifiers = [&](TType& lhs, TType& rhs) -> bool {
6931 if (lhs.getQualifier().layoutOffset != rhs.getQualifier().layoutOffset)
6932 return false;
6933
6934 if (lhs.isStruct() != rhs.isStruct())
6935 return false;
6936
6937 if (lhs.isStruct() && rhs.isStruct()) {
6938 if (lhs.getStruct()->size() != rhs.getStruct()->size())
6939 return false;
6940
6941 for (int i = 0; i < int(lhs.getStruct()->size()); ++i)
6942 if (!compareQualifiers(*(*lhs.getStruct())[i].type, *(*rhs.getStruct())[i].type))
6943 return false;
6944 }
6945
6946 return true;
6947 };
6948
6949 // We need to compare certain qualifiers in addition to the type.
6950 const auto typeEqual = [compareQualifiers](TType& lhs, TType& rhs) -> bool {
6951 if (lhs.getQualifier().readonly != rhs.getQualifier().readonly)
6952 return false;
6953
6954 // If both are structures, recursively look for packOffset equality
6955 // as well as type equality.
6956 return compareQualifiers(lhs, rhs) && lhs == rhs;
6957 };
6958
6959 // This is an exhaustive O(N) search, but real world shaders have
6960 // only a small number of these.
6961 for (int idx = 0; idx < int(structBufferTypes.size()); ++idx) {
6962 // If the deep structure matches, modulo qualifiers, use it
6963 if (typeEqual(*structBufferTypes[idx], type)) {
6964 type.shallowCopy(*structBufferTypes[idx]);
6965 return;
6966 }
6967 }
6968
6969 // Otherwise, remember it:
6970 TType* typeCopy = new TType;
6971 typeCopy->shallowCopy(type);
6972 structBufferTypes.push_back(typeCopy);
6973 }
6974
paramFix(TType & type)6975 void HlslParseContext::paramFix(TType& type)
6976 {
6977 switch (type.getQualifier().storage) {
6978 case EvqConst:
6979 type.getQualifier().storage = EvqConstReadOnly;
6980 break;
6981 case EvqGlobal:
6982 case EvqTemporary:
6983 type.getQualifier().storage = EvqIn;
6984 break;
6985 case EvqBuffer:
6986 {
6987 // SSBO parameter. These do not go through the declareBlock path since they are fn parameters.
6988 correctUniform(type.getQualifier());
6989 TQualifier bufferQualifier = globalBufferDefaults;
6990 mergeObjectLayoutQualifiers(bufferQualifier, type.getQualifier(), true);
6991 bufferQualifier.storage = type.getQualifier().storage;
6992 bufferQualifier.readonly = type.getQualifier().readonly;
6993 bufferQualifier.coherent = type.getQualifier().coherent;
6994 bufferQualifier.declaredBuiltIn = type.getQualifier().declaredBuiltIn;
6995 type.getQualifier() = bufferQualifier;
6996 break;
6997 }
6998 default:
6999 break;
7000 }
7001 }
7002
specializationCheck(const TSourceLoc & loc,const TType & type,const char * op)7003 void HlslParseContext::specializationCheck(const TSourceLoc& loc, const TType& type, const char* op)
7004 {
7005 if (type.containsSpecializationSize())
7006 error(loc, "can't use with types containing arrays sized with a specialization constant", op, "");
7007 }
7008
7009 //
7010 // Layout qualifier stuff.
7011 //
7012
7013 // Put the id's layout qualification into the public type, for qualifiers not having a number set.
7014 // This is before we know any type information for error checking.
setLayoutQualifier(const TSourceLoc & loc,TQualifier & qualifier,TString & id)7015 void HlslParseContext::setLayoutQualifier(const TSourceLoc& loc, TQualifier& qualifier, TString& id)
7016 {
7017 std::transform(id.begin(), id.end(), id.begin(), ::tolower);
7018
7019 if (id == TQualifier::getLayoutMatrixString(ElmColumnMajor)) {
7020 qualifier.layoutMatrix = ElmRowMajor;
7021 return;
7022 }
7023 if (id == TQualifier::getLayoutMatrixString(ElmRowMajor)) {
7024 qualifier.layoutMatrix = ElmColumnMajor;
7025 return;
7026 }
7027 if (id == "push_constant") {
7028 requireVulkan(loc, "push_constant");
7029 qualifier.layoutPushConstant = true;
7030 return;
7031 }
7032 if (language == EShLangGeometry || language == EShLangTessEvaluation) {
7033 if (id == TQualifier::getGeometryString(ElgTriangles)) {
7034 // publicType.shaderQualifiers.geometry = ElgTriangles;
7035 warn(loc, "ignored", id.c_str(), "");
7036 return;
7037 }
7038 if (language == EShLangGeometry) {
7039 if (id == TQualifier::getGeometryString(ElgPoints)) {
7040 // publicType.shaderQualifiers.geometry = ElgPoints;
7041 warn(loc, "ignored", id.c_str(), "");
7042 return;
7043 }
7044 if (id == TQualifier::getGeometryString(ElgLineStrip)) {
7045 // publicType.shaderQualifiers.geometry = ElgLineStrip;
7046 warn(loc, "ignored", id.c_str(), "");
7047 return;
7048 }
7049 if (id == TQualifier::getGeometryString(ElgLines)) {
7050 // publicType.shaderQualifiers.geometry = ElgLines;
7051 warn(loc, "ignored", id.c_str(), "");
7052 return;
7053 }
7054 if (id == TQualifier::getGeometryString(ElgLinesAdjacency)) {
7055 // publicType.shaderQualifiers.geometry = ElgLinesAdjacency;
7056 warn(loc, "ignored", id.c_str(), "");
7057 return;
7058 }
7059 if (id == TQualifier::getGeometryString(ElgTrianglesAdjacency)) {
7060 // publicType.shaderQualifiers.geometry = ElgTrianglesAdjacency;
7061 warn(loc, "ignored", id.c_str(), "");
7062 return;
7063 }
7064 if (id == TQualifier::getGeometryString(ElgTriangleStrip)) {
7065 // publicType.shaderQualifiers.geometry = ElgTriangleStrip;
7066 warn(loc, "ignored", id.c_str(), "");
7067 return;
7068 }
7069 } else {
7070 assert(language == EShLangTessEvaluation);
7071
7072 // input primitive
7073 if (id == TQualifier::getGeometryString(ElgTriangles)) {
7074 // publicType.shaderQualifiers.geometry = ElgTriangles;
7075 warn(loc, "ignored", id.c_str(), "");
7076 return;
7077 }
7078 if (id == TQualifier::getGeometryString(ElgQuads)) {
7079 // publicType.shaderQualifiers.geometry = ElgQuads;
7080 warn(loc, "ignored", id.c_str(), "");
7081 return;
7082 }
7083 if (id == TQualifier::getGeometryString(ElgIsolines)) {
7084 // publicType.shaderQualifiers.geometry = ElgIsolines;
7085 warn(loc, "ignored", id.c_str(), "");
7086 return;
7087 }
7088
7089 // vertex spacing
7090 if (id == TQualifier::getVertexSpacingString(EvsEqual)) {
7091 // publicType.shaderQualifiers.spacing = EvsEqual;
7092 warn(loc, "ignored", id.c_str(), "");
7093 return;
7094 }
7095 if (id == TQualifier::getVertexSpacingString(EvsFractionalEven)) {
7096 // publicType.shaderQualifiers.spacing = EvsFractionalEven;
7097 warn(loc, "ignored", id.c_str(), "");
7098 return;
7099 }
7100 if (id == TQualifier::getVertexSpacingString(EvsFractionalOdd)) {
7101 // publicType.shaderQualifiers.spacing = EvsFractionalOdd;
7102 warn(loc, "ignored", id.c_str(), "");
7103 return;
7104 }
7105
7106 // triangle order
7107 if (id == TQualifier::getVertexOrderString(EvoCw)) {
7108 // publicType.shaderQualifiers.order = EvoCw;
7109 warn(loc, "ignored", id.c_str(), "");
7110 return;
7111 }
7112 if (id == TQualifier::getVertexOrderString(EvoCcw)) {
7113 // publicType.shaderQualifiers.order = EvoCcw;
7114 warn(loc, "ignored", id.c_str(), "");
7115 return;
7116 }
7117
7118 // point mode
7119 if (id == "point_mode") {
7120 // publicType.shaderQualifiers.pointMode = true;
7121 warn(loc, "ignored", id.c_str(), "");
7122 return;
7123 }
7124 }
7125 }
7126 if (language == EShLangFragment) {
7127 if (id == "origin_upper_left") {
7128 // publicType.shaderQualifiers.originUpperLeft = true;
7129 warn(loc, "ignored", id.c_str(), "");
7130 return;
7131 }
7132 if (id == "pixel_center_integer") {
7133 // publicType.shaderQualifiers.pixelCenterInteger = true;
7134 warn(loc, "ignored", id.c_str(), "");
7135 return;
7136 }
7137 if (id == "early_fragment_tests") {
7138 // publicType.shaderQualifiers.earlyFragmentTests = true;
7139 warn(loc, "ignored", id.c_str(), "");
7140 return;
7141 }
7142 for (TLayoutDepth depth = (TLayoutDepth)(EldNone + 1); depth < EldCount; depth = (TLayoutDepth)(depth + 1)) {
7143 if (id == TQualifier::getLayoutDepthString(depth)) {
7144 // publicType.shaderQualifiers.layoutDepth = depth;
7145 warn(loc, "ignored", id.c_str(), "");
7146 return;
7147 }
7148 }
7149 if (id.compare(0, 13, "blend_support") == 0) {
7150 bool found = false;
7151 for (TBlendEquationShift be = (TBlendEquationShift)0; be < EBlendCount; be = (TBlendEquationShift)(be + 1)) {
7152 if (id == TQualifier::getBlendEquationString(be)) {
7153 requireExtensions(loc, 1, &E_GL_KHR_blend_equation_advanced, "blend equation");
7154 intermediate.addBlendEquation(be);
7155 // publicType.shaderQualifiers.blendEquation = true;
7156 warn(loc, "ignored", id.c_str(), "");
7157 found = true;
7158 break;
7159 }
7160 }
7161 if (! found)
7162 error(loc, "unknown blend equation", "blend_support", "");
7163 return;
7164 }
7165 }
7166 error(loc, "unrecognized layout identifier, or qualifier requires assignment (e.g., binding = 4)", id.c_str(), "");
7167 }
7168
7169 // Put the id's layout qualifier value into the public type, for qualifiers having a number set.
7170 // This is before we know any type information for error checking.
setLayoutQualifier(const TSourceLoc & loc,TQualifier & qualifier,TString & id,const TIntermTyped * node)7171 void HlslParseContext::setLayoutQualifier(const TSourceLoc& loc, TQualifier& qualifier, TString& id,
7172 const TIntermTyped* node)
7173 {
7174 const char* feature = "layout-id value";
7175 // const char* nonLiteralFeature = "non-literal layout-id value";
7176
7177 integerCheck(node, feature);
7178 const TIntermConstantUnion* constUnion = node->getAsConstantUnion();
7179 int value = 0;
7180 if (constUnion) {
7181 value = constUnion->getConstArray()[0].getIConst();
7182 }
7183
7184 std::transform(id.begin(), id.end(), id.begin(), ::tolower);
7185
7186 if (id == "offset") {
7187 qualifier.layoutOffset = value;
7188 return;
7189 } else if (id == "align") {
7190 // "The specified alignment must be a power of 2, or a compile-time error results."
7191 if (! IsPow2(value))
7192 error(loc, "must be a power of 2", "align", "");
7193 else
7194 qualifier.layoutAlign = value;
7195 return;
7196 } else if (id == "location") {
7197 if ((unsigned int)value >= TQualifier::layoutLocationEnd)
7198 error(loc, "location is too large", id.c_str(), "");
7199 else
7200 qualifier.layoutLocation = value;
7201 return;
7202 } else if (id == "set") {
7203 if ((unsigned int)value >= TQualifier::layoutSetEnd)
7204 error(loc, "set is too large", id.c_str(), "");
7205 else
7206 qualifier.layoutSet = value;
7207 return;
7208 } else if (id == "binding") {
7209 if ((unsigned int)value >= TQualifier::layoutBindingEnd)
7210 error(loc, "binding is too large", id.c_str(), "");
7211 else
7212 qualifier.layoutBinding = value;
7213 return;
7214 } else if (id == "component") {
7215 if ((unsigned)value >= TQualifier::layoutComponentEnd)
7216 error(loc, "component is too large", id.c_str(), "");
7217 else
7218 qualifier.layoutComponent = value;
7219 return;
7220 } else if (id.compare(0, 4, "xfb_") == 0) {
7221 // "Any shader making any static use (after preprocessing) of any of these
7222 // *xfb_* qualifiers will cause the shader to be in a transform feedback
7223 // capturing mode and hence responsible for describing the transform feedback
7224 // setup."
7225 intermediate.setXfbMode();
7226 if (id == "xfb_buffer") {
7227 // "It is a compile-time error to specify an *xfb_buffer* that is greater than
7228 // the implementation-dependent constant gl_MaxTransformFeedbackBuffers."
7229 if (value >= resources.maxTransformFeedbackBuffers)
7230 error(loc, "buffer is too large:", id.c_str(), "gl_MaxTransformFeedbackBuffers is %d",
7231 resources.maxTransformFeedbackBuffers);
7232 if (value >= (int)TQualifier::layoutXfbBufferEnd)
7233 error(loc, "buffer is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbBufferEnd - 1);
7234 else
7235 qualifier.layoutXfbBuffer = value;
7236 return;
7237 } else if (id == "xfb_offset") {
7238 if (value >= (int)TQualifier::layoutXfbOffsetEnd)
7239 error(loc, "offset is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbOffsetEnd - 1);
7240 else
7241 qualifier.layoutXfbOffset = value;
7242 return;
7243 } else if (id == "xfb_stride") {
7244 // "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the
7245 // implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents."
7246 if (value > 4 * resources.maxTransformFeedbackInterleavedComponents)
7247 error(loc, "1/4 stride is too large:", id.c_str(), "gl_MaxTransformFeedbackInterleavedComponents is %d",
7248 resources.maxTransformFeedbackInterleavedComponents);
7249 else if (value >= (int)TQualifier::layoutXfbStrideEnd)
7250 error(loc, "stride is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbStrideEnd - 1);
7251 if (value < (int)TQualifier::layoutXfbStrideEnd)
7252 qualifier.layoutXfbStride = value;
7253 return;
7254 }
7255 }
7256
7257 if (id == "input_attachment_index") {
7258 requireVulkan(loc, "input_attachment_index");
7259 if (value >= (int)TQualifier::layoutAttachmentEnd)
7260 error(loc, "attachment index is too large", id.c_str(), "");
7261 else
7262 qualifier.layoutAttachment = value;
7263 return;
7264 }
7265 if (id == "constant_id") {
7266 setSpecConstantId(loc, qualifier, value);
7267 return;
7268 }
7269
7270 switch (language) {
7271 case EShLangVertex:
7272 break;
7273
7274 case EShLangTessControl:
7275 if (id == "vertices") {
7276 if (value == 0)
7277 error(loc, "must be greater than 0", "vertices", "");
7278 else
7279 // publicType.shaderQualifiers.vertices = value;
7280 warn(loc, "ignored", id.c_str(), "");
7281 return;
7282 }
7283 break;
7284
7285 case EShLangTessEvaluation:
7286 break;
7287
7288 case EShLangGeometry:
7289 if (id == "invocations") {
7290 if (value == 0)
7291 error(loc, "must be at least 1", "invocations", "");
7292 else
7293 // publicType.shaderQualifiers.invocations = value;
7294 warn(loc, "ignored", id.c_str(), "");
7295 return;
7296 }
7297 if (id == "max_vertices") {
7298 // publicType.shaderQualifiers.vertices = value;
7299 warn(loc, "ignored", id.c_str(), "");
7300 if (value > resources.maxGeometryOutputVertices)
7301 error(loc, "too large, must be less than gl_MaxGeometryOutputVertices", "max_vertices", "");
7302 return;
7303 }
7304 if (id == "stream") {
7305 qualifier.layoutStream = value;
7306 return;
7307 }
7308 break;
7309
7310 case EShLangFragment:
7311 if (id == "index") {
7312 qualifier.layoutIndex = value;
7313 return;
7314 }
7315 break;
7316
7317 case EShLangCompute:
7318 if (id.compare(0, 11, "local_size_") == 0) {
7319 if (id == "local_size_x") {
7320 // publicType.shaderQualifiers.localSize[0] = value;
7321 warn(loc, "ignored", id.c_str(), "");
7322 return;
7323 }
7324 if (id == "local_size_y") {
7325 // publicType.shaderQualifiers.localSize[1] = value;
7326 warn(loc, "ignored", id.c_str(), "");
7327 return;
7328 }
7329 if (id == "local_size_z") {
7330 // publicType.shaderQualifiers.localSize[2] = value;
7331 warn(loc, "ignored", id.c_str(), "");
7332 return;
7333 }
7334 if (spvVersion.spv != 0) {
7335 if (id == "local_size_x_id") {
7336 // publicType.shaderQualifiers.localSizeSpecId[0] = value;
7337 warn(loc, "ignored", id.c_str(), "");
7338 return;
7339 }
7340 if (id == "local_size_y_id") {
7341 // publicType.shaderQualifiers.localSizeSpecId[1] = value;
7342 warn(loc, "ignored", id.c_str(), "");
7343 return;
7344 }
7345 if (id == "local_size_z_id") {
7346 // publicType.shaderQualifiers.localSizeSpecId[2] = value;
7347 warn(loc, "ignored", id.c_str(), "");
7348 return;
7349 }
7350 }
7351 }
7352 break;
7353
7354 default:
7355 break;
7356 }
7357
7358 error(loc, "there is no such layout identifier for this stage taking an assigned value", id.c_str(), "");
7359 }
7360
setSpecConstantId(const TSourceLoc & loc,TQualifier & qualifier,int value)7361 void HlslParseContext::setSpecConstantId(const TSourceLoc& loc, TQualifier& qualifier, int value)
7362 {
7363 if (value >= (int)TQualifier::layoutSpecConstantIdEnd) {
7364 error(loc, "specialization-constant id is too large", "constant_id", "");
7365 } else {
7366 qualifier.layoutSpecConstantId = value;
7367 qualifier.specConstant = true;
7368 if (! intermediate.addUsedConstantId(value))
7369 error(loc, "specialization-constant id already used", "constant_id", "");
7370 }
7371 return;
7372 }
7373
7374 // Merge any layout qualifier information from src into dst, leaving everything else in dst alone
7375 //
7376 // "More than one layout qualifier may appear in a single declaration.
7377 // Additionally, the same layout-qualifier-name can occur multiple times
7378 // within a layout qualifier or across multiple layout qualifiers in the
7379 // same declaration. When the same layout-qualifier-name occurs
7380 // multiple times, in a single declaration, the last occurrence overrides
7381 // the former occurrence(s). Further, if such a layout-qualifier-name
7382 // will effect subsequent declarations or other observable behavior, it
7383 // is only the last occurrence that will have any effect, behaving as if
7384 // the earlier occurrence(s) within the declaration are not present.
7385 // This is also true for overriding layout-qualifier-names, where one
7386 // overrides the other (e.g., row_major vs. column_major); only the last
7387 // occurrence has any effect."
7388 //
mergeObjectLayoutQualifiers(TQualifier & dst,const TQualifier & src,bool inheritOnly)7389 void HlslParseContext::mergeObjectLayoutQualifiers(TQualifier& dst, const TQualifier& src, bool inheritOnly)
7390 {
7391 if (src.hasMatrix())
7392 dst.layoutMatrix = src.layoutMatrix;
7393 if (src.hasPacking())
7394 dst.layoutPacking = src.layoutPacking;
7395
7396 if (src.hasStream())
7397 dst.layoutStream = src.layoutStream;
7398
7399 if (src.hasFormat())
7400 dst.layoutFormat = src.layoutFormat;
7401
7402 if (src.hasXfbBuffer())
7403 dst.layoutXfbBuffer = src.layoutXfbBuffer;
7404
7405 if (src.hasAlign())
7406 dst.layoutAlign = src.layoutAlign;
7407
7408 if (! inheritOnly) {
7409 if (src.hasLocation())
7410 dst.layoutLocation = src.layoutLocation;
7411 if (src.hasComponent())
7412 dst.layoutComponent = src.layoutComponent;
7413 if (src.hasIndex())
7414 dst.layoutIndex = src.layoutIndex;
7415
7416 if (src.hasOffset())
7417 dst.layoutOffset = src.layoutOffset;
7418
7419 if (src.hasSet())
7420 dst.layoutSet = src.layoutSet;
7421 if (src.layoutBinding != TQualifier::layoutBindingEnd)
7422 dst.layoutBinding = src.layoutBinding;
7423
7424 if (src.hasXfbStride())
7425 dst.layoutXfbStride = src.layoutXfbStride;
7426 if (src.hasXfbOffset())
7427 dst.layoutXfbOffset = src.layoutXfbOffset;
7428 if (src.hasAttachment())
7429 dst.layoutAttachment = src.layoutAttachment;
7430 if (src.hasSpecConstantId())
7431 dst.layoutSpecConstantId = src.layoutSpecConstantId;
7432
7433 if (src.layoutPushConstant)
7434 dst.layoutPushConstant = true;
7435 }
7436 }
7437
7438
7439 //
7440 // Look up a function name in the symbol table, and make sure it is a function.
7441 //
7442 // First, look for an exact match. If there is none, use the generic selector
7443 // TParseContextBase::selectFunction() to find one, parameterized by the
7444 // convertible() and better() predicates defined below.
7445 //
7446 // Return the function symbol if found, otherwise nullptr.
7447 //
findFunction(const TSourceLoc & loc,TFunction & call,bool & builtIn,int & thisDepth,TIntermTyped * & args)7448 const TFunction* HlslParseContext::findFunction(const TSourceLoc& loc, TFunction& call, bool& builtIn, int& thisDepth,
7449 TIntermTyped*& args)
7450 {
7451 if (symbolTable.isFunctionNameVariable(call.getName())) {
7452 error(loc, "can't use function syntax on variable", call.getName().c_str(), "");
7453 return nullptr;
7454 }
7455
7456 // first, look for an exact match
7457 bool dummyScope;
7458 TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn, &dummyScope, &thisDepth);
7459 if (symbol)
7460 return symbol->getAsFunction();
7461
7462 // no exact match, use the generic selector, parameterized by the GLSL rules
7463
7464 // create list of candidates to send
7465 TVector<const TFunction*> candidateList;
7466 symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
7467
7468 // These built-in ops can accept any type, so we bypass the argument selection
7469 if (candidateList.size() == 1 && builtIn &&
7470 (candidateList[0]->getBuiltInOp() == EOpMethodAppend ||
7471 candidateList[0]->getBuiltInOp() == EOpMethodRestartStrip ||
7472 candidateList[0]->getBuiltInOp() == EOpMethodIncrementCounter ||
7473 candidateList[0]->getBuiltInOp() == EOpMethodDecrementCounter ||
7474 candidateList[0]->getBuiltInOp() == EOpMethodAppend ||
7475 candidateList[0]->getBuiltInOp() == EOpMethodConsume)) {
7476 return candidateList[0];
7477 }
7478
7479 bool allowOnlyUpConversions = true;
7480
7481 // can 'from' convert to 'to'?
7482 const auto convertible = [&](const TType& from, const TType& to, TOperator op, int arg) -> bool {
7483 if (from == to)
7484 return true;
7485
7486 // no aggregate conversions
7487 if (from.isArray() || to.isArray() ||
7488 from.isStruct() || to.isStruct())
7489 return false;
7490
7491 switch (op) {
7492 case EOpInterlockedAdd:
7493 case EOpInterlockedAnd:
7494 case EOpInterlockedCompareExchange:
7495 case EOpInterlockedCompareStore:
7496 case EOpInterlockedExchange:
7497 case EOpInterlockedMax:
7498 case EOpInterlockedMin:
7499 case EOpInterlockedOr:
7500 case EOpInterlockedXor:
7501 // We do not promote the texture or image type for these ocodes. Normally that would not
7502 // be an issue because it's a buffer, but we haven't decomposed the opcode yet, and at this
7503 // stage it's merely e.g, a basic integer type.
7504 //
7505 // Instead, we want to promote other arguments, but stay within the same family. In other
7506 // words, InterlockedAdd(RWBuffer<int>, ...) will always use the int flavor, never the uint flavor,
7507 // but it is allowed to promote its other arguments.
7508 if (arg == 0)
7509 return false;
7510 break;
7511 case EOpMethodSample:
7512 case EOpMethodSampleBias:
7513 case EOpMethodSampleCmp:
7514 case EOpMethodSampleCmpLevelZero:
7515 case EOpMethodSampleGrad:
7516 case EOpMethodSampleLevel:
7517 case EOpMethodLoad:
7518 case EOpMethodGetDimensions:
7519 case EOpMethodGetSamplePosition:
7520 case EOpMethodGather:
7521 case EOpMethodCalculateLevelOfDetail:
7522 case EOpMethodCalculateLevelOfDetailUnclamped:
7523 case EOpMethodGatherRed:
7524 case EOpMethodGatherGreen:
7525 case EOpMethodGatherBlue:
7526 case EOpMethodGatherAlpha:
7527 case EOpMethodGatherCmp:
7528 case EOpMethodGatherCmpRed:
7529 case EOpMethodGatherCmpGreen:
7530 case EOpMethodGatherCmpBlue:
7531 case EOpMethodGatherCmpAlpha:
7532 case EOpMethodAppend:
7533 case EOpMethodRestartStrip:
7534 // those are method calls, the object type can not be changed
7535 // they are equal if the dim and type match (is dim sufficient?)
7536 if (arg == 0)
7537 return from.getSampler().type == to.getSampler().type &&
7538 from.getSampler().arrayed == to.getSampler().arrayed &&
7539 from.getSampler().shadow == to.getSampler().shadow &&
7540 from.getSampler().ms == to.getSampler().ms &&
7541 from.getSampler().dim == to.getSampler().dim;
7542 break;
7543 default:
7544 break;
7545 }
7546
7547 // basic types have to be convertible
7548 if (allowOnlyUpConversions)
7549 if (! intermediate.canImplicitlyPromote(from.getBasicType(), to.getBasicType(), EOpFunctionCall))
7550 return false;
7551
7552 // shapes have to be convertible
7553 if ((from.isScalarOrVec1() && to.isScalarOrVec1()) ||
7554 (from.isScalarOrVec1() && to.isVector()) ||
7555 (from.isScalarOrVec1() && to.isMatrix()) ||
7556 (from.isVector() && to.isVector() && from.getVectorSize() >= to.getVectorSize()))
7557 return true;
7558
7559 // TODO: what are the matrix rules? they go here
7560
7561 return false;
7562 };
7563
7564 // Is 'to2' a better conversion than 'to1'?
7565 // Ties should not be considered as better.
7566 // Assumes 'convertible' already said true.
7567 const auto better = [](const TType& from, const TType& to1, const TType& to2) -> bool {
7568 // exact match is always better than mismatch
7569 if (from == to2)
7570 return from != to1;
7571 if (from == to1)
7572 return false;
7573
7574 // shape changes are always worse
7575 if (from.isScalar() || from.isVector()) {
7576 if (from.getVectorSize() == to2.getVectorSize() &&
7577 from.getVectorSize() != to1.getVectorSize())
7578 return true;
7579 if (from.getVectorSize() == to1.getVectorSize() &&
7580 from.getVectorSize() != to2.getVectorSize())
7581 return false;
7582 }
7583
7584 // Handle sampler betterness: An exact sampler match beats a non-exact match.
7585 // (If we just looked at basic type, all EbtSamplers would look the same).
7586 // If any type is not a sampler, just use the linearize function below.
7587 if (from.getBasicType() == EbtSampler && to1.getBasicType() == EbtSampler && to2.getBasicType() == EbtSampler) {
7588 // We can ignore the vector size in the comparison.
7589 TSampler to1Sampler = to1.getSampler();
7590 TSampler to2Sampler = to2.getSampler();
7591
7592 to1Sampler.vectorSize = to2Sampler.vectorSize = from.getSampler().vectorSize;
7593
7594 if (from.getSampler() == to2Sampler)
7595 return from.getSampler() != to1Sampler;
7596 if (from.getSampler() == to1Sampler)
7597 return false;
7598 }
7599
7600 // Might or might not be changing shape, which means basic type might
7601 // or might not match, so within that, the question is how big a
7602 // basic-type conversion is being done.
7603 //
7604 // Use a hierarchy of domains, translated to order of magnitude
7605 // in a linearized view:
7606 // - floating-point vs. integer
7607 // - 32 vs. 64 bit (or width in general)
7608 // - bool vs. non bool
7609 // - signed vs. not signed
7610 const auto linearize = [](const TBasicType& basicType) -> int {
7611 switch (basicType) {
7612 case EbtBool: return 1;
7613 case EbtInt: return 10;
7614 case EbtUint: return 11;
7615 case EbtInt64: return 20;
7616 case EbtUint64: return 21;
7617 case EbtFloat: return 100;
7618 case EbtDouble: return 110;
7619 default: return 0;
7620 }
7621 };
7622
7623 return abs(linearize(to2.getBasicType()) - linearize(from.getBasicType())) <
7624 abs(linearize(to1.getBasicType()) - linearize(from.getBasicType()));
7625 };
7626
7627 // for ambiguity reporting
7628 bool tie = false;
7629
7630 // send to the generic selector
7631 const TFunction* bestMatch = nullptr;
7632
7633 // printf has var args and is in the symbol table as "printf()",
7634 // mangled to "printf("
7635 if (call.getName() == "printf") {
7636 TSymbol* symbol = symbolTable.find("printf(", &builtIn);
7637 if (symbol)
7638 return symbol->getAsFunction();
7639 }
7640
7641 bestMatch = selectFunction(candidateList, call, convertible, better, tie);
7642
7643 if (bestMatch == nullptr) {
7644 // If there is nothing selected by allowing only up-conversions (to a larger linearize() value),
7645 // we instead try down-conversions, which are valid in HLSL, but not preferred if there are any
7646 // upconversions possible.
7647 allowOnlyUpConversions = false;
7648 bestMatch = selectFunction(candidateList, call, convertible, better, tie);
7649 }
7650
7651 if (bestMatch == nullptr) {
7652 error(loc, "no matching overloaded function found", call.getName().c_str(), "");
7653 return nullptr;
7654 }
7655
7656 // For built-ins, we can convert across the arguments. This will happen in several steps:
7657 // Step 1: If there's an exact match, use it.
7658 // Step 2a: Otherwise, get the operator from the best match and promote arguments:
7659 // Step 2b: reconstruct the TFunction based on the new arg types
7660 // Step 3: Re-select after type promotion is applied, to find proper candidate.
7661 if (builtIn) {
7662 // Step 1: If there's an exact match, use it.
7663 if (call.getMangledName() == bestMatch->getMangledName())
7664 return bestMatch;
7665
7666 // Step 2a: Otherwise, get the operator from the best match and promote arguments as if we
7667 // are that kind of operator.
7668 if (args != nullptr) {
7669 // The arg list can be a unary node, or an aggregate. We have to handle both.
7670 // We will use the normal promote() facilities, which require an interm node.
7671 TIntermOperator* promote = nullptr;
7672
7673 if (call.getParamCount() == 1) {
7674 promote = new TIntermUnary(bestMatch->getBuiltInOp());
7675 promote->getAsUnaryNode()->setOperand(args->getAsTyped());
7676 } else {
7677 promote = new TIntermAggregate(bestMatch->getBuiltInOp());
7678 promote->getAsAggregate()->getSequence().swap(args->getAsAggregate()->getSequence());
7679 }
7680
7681 if (! intermediate.promote(promote))
7682 return nullptr;
7683
7684 // Obtain the promoted arg list.
7685 if (call.getParamCount() == 1) {
7686 args = promote->getAsUnaryNode()->getOperand();
7687 } else {
7688 promote->getAsAggregate()->getSequence().swap(args->getAsAggregate()->getSequence());
7689 }
7690 }
7691
7692 // Step 2b: reconstruct the TFunction based on the new arg types
7693 TFunction convertedCall(&call.getName(), call.getType(), call.getBuiltInOp());
7694
7695 if (args->getAsAggregate()) {
7696 // Handle aggregates: put all args into the new function call
7697 for (int arg = 0; arg < int(args->getAsAggregate()->getSequence().size()); ++arg) {
7698 // TODO: But for constness, we could avoid the new & shallowCopy, and use the pointer directly.
7699 TParameter param = { 0, new TType, nullptr };
7700 param.type->shallowCopy(args->getAsAggregate()->getSequence()[arg]->getAsTyped()->getType());
7701 convertedCall.addParameter(param);
7702 }
7703 } else if (args->getAsUnaryNode()) {
7704 // Handle unaries: put all args into the new function call
7705 TParameter param = { 0, new TType, nullptr };
7706 param.type->shallowCopy(args->getAsUnaryNode()->getOperand()->getAsTyped()->getType());
7707 convertedCall.addParameter(param);
7708 } else if (args->getAsTyped()) {
7709 // Handle bare e.g, floats, not in an aggregate.
7710 TParameter param = { 0, new TType, nullptr };
7711 param.type->shallowCopy(args->getAsTyped()->getType());
7712 convertedCall.addParameter(param);
7713 } else {
7714 assert(0); // unknown argument list.
7715 return nullptr;
7716 }
7717
7718 // Step 3: Re-select after type promotion, to find proper candidate
7719 // send to the generic selector
7720 bestMatch = selectFunction(candidateList, convertedCall, convertible, better, tie);
7721
7722 // At this point, there should be no tie.
7723 }
7724
7725 if (tie)
7726 error(loc, "ambiguous best function under implicit type conversion", call.getName().c_str(), "");
7727
7728 // Append default parameter values if needed
7729 if (!tie && bestMatch != nullptr) {
7730 for (int defParam = call.getParamCount(); defParam < bestMatch->getParamCount(); ++defParam) {
7731 handleFunctionArgument(&call, args, (*bestMatch)[defParam].defaultValue);
7732 }
7733 }
7734
7735 return bestMatch;
7736 }
7737
7738 //
7739 // Do everything necessary to handle a typedef declaration, for a single symbol.
7740 //
7741 // 'parseType' is the type part of the declaration (to the left)
7742 // 'arraySizes' is the arrayness tagged on the identifier (to the right)
7743 //
declareTypedef(const TSourceLoc & loc,const TString & identifier,const TType & parseType)7744 void HlslParseContext::declareTypedef(const TSourceLoc& loc, const TString& identifier, const TType& parseType)
7745 {
7746 TVariable* typeSymbol = new TVariable(&identifier, parseType, true);
7747 if (! symbolTable.insert(*typeSymbol))
7748 error(loc, "name already defined", "typedef", identifier.c_str());
7749 }
7750
7751 // Do everything necessary to handle a struct declaration, including
7752 // making IO aliases because HLSL allows mixed IO in a struct that specializes
7753 // based on the usage (input, output, uniform, none).
declareStruct(const TSourceLoc & loc,TString & structName,TType & type)7754 void HlslParseContext::declareStruct(const TSourceLoc& loc, TString& structName, TType& type)
7755 {
7756 // If it was named, which means the type can be reused later, add
7757 // it to the symbol table. (Unless it's a block, in which
7758 // case the name is not a type.)
7759 if (type.getBasicType() == EbtBlock || structName.size() == 0)
7760 return;
7761
7762 TVariable* userTypeDef = new TVariable(&structName, type, true);
7763 if (! symbolTable.insert(*userTypeDef)) {
7764 error(loc, "redefinition", structName.c_str(), "struct");
7765 return;
7766 }
7767
7768 // See if we need IO aliases for the structure typeList
7769
7770 const auto condAlloc = [](bool pred, TTypeList*& list) {
7771 if (pred && list == nullptr)
7772 list = new TTypeList;
7773 };
7774
7775 tIoKinds newLists = { nullptr, nullptr, nullptr }; // allocate for each kind found
7776 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
7777 condAlloc(hasUniform(member->type->getQualifier()), newLists.uniform);
7778 condAlloc( hasInput(member->type->getQualifier()), newLists.input);
7779 condAlloc( hasOutput(member->type->getQualifier()), newLists.output);
7780
7781 if (member->type->isStruct()) {
7782 auto it = ioTypeMap.find(member->type->getStruct());
7783 if (it != ioTypeMap.end()) {
7784 condAlloc(it->second.uniform != nullptr, newLists.uniform);
7785 condAlloc(it->second.input != nullptr, newLists.input);
7786 condAlloc(it->second.output != nullptr, newLists.output);
7787 }
7788 }
7789 }
7790 if (newLists.uniform == nullptr &&
7791 newLists.input == nullptr &&
7792 newLists.output == nullptr) {
7793 // Won't do any IO caching, clear up the type and get out now.
7794 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member)
7795 clearUniformInputOutput(member->type->getQualifier());
7796 return;
7797 }
7798
7799 // We have IO involved.
7800
7801 // Make a pure typeList for the symbol table, and cache side copies of IO versions.
7802 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
7803 const auto inheritStruct = [&](TTypeList* s, TTypeLoc& ioMember) {
7804 if (s != nullptr) {
7805 ioMember.type = new TType;
7806 ioMember.type->shallowCopy(*member->type);
7807 ioMember.type->setStruct(s);
7808 }
7809 };
7810 const auto newMember = [&](TTypeLoc& m) {
7811 if (m.type == nullptr) {
7812 m.type = new TType;
7813 m.type->shallowCopy(*member->type);
7814 }
7815 };
7816
7817 TTypeLoc newUniformMember = { nullptr, member->loc };
7818 TTypeLoc newInputMember = { nullptr, member->loc };
7819 TTypeLoc newOutputMember = { nullptr, member->loc };
7820 if (member->type->isStruct()) {
7821 // swap in an IO child if there is one
7822 auto it = ioTypeMap.find(member->type->getStruct());
7823 if (it != ioTypeMap.end()) {
7824 inheritStruct(it->second.uniform, newUniformMember);
7825 inheritStruct(it->second.input, newInputMember);
7826 inheritStruct(it->second.output, newOutputMember);
7827 }
7828 }
7829 if (newLists.uniform) {
7830 newMember(newUniformMember);
7831
7832 // inherit default matrix layout (changeable via #pragma pack_matrix), if none given.
7833 if (member->type->isMatrix() && member->type->getQualifier().layoutMatrix == ElmNone)
7834 newUniformMember.type->getQualifier().layoutMatrix = globalUniformDefaults.layoutMatrix;
7835
7836 correctUniform(newUniformMember.type->getQualifier());
7837 newLists.uniform->push_back(newUniformMember);
7838 }
7839 if (newLists.input) {
7840 newMember(newInputMember);
7841 correctInput(newInputMember.type->getQualifier());
7842 newLists.input->push_back(newInputMember);
7843 }
7844 if (newLists.output) {
7845 newMember(newOutputMember);
7846 correctOutput(newOutputMember.type->getQualifier());
7847 newLists.output->push_back(newOutputMember);
7848 }
7849
7850 // make original pure
7851 clearUniformInputOutput(member->type->getQualifier());
7852 }
7853 ioTypeMap[type.getStruct()] = newLists;
7854 }
7855
7856 // Lookup a user-type by name.
7857 // If found, fill in the type and return the defining symbol.
7858 // If not found, return nullptr.
lookupUserType(const TString & typeName,TType & type)7859 TSymbol* HlslParseContext::lookupUserType(const TString& typeName, TType& type)
7860 {
7861 TSymbol* symbol = symbolTable.find(typeName);
7862 if (symbol && symbol->getAsVariable() && symbol->getAsVariable()->isUserType()) {
7863 type.shallowCopy(symbol->getType());
7864 return symbol;
7865 } else
7866 return nullptr;
7867 }
7868
7869 //
7870 // Do everything necessary to handle a variable (non-block) declaration.
7871 // Either redeclaring a variable, or making a new one, updating the symbol
7872 // table, and all error checking.
7873 //
7874 // Returns a subtree node that computes an initializer, if needed.
7875 // Returns nullptr if there is no code to execute for initialization.
7876 //
7877 // 'parseType' is the type part of the declaration (to the left)
7878 // 'arraySizes' is the arrayness tagged on the identifier (to the right)
7879 //
declareVariable(const TSourceLoc & loc,const TString & identifier,TType & type,TIntermTyped * initializer)7880 TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, const TString& identifier, TType& type,
7881 TIntermTyped* initializer)
7882 {
7883 if (voidErrorCheck(loc, identifier, type.getBasicType()))
7884 return nullptr;
7885
7886 // Global consts with initializers that are non-const act like EvqGlobal in HLSL.
7887 // This test is implicitly recursive, because initializers propagate constness
7888 // up the aggregate node tree during creation. E.g, for:
7889 // { { 1, 2 }, { 3, 4 } }
7890 // the initializer list is marked EvqConst at the top node, and remains so here. However:
7891 // { 1, { myvar, 2 }, 3 }
7892 // is not a const intializer, and still becomes EvqGlobal here.
7893
7894 const bool nonConstInitializer = (initializer != nullptr && initializer->getQualifier().storage != EvqConst);
7895
7896 if (type.getQualifier().storage == EvqConst && symbolTable.atGlobalLevel() && nonConstInitializer) {
7897 // Force to global
7898 type.getQualifier().storage = EvqGlobal;
7899 }
7900
7901 // make const and initialization consistent
7902 fixConstInit(loc, identifier, type, initializer);
7903
7904 // Check for redeclaration of built-ins and/or attempting to declare a reserved name
7905 TSymbol* symbol = nullptr;
7906
7907 inheritGlobalDefaults(type.getQualifier());
7908
7909 const bool flattenVar = shouldFlatten(type, type.getQualifier().storage, true);
7910
7911 // correct IO in the type
7912 switch (type.getQualifier().storage) {
7913 case EvqGlobal:
7914 case EvqTemporary:
7915 clearUniformInputOutput(type.getQualifier());
7916 break;
7917 case EvqUniform:
7918 case EvqBuffer:
7919 correctUniform(type.getQualifier());
7920 if (type.isStruct()) {
7921 auto it = ioTypeMap.find(type.getStruct());
7922 if (it != ioTypeMap.end())
7923 type.setStruct(it->second.uniform);
7924 }
7925
7926 break;
7927 default:
7928 break;
7929 }
7930
7931 // Declare the variable
7932 if (type.isArray()) {
7933 // array case
7934 declareArray(loc, identifier, type, symbol, !flattenVar);
7935 } else {
7936 // non-array case
7937 if (symbol == nullptr)
7938 symbol = declareNonArray(loc, identifier, type, !flattenVar);
7939 else if (type != symbol->getType())
7940 error(loc, "cannot change the type of", "redeclaration", symbol->getName().c_str());
7941 }
7942
7943 if (symbol == nullptr)
7944 return nullptr;
7945
7946 if (flattenVar)
7947 flatten(*symbol->getAsVariable(), symbolTable.atGlobalLevel());
7948
7949 if (initializer == nullptr)
7950 return nullptr;
7951
7952 // Deal with initializer
7953 TVariable* variable = symbol->getAsVariable();
7954 if (variable == nullptr) {
7955 error(loc, "initializer requires a variable, not a member", identifier.c_str(), "");
7956 return nullptr;
7957 }
7958 return executeInitializer(loc, initializer, variable);
7959 }
7960
7961 // Pick up global defaults from the provide global defaults into dst.
inheritGlobalDefaults(TQualifier & dst) const7962 void HlslParseContext::inheritGlobalDefaults(TQualifier& dst) const
7963 {
7964 if (dst.storage == EvqVaryingOut) {
7965 if (! dst.hasStream() && language == EShLangGeometry)
7966 dst.layoutStream = globalOutputDefaults.layoutStream;
7967 if (! dst.hasXfbBuffer())
7968 dst.layoutXfbBuffer = globalOutputDefaults.layoutXfbBuffer;
7969 }
7970 }
7971
7972 //
7973 // Make an internal-only variable whose name is for debug purposes only
7974 // and won't be searched for. Callers will only use the return value to use
7975 // the variable, not the name to look it up. It is okay if the name
7976 // is the same as other names; there won't be any conflict.
7977 //
makeInternalVariable(const char * name,const TType & type) const7978 TVariable* HlslParseContext::makeInternalVariable(const char* name, const TType& type) const
7979 {
7980 TString* nameString = NewPoolTString(name);
7981 TVariable* variable = new TVariable(nameString, type);
7982 symbolTable.makeInternalVariable(*variable);
7983
7984 return variable;
7985 }
7986
7987 // Make a symbol node holding a new internal temporary variable.
makeInternalVariableNode(const TSourceLoc & loc,const char * name,const TType & type) const7988 TIntermSymbol* HlslParseContext::makeInternalVariableNode(const TSourceLoc& loc, const char* name,
7989 const TType& type) const
7990 {
7991 TVariable* tmpVar = makeInternalVariable(name, type);
7992 tmpVar->getWritableType().getQualifier().makeTemporary();
7993
7994 return intermediate.addSymbol(*tmpVar, loc);
7995 }
7996
7997 //
7998 // Declare a non-array variable, the main point being there is no redeclaration
7999 // for resizing allowed.
8000 //
8001 // Return the successfully declared variable.
8002 //
declareNonArray(const TSourceLoc & loc,const TString & identifier,const TType & type,bool track)8003 TVariable* HlslParseContext::declareNonArray(const TSourceLoc& loc, const TString& identifier, const TType& type,
8004 bool track)
8005 {
8006 // make a new variable
8007 TVariable* variable = new TVariable(&identifier, type);
8008
8009 // add variable to symbol table
8010 if (symbolTable.insert(*variable)) {
8011 if (track && symbolTable.atGlobalLevel())
8012 trackLinkage(*variable);
8013 return variable;
8014 }
8015
8016 error(loc, "redefinition", variable->getName().c_str(), "");
8017 return nullptr;
8018 }
8019
8020 //
8021 // Handle all types of initializers from the grammar.
8022 //
8023 // Returning nullptr just means there is no code to execute to handle the
8024 // initializer, which will, for example, be the case for constant initializers.
8025 //
8026 // Returns a subtree that accomplished the initialization.
8027 //
executeInitializer(const TSourceLoc & loc,TIntermTyped * initializer,TVariable * variable)8028 TIntermNode* HlslParseContext::executeInitializer(const TSourceLoc& loc, TIntermTyped* initializer, TVariable* variable)
8029 {
8030 //
8031 // Identifier must be of type constant, a global, or a temporary, and
8032 // starting at version 120, desktop allows uniforms to have initializers.
8033 //
8034 TStorageQualifier qualifier = variable->getType().getQualifier().storage;
8035
8036 //
8037 // If the initializer was from braces { ... }, we convert the whole subtree to a
8038 // constructor-style subtree, allowing the rest of the code to operate
8039 // identically for both kinds of initializers.
8040 //
8041 //
8042 // Type can't be deduced from the initializer list, so a skeletal type to
8043 // follow has to be passed in. Constness and specialization-constness
8044 // should be deduced bottom up, not dictated by the skeletal type.
8045 //
8046 TType skeletalType;
8047 skeletalType.shallowCopy(variable->getType());
8048 skeletalType.getQualifier().makeTemporary();
8049 if (initializer->getAsAggregate() && initializer->getAsAggregate()->getOp() == EOpNull)
8050 initializer = convertInitializerList(loc, skeletalType, initializer, nullptr);
8051 if (initializer == nullptr) {
8052 // error recovery; don't leave const without constant values
8053 if (qualifier == EvqConst)
8054 variable->getWritableType().getQualifier().storage = EvqTemporary;
8055 return nullptr;
8056 }
8057
8058 // Fix outer arrayness if variable is unsized, getting size from the initializer
8059 if (initializer->getType().isSizedArray() && variable->getType().isUnsizedArray())
8060 variable->getWritableType().changeOuterArraySize(initializer->getType().getOuterArraySize());
8061
8062 // Inner arrayness can also get set by an initializer
8063 if (initializer->getType().isArrayOfArrays() && variable->getType().isArrayOfArrays() &&
8064 initializer->getType().getArraySizes()->getNumDims() ==
8065 variable->getType().getArraySizes()->getNumDims()) {
8066 // adopt unsized sizes from the initializer's sizes
8067 for (int d = 1; d < variable->getType().getArraySizes()->getNumDims(); ++d) {
8068 if (variable->getType().getArraySizes()->getDimSize(d) == UnsizedArraySize) {
8069 variable->getWritableType().getArraySizes()->setDimSize(d,
8070 initializer->getType().getArraySizes()->getDimSize(d));
8071 }
8072 }
8073 }
8074
8075 // Uniform and global consts require a constant initializer
8076 if (qualifier == EvqUniform && initializer->getType().getQualifier().storage != EvqConst) {
8077 error(loc, "uniform initializers must be constant", "=", "'%s'", variable->getType().getCompleteString().c_str());
8078 variable->getWritableType().getQualifier().storage = EvqTemporary;
8079 return nullptr;
8080 }
8081
8082 // Const variables require a constant initializer
8083 if (qualifier == EvqConst) {
8084 if (initializer->getType().getQualifier().storage != EvqConst) {
8085 variable->getWritableType().getQualifier().storage = EvqConstReadOnly;
8086 qualifier = EvqConstReadOnly;
8087 }
8088 }
8089
8090 if (qualifier == EvqConst || qualifier == EvqUniform) {
8091 // Compile-time tagging of the variable with its constant value...
8092
8093 initializer = intermediate.addConversion(EOpAssign, variable->getType(), initializer);
8094 if (initializer != nullptr && variable->getType() != initializer->getType())
8095 initializer = intermediate.addUniShapeConversion(EOpAssign, variable->getType(), initializer);
8096 if (initializer == nullptr || !initializer->getAsConstantUnion() ||
8097 variable->getType() != initializer->getType()) {
8098 error(loc, "non-matching or non-convertible constant type for const initializer",
8099 variable->getType().getStorageQualifierString(), "");
8100 variable->getWritableType().getQualifier().storage = EvqTemporary;
8101 return nullptr;
8102 }
8103
8104 variable->setConstArray(initializer->getAsConstantUnion()->getConstArray());
8105 } else {
8106 // normal assigning of a value to a variable...
8107 specializationCheck(loc, initializer->getType(), "initializer");
8108 TIntermSymbol* intermSymbol = intermediate.addSymbol(*variable, loc);
8109 TIntermNode* initNode = handleAssign(loc, EOpAssign, intermSymbol, initializer);
8110 if (initNode == nullptr)
8111 assignError(loc, "=", intermSymbol->getCompleteString(), initializer->getCompleteString());
8112 return initNode;
8113 }
8114
8115 return nullptr;
8116 }
8117
8118 //
8119 // Reprocess any initializer-list { ... } parts of the initializer.
8120 // Need to hierarchically assign correct types and implicit
8121 // conversions. Will do this mimicking the same process used for
8122 // creating a constructor-style initializer, ensuring we get the
8123 // same form.
8124 //
8125 // Returns a node representing an expression for the initializer list expressed
8126 // as the correct type.
8127 //
8128 // Returns nullptr if there is an error.
8129 //
convertInitializerList(const TSourceLoc & loc,const TType & type,TIntermTyped * initializer,TIntermTyped * scalarInit)8130 TIntermTyped* HlslParseContext::convertInitializerList(const TSourceLoc& loc, const TType& type,
8131 TIntermTyped* initializer, TIntermTyped* scalarInit)
8132 {
8133 // Will operate recursively. Once a subtree is found that is constructor style,
8134 // everything below it is already good: Only the "top part" of the initializer
8135 // can be an initializer list, where "top part" can extend for several (or all) levels.
8136
8137 // see if we have bottomed out in the tree within the initializer-list part
8138 TIntermAggregate* initList = initializer->getAsAggregate();
8139 if (initList == nullptr || initList->getOp() != EOpNull) {
8140 // We don't have a list, but if it's a scalar and the 'type' is a
8141 // composite, we need to lengthen below to make it useful.
8142 // Otherwise, this is an already formed object to initialize with.
8143 if (type.isScalar() || !initializer->getType().isScalar())
8144 return initializer;
8145 else
8146 initList = intermediate.makeAggregate(initializer);
8147 }
8148
8149 // Of the initializer-list set of nodes, need to process bottom up,
8150 // so recurse deep, then process on the way up.
8151
8152 // Go down the tree here...
8153 if (type.isArray()) {
8154 // The type's array might be unsized, which could be okay, so base sizes on the size of the aggregate.
8155 // Later on, initializer execution code will deal with array size logic.
8156 TType arrayType;
8157 arrayType.shallowCopy(type); // sharing struct stuff is fine
8158 arrayType.copyArraySizes(*type.getArraySizes()); // but get a fresh copy of the array information, to edit below
8159
8160 // edit array sizes to fill in unsized dimensions
8161 if (type.isUnsizedArray())
8162 arrayType.changeOuterArraySize((int)initList->getSequence().size());
8163
8164 // set unsized array dimensions that can be derived from the initializer's first element
8165 if (arrayType.isArrayOfArrays() && initList->getSequence().size() > 0) {
8166 TIntermTyped* firstInit = initList->getSequence()[0]->getAsTyped();
8167 if (firstInit->getType().isArray() &&
8168 arrayType.getArraySizes()->getNumDims() == firstInit->getType().getArraySizes()->getNumDims() + 1) {
8169 for (int d = 1; d < arrayType.getArraySizes()->getNumDims(); ++d) {
8170 if (arrayType.getArraySizes()->getDimSize(d) == UnsizedArraySize)
8171 arrayType.getArraySizes()->setDimSize(d, firstInit->getType().getArraySizes()->getDimSize(d - 1));
8172 }
8173 }
8174 }
8175
8176 // lengthen list to be long enough
8177 lengthenList(loc, initList->getSequence(), arrayType.getOuterArraySize(), scalarInit);
8178
8179 // recursively process each element
8180 TType elementType(arrayType, 0); // dereferenced type
8181 for (int i = 0; i < arrayType.getOuterArraySize(); ++i) {
8182 initList->getSequence()[i] = convertInitializerList(loc, elementType,
8183 initList->getSequence()[i]->getAsTyped(), scalarInit);
8184 if (initList->getSequence()[i] == nullptr)
8185 return nullptr;
8186 }
8187
8188 return addConstructor(loc, initList, arrayType);
8189 } else if (type.isStruct()) {
8190 // do we have implicit assignments to opaques?
8191 for (size_t i = initList->getSequence().size(); i < type.getStruct()->size(); ++i) {
8192 if ((*type.getStruct())[i].type->containsOpaque()) {
8193 error(loc, "cannot implicitly initialize opaque members", "initializer list", "");
8194 return nullptr;
8195 }
8196 }
8197
8198 // lengthen list to be long enough
8199 lengthenList(loc, initList->getSequence(), static_cast<int>(type.getStruct()->size()), scalarInit);
8200
8201 if (type.getStruct()->size() != initList->getSequence().size()) {
8202 error(loc, "wrong number of structure members", "initializer list", "");
8203 return nullptr;
8204 }
8205 for (size_t i = 0; i < type.getStruct()->size(); ++i) {
8206 initList->getSequence()[i] = convertInitializerList(loc, *(*type.getStruct())[i].type,
8207 initList->getSequence()[i]->getAsTyped(), scalarInit);
8208 if (initList->getSequence()[i] == nullptr)
8209 return nullptr;
8210 }
8211 } else if (type.isMatrix()) {
8212 if (type.computeNumComponents() == (int)initList->getSequence().size()) {
8213 // This means the matrix is initialized component-wise, rather than as
8214 // a series of rows and columns. We can just use the list directly as
8215 // a constructor; no further processing needed.
8216 } else {
8217 // lengthen list to be long enough
8218 lengthenList(loc, initList->getSequence(), type.getMatrixCols(), scalarInit);
8219
8220 if (type.getMatrixCols() != (int)initList->getSequence().size()) {
8221 error(loc, "wrong number of matrix columns:", "initializer list", type.getCompleteString().c_str());
8222 return nullptr;
8223 }
8224 TType vectorType(type, 0); // dereferenced type
8225 for (int i = 0; i < type.getMatrixCols(); ++i) {
8226 initList->getSequence()[i] = convertInitializerList(loc, vectorType,
8227 initList->getSequence()[i]->getAsTyped(), scalarInit);
8228 if (initList->getSequence()[i] == nullptr)
8229 return nullptr;
8230 }
8231 }
8232 } else if (type.isVector()) {
8233 // lengthen list to be long enough
8234 lengthenList(loc, initList->getSequence(), type.getVectorSize(), scalarInit);
8235
8236 // error check; we're at bottom, so work is finished below
8237 if (type.getVectorSize() != (int)initList->getSequence().size()) {
8238 error(loc, "wrong vector size (or rows in a matrix column):", "initializer list",
8239 type.getCompleteString().c_str());
8240 return nullptr;
8241 }
8242 } else if (type.isScalar()) {
8243 // lengthen list to be long enough
8244 lengthenList(loc, initList->getSequence(), 1, scalarInit);
8245
8246 if ((int)initList->getSequence().size() != 1) {
8247 error(loc, "scalar expected one element:", "initializer list", type.getCompleteString().c_str());
8248 return nullptr;
8249 }
8250 } else {
8251 error(loc, "unexpected initializer-list type:", "initializer list", type.getCompleteString().c_str());
8252 return nullptr;
8253 }
8254
8255 // Now that the subtree is processed, process this node as if the
8256 // initializer list is a set of arguments to a constructor.
8257 TIntermTyped* emulatedConstructorArguments;
8258 if (initList->getSequence().size() == 1)
8259 emulatedConstructorArguments = initList->getSequence()[0]->getAsTyped();
8260 else
8261 emulatedConstructorArguments = initList;
8262
8263 return addConstructor(loc, emulatedConstructorArguments, type);
8264 }
8265
8266 // Lengthen list to be long enough to cover any gap from the current list size
8267 // to 'size'. If the list is longer, do nothing.
8268 // The value to lengthen with is the default for short lists.
8269 //
8270 // By default, lists that are too short due to lack of initializers initialize to zero.
8271 // Alternatively, it could be a scalar initializer for a structure. Both cases are handled,
8272 // based on whether something is passed in as 'scalarInit'.
8273 //
8274 // 'scalarInit' must be safe to use each time this is called (no side effects replication).
8275 //
lengthenList(const TSourceLoc & loc,TIntermSequence & list,int size,TIntermTyped * scalarInit)8276 void HlslParseContext::lengthenList(const TSourceLoc& loc, TIntermSequence& list, int size, TIntermTyped* scalarInit)
8277 {
8278 for (int c = (int)list.size(); c < size; ++c) {
8279 if (scalarInit == nullptr)
8280 list.push_back(intermediate.addConstantUnion(0, loc));
8281 else
8282 list.push_back(scalarInit);
8283 }
8284 }
8285
8286 //
8287 // Test for the correctness of the parameters passed to various constructor functions
8288 // and also convert them to the right data type, if allowed and required.
8289 //
8290 // Returns nullptr for an error or the constructed node (aggregate or typed) for no error.
8291 //
handleConstructor(const TSourceLoc & loc,TIntermTyped * node,const TType & type)8292 TIntermTyped* HlslParseContext::handleConstructor(const TSourceLoc& loc, TIntermTyped* node, const TType& type)
8293 {
8294 if (node == nullptr)
8295 return nullptr;
8296
8297 // Construct identical type
8298 if (type == node->getType())
8299 return node;
8300
8301 // Handle the idiom "(struct type)<scalar value>"
8302 if (type.isStruct() && isScalarConstructor(node)) {
8303 // 'node' will almost always get used multiple times, so should not be used directly,
8304 // it would create a DAG instead of a tree, which might be okay (would
8305 // like to formalize that for constants and symbols), but if it has
8306 // side effects, they would get executed multiple times, which is not okay.
8307 if (node->getAsConstantUnion() == nullptr && node->getAsSymbolNode() == nullptr) {
8308 TIntermAggregate* seq = intermediate.makeAggregate(loc);
8309 TIntermSymbol* copy = makeInternalVariableNode(loc, "scalarCopy", node->getType());
8310 seq = intermediate.growAggregate(seq, intermediate.addBinaryNode(EOpAssign, copy, node, loc));
8311 seq = intermediate.growAggregate(seq, convertInitializerList(loc, type, intermediate.makeAggregate(loc), copy));
8312 seq->setOp(EOpComma);
8313 seq->setType(type);
8314 return seq;
8315 } else
8316 return convertInitializerList(loc, type, intermediate.makeAggregate(loc), node);
8317 }
8318
8319 return addConstructor(loc, node, type);
8320 }
8321
8322 // Add a constructor, either from the grammar, or other programmatic reasons.
8323 //
8324 // 'node' is what to construct from.
8325 // 'type' is what type to construct.
8326 //
8327 // Returns the constructed object.
8328 // Return nullptr if it can't be done.
8329 //
addConstructor(const TSourceLoc & loc,TIntermTyped * node,const TType & type)8330 TIntermTyped* HlslParseContext::addConstructor(const TSourceLoc& loc, TIntermTyped* node, const TType& type)
8331 {
8332 TIntermAggregate* aggrNode = node->getAsAggregate();
8333 TOperator op = intermediate.mapTypeToConstructorOp(type);
8334
8335 if (op == EOpConstructTextureSampler)
8336 return intermediate.setAggregateOperator(aggrNode, op, type, loc);
8337
8338 TTypeList::const_iterator memberTypes;
8339 if (op == EOpConstructStruct)
8340 memberTypes = type.getStruct()->begin();
8341
8342 TType elementType;
8343 if (type.isArray()) {
8344 TType dereferenced(type, 0);
8345 elementType.shallowCopy(dereferenced);
8346 } else
8347 elementType.shallowCopy(type);
8348
8349 bool singleArg;
8350 if (aggrNode != nullptr) {
8351 if (aggrNode->getOp() != EOpNull)
8352 singleArg = true;
8353 else
8354 singleArg = false;
8355 } else
8356 singleArg = true;
8357
8358 TIntermTyped *newNode;
8359 if (singleArg) {
8360 // Handle array -> array conversion
8361 // Constructing an array of one type from an array of another type is allowed,
8362 // assuming there are enough components available (semantic-checked earlier).
8363 if (type.isArray() && node->isArray())
8364 newNode = convertArray(node, type);
8365
8366 // If structure constructor or array constructor is being called
8367 // for only one parameter inside the aggregate, we need to call constructAggregate function once.
8368 else if (type.isArray())
8369 newNode = constructAggregate(node, elementType, 1, node->getLoc());
8370 else if (op == EOpConstructStruct)
8371 newNode = constructAggregate(node, *(*memberTypes).type, 1, node->getLoc());
8372 else {
8373 // shape conversion for matrix constructor from scalar. HLSL semantics are: scalar
8374 // is replicated into every element of the matrix (not just the diagnonal), so
8375 // that is handled specially here.
8376 if (type.isMatrix() && node->getType().isScalarOrVec1())
8377 node = intermediate.addShapeConversion(type, node);
8378
8379 newNode = constructBuiltIn(type, op, node, node->getLoc(), false);
8380 }
8381
8382 if (newNode && (type.isArray() || op == EOpConstructStruct))
8383 newNode = intermediate.setAggregateOperator(newNode, EOpConstructStruct, type, loc);
8384
8385 return newNode;
8386 }
8387
8388 //
8389 // Handle list of arguments.
8390 //
8391 TIntermSequence& sequenceVector = aggrNode->getSequence(); // Stores the information about the parameter to the constructor
8392 // if the structure constructor contains more than one parameter, then construct
8393 // each parameter
8394
8395 int paramCount = 0; // keeps a track of the constructor parameter number being checked
8396
8397 // for each parameter to the constructor call, check to see if the right type is passed or convert them
8398 // to the right type if possible (and allowed).
8399 // for structure constructors, just check if the right type is passed, no conversion is allowed.
8400
8401 for (TIntermSequence::iterator p = sequenceVector.begin();
8402 p != sequenceVector.end(); p++, paramCount++) {
8403 if (type.isArray())
8404 newNode = constructAggregate(*p, elementType, paramCount + 1, node->getLoc());
8405 else if (op == EOpConstructStruct)
8406 newNode = constructAggregate(*p, *(memberTypes[paramCount]).type, paramCount + 1, node->getLoc());
8407 else
8408 newNode = constructBuiltIn(type, op, (*p)->getAsTyped(), node->getLoc(), true);
8409
8410 if (newNode)
8411 *p = newNode;
8412 else
8413 return nullptr;
8414 }
8415
8416 TIntermTyped* constructor = intermediate.setAggregateOperator(aggrNode, op, type, loc);
8417
8418 return constructor;
8419 }
8420
8421 // Function for constructor implementation. Calls addUnaryMath with appropriate EOp value
8422 // for the parameter to the constructor (passed to this function). Essentially, it converts
8423 // the parameter types correctly. If a constructor expects an int (like ivec2) and is passed a
8424 // float, then float is converted to int.
8425 //
8426 // Returns nullptr for an error or the constructed node.
8427 //
constructBuiltIn(const TType & type,TOperator op,TIntermTyped * node,const TSourceLoc & loc,bool subset)8428 TIntermTyped* HlslParseContext::constructBuiltIn(const TType& type, TOperator op, TIntermTyped* node,
8429 const TSourceLoc& loc, bool subset)
8430 {
8431 TIntermTyped* newNode;
8432 TOperator basicOp;
8433
8434 //
8435 // First, convert types as needed.
8436 //
8437 switch (op) {
8438 case EOpConstructF16Vec2:
8439 case EOpConstructF16Vec3:
8440 case EOpConstructF16Vec4:
8441 case EOpConstructF16Mat2x2:
8442 case EOpConstructF16Mat2x3:
8443 case EOpConstructF16Mat2x4:
8444 case EOpConstructF16Mat3x2:
8445 case EOpConstructF16Mat3x3:
8446 case EOpConstructF16Mat3x4:
8447 case EOpConstructF16Mat4x2:
8448 case EOpConstructF16Mat4x3:
8449 case EOpConstructF16Mat4x4:
8450 case EOpConstructFloat16:
8451 basicOp = EOpConstructFloat16;
8452 break;
8453
8454 case EOpConstructVec2:
8455 case EOpConstructVec3:
8456 case EOpConstructVec4:
8457 case EOpConstructMat2x2:
8458 case EOpConstructMat2x3:
8459 case EOpConstructMat2x4:
8460 case EOpConstructMat3x2:
8461 case EOpConstructMat3x3:
8462 case EOpConstructMat3x4:
8463 case EOpConstructMat4x2:
8464 case EOpConstructMat4x3:
8465 case EOpConstructMat4x4:
8466 case EOpConstructFloat:
8467 basicOp = EOpConstructFloat;
8468 break;
8469
8470 case EOpConstructDVec2:
8471 case EOpConstructDVec3:
8472 case EOpConstructDVec4:
8473 case EOpConstructDMat2x2:
8474 case EOpConstructDMat2x3:
8475 case EOpConstructDMat2x4:
8476 case EOpConstructDMat3x2:
8477 case EOpConstructDMat3x3:
8478 case EOpConstructDMat3x4:
8479 case EOpConstructDMat4x2:
8480 case EOpConstructDMat4x3:
8481 case EOpConstructDMat4x4:
8482 case EOpConstructDouble:
8483 basicOp = EOpConstructDouble;
8484 break;
8485
8486 case EOpConstructI16Vec2:
8487 case EOpConstructI16Vec3:
8488 case EOpConstructI16Vec4:
8489 case EOpConstructInt16:
8490 basicOp = EOpConstructInt16;
8491 break;
8492
8493 case EOpConstructIVec2:
8494 case EOpConstructIVec3:
8495 case EOpConstructIVec4:
8496 case EOpConstructIMat2x2:
8497 case EOpConstructIMat2x3:
8498 case EOpConstructIMat2x4:
8499 case EOpConstructIMat3x2:
8500 case EOpConstructIMat3x3:
8501 case EOpConstructIMat3x4:
8502 case EOpConstructIMat4x2:
8503 case EOpConstructIMat4x3:
8504 case EOpConstructIMat4x4:
8505 case EOpConstructInt:
8506 basicOp = EOpConstructInt;
8507 break;
8508
8509 case EOpConstructU16Vec2:
8510 case EOpConstructU16Vec3:
8511 case EOpConstructU16Vec4:
8512 case EOpConstructUint16:
8513 basicOp = EOpConstructUint16;
8514 break;
8515
8516 case EOpConstructUVec2:
8517 case EOpConstructUVec3:
8518 case EOpConstructUVec4:
8519 case EOpConstructUMat2x2:
8520 case EOpConstructUMat2x3:
8521 case EOpConstructUMat2x4:
8522 case EOpConstructUMat3x2:
8523 case EOpConstructUMat3x3:
8524 case EOpConstructUMat3x4:
8525 case EOpConstructUMat4x2:
8526 case EOpConstructUMat4x3:
8527 case EOpConstructUMat4x4:
8528 case EOpConstructUint:
8529 basicOp = EOpConstructUint;
8530 break;
8531
8532 case EOpConstructBVec2:
8533 case EOpConstructBVec3:
8534 case EOpConstructBVec4:
8535 case EOpConstructBMat2x2:
8536 case EOpConstructBMat2x3:
8537 case EOpConstructBMat2x4:
8538 case EOpConstructBMat3x2:
8539 case EOpConstructBMat3x3:
8540 case EOpConstructBMat3x4:
8541 case EOpConstructBMat4x2:
8542 case EOpConstructBMat4x3:
8543 case EOpConstructBMat4x4:
8544 case EOpConstructBool:
8545 basicOp = EOpConstructBool;
8546 break;
8547
8548 default:
8549 error(loc, "unsupported construction", "", "");
8550
8551 return nullptr;
8552 }
8553 newNode = intermediate.addUnaryMath(basicOp, node, node->getLoc());
8554 if (newNode == nullptr) {
8555 error(loc, "can't convert", "constructor", "");
8556 return nullptr;
8557 }
8558
8559 //
8560 // Now, if there still isn't an operation to do the construction, and we need one, add one.
8561 //
8562
8563 // Otherwise, skip out early.
8564 if (subset || (newNode != node && newNode->getType() == type))
8565 return newNode;
8566
8567 // setAggregateOperator will insert a new node for the constructor, as needed.
8568 return intermediate.setAggregateOperator(newNode, op, type, loc);
8569 }
8570
8571 // Convert the array in node to the requested type, which is also an array.
8572 // Returns nullptr on failure, otherwise returns aggregate holding the list of
8573 // elements needed to construct the array.
convertArray(TIntermTyped * node,const TType & type)8574 TIntermTyped* HlslParseContext::convertArray(TIntermTyped* node, const TType& type)
8575 {
8576 assert(node->isArray() && type.isArray());
8577 if (node->getType().computeNumComponents() < type.computeNumComponents())
8578 return nullptr;
8579
8580 // TODO: write an argument replicator, for the case the argument should not be
8581 // executed multiple times, yet multiple copies are needed.
8582
8583 TIntermTyped* constructee = node->getAsTyped();
8584 // track where we are in consuming the argument
8585 int constructeeElement = 0;
8586 int constructeeComponent = 0;
8587
8588 // bump up to the next component to consume
8589 const auto getNextComponent = [&]() {
8590 TIntermTyped* component;
8591 component = handleBracketDereference(node->getLoc(), constructee,
8592 intermediate.addConstantUnion(constructeeElement, node->getLoc()));
8593 if (component->isVector())
8594 component = handleBracketDereference(node->getLoc(), component,
8595 intermediate.addConstantUnion(constructeeComponent, node->getLoc()));
8596 // bump component pointer up
8597 ++constructeeComponent;
8598 if (constructeeComponent == constructee->getVectorSize()) {
8599 constructeeComponent = 0;
8600 ++constructeeElement;
8601 }
8602 return component;
8603 };
8604
8605 // make one subnode per constructed array element
8606 TIntermAggregate* constructor = nullptr;
8607 TType derefType(type, 0);
8608 TType speculativeComponentType(derefType, 0);
8609 TType* componentType = derefType.isVector() ? &speculativeComponentType : &derefType;
8610 TOperator componentOp = intermediate.mapTypeToConstructorOp(*componentType);
8611 TType crossType(node->getBasicType(), EvqTemporary, type.getVectorSize());
8612 for (int e = 0; e < type.getOuterArraySize(); ++e) {
8613 // construct an element
8614 TIntermTyped* elementArg;
8615 if (type.getVectorSize() == constructee->getVectorSize()) {
8616 // same element shape
8617 elementArg = handleBracketDereference(node->getLoc(), constructee,
8618 intermediate.addConstantUnion(e, node->getLoc()));
8619 } else {
8620 // mismatched element shapes
8621 if (type.getVectorSize() == 1)
8622 elementArg = getNextComponent();
8623 else {
8624 // make a vector
8625 TIntermAggregate* elementConstructee = nullptr;
8626 for (int c = 0; c < type.getVectorSize(); ++c)
8627 elementConstructee = intermediate.growAggregate(elementConstructee, getNextComponent());
8628 elementArg = addConstructor(node->getLoc(), elementConstructee, crossType);
8629 }
8630 }
8631 // convert basic types
8632 elementArg = intermediate.addConversion(componentOp, derefType, elementArg);
8633 if (elementArg == nullptr)
8634 return nullptr;
8635 // combine with top-level constructor
8636 constructor = intermediate.growAggregate(constructor, elementArg);
8637 }
8638
8639 return constructor;
8640 }
8641
8642 // This function tests for the type of the parameters to the structure or array constructor. Raises
8643 // an error message if the expected type does not match the parameter passed to the constructor.
8644 //
8645 // Returns nullptr for an error or the input node itself if the expected and the given parameter types match.
8646 //
constructAggregate(TIntermNode * node,const TType & type,int paramCount,const TSourceLoc & loc)8647 TIntermTyped* HlslParseContext::constructAggregate(TIntermNode* node, const TType& type, int paramCount,
8648 const TSourceLoc& loc)
8649 {
8650 // Handle cases that map more 1:1 between constructor arguments and constructed.
8651 TIntermTyped* converted = intermediate.addConversion(EOpConstructStruct, type, node->getAsTyped());
8652 if (converted == nullptr || converted->getType() != type) {
8653 error(loc, "", "constructor", "cannot convert parameter %d from '%s' to '%s'", paramCount,
8654 node->getAsTyped()->getType().getCompleteString().c_str(), type.getCompleteString().c_str());
8655
8656 return nullptr;
8657 }
8658
8659 return converted;
8660 }
8661
8662 //
8663 // Do everything needed to add an interface block.
8664 //
declareBlock(const TSourceLoc & loc,TType & type,const TString * instanceName)8665 void HlslParseContext::declareBlock(const TSourceLoc& loc, TType& type, const TString* instanceName)
8666 {
8667 assert(type.getWritableStruct() != nullptr);
8668
8669 // Clean up top-level decorations that don't belong.
8670 switch (type.getQualifier().storage) {
8671 case EvqUniform:
8672 case EvqBuffer:
8673 correctUniform(type.getQualifier());
8674 break;
8675 case EvqVaryingIn:
8676 correctInput(type.getQualifier());
8677 break;
8678 case EvqVaryingOut:
8679 correctOutput(type.getQualifier());
8680 break;
8681 default:
8682 break;
8683 }
8684
8685 TTypeList& typeList = *type.getWritableStruct();
8686 // fix and check for member storage qualifiers and types that don't belong within a block
8687 for (unsigned int member = 0; member < typeList.size(); ++member) {
8688 TType& memberType = *typeList[member].type;
8689 TQualifier& memberQualifier = memberType.getQualifier();
8690 const TSourceLoc& memberLoc = typeList[member].loc;
8691 globalQualifierFix(memberLoc, memberQualifier);
8692 memberQualifier.storage = type.getQualifier().storage;
8693
8694 if (memberType.isStruct()) {
8695 // clean up and pick up the right set of decorations
8696 auto it = ioTypeMap.find(memberType.getStruct());
8697 switch (type.getQualifier().storage) {
8698 case EvqUniform:
8699 case EvqBuffer:
8700 correctUniform(type.getQualifier());
8701 if (it != ioTypeMap.end() && it->second.uniform)
8702 memberType.setStruct(it->second.uniform);
8703 break;
8704 case EvqVaryingIn:
8705 correctInput(type.getQualifier());
8706 if (it != ioTypeMap.end() && it->second.input)
8707 memberType.setStruct(it->second.input);
8708 break;
8709 case EvqVaryingOut:
8710 correctOutput(type.getQualifier());
8711 if (it != ioTypeMap.end() && it->second.output)
8712 memberType.setStruct(it->second.output);
8713 break;
8714 default:
8715 break;
8716 }
8717 }
8718 }
8719
8720 // Make default block qualification, and adjust the member qualifications
8721
8722 TQualifier defaultQualification;
8723 switch (type.getQualifier().storage) {
8724 case EvqUniform: defaultQualification = globalUniformDefaults; break;
8725 case EvqBuffer: defaultQualification = globalBufferDefaults; break;
8726 case EvqVaryingIn: defaultQualification = globalInputDefaults; break;
8727 case EvqVaryingOut: defaultQualification = globalOutputDefaults; break;
8728 default: defaultQualification.clear(); break;
8729 }
8730
8731 // Special case for "push_constant uniform", which has a default of std430,
8732 // contrary to normal uniform defaults, and can't have a default tracked for it.
8733 if (type.getQualifier().layoutPushConstant && ! type.getQualifier().hasPacking())
8734 type.getQualifier().layoutPacking = ElpStd430;
8735
8736 // fix and check for member layout qualifiers
8737
8738 mergeObjectLayoutQualifiers(defaultQualification, type.getQualifier(), true);
8739
8740 bool memberWithLocation = false;
8741 bool memberWithoutLocation = false;
8742 for (unsigned int member = 0; member < typeList.size(); ++member) {
8743 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8744 const TSourceLoc& memberLoc = typeList[member].loc;
8745 if (memberQualifier.hasStream()) {
8746 if (defaultQualification.layoutStream != memberQualifier.layoutStream)
8747 error(memberLoc, "member cannot contradict block", "stream", "");
8748 }
8749
8750 // "This includes a block's inheritance of the
8751 // current global default buffer, a block member's inheritance of the block's
8752 // buffer, and the requirement that any *xfb_buffer* declared on a block
8753 // member must match the buffer inherited from the block."
8754 if (memberQualifier.hasXfbBuffer()) {
8755 if (defaultQualification.layoutXfbBuffer != memberQualifier.layoutXfbBuffer)
8756 error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_buffer", "");
8757 }
8758
8759 if (memberQualifier.hasLocation()) {
8760 switch (type.getQualifier().storage) {
8761 case EvqVaryingIn:
8762 case EvqVaryingOut:
8763 memberWithLocation = true;
8764 break;
8765 default:
8766 break;
8767 }
8768 } else
8769 memberWithoutLocation = true;
8770
8771 TQualifier newMemberQualification = defaultQualification;
8772 mergeQualifiers(newMemberQualification, memberQualifier);
8773 memberQualifier = newMemberQualification;
8774 }
8775
8776 // Process the members
8777 fixBlockLocations(loc, type.getQualifier(), typeList, memberWithLocation, memberWithoutLocation);
8778 fixXfbOffsets(type.getQualifier(), typeList);
8779 fixBlockUniformOffsets(type.getQualifier(), typeList);
8780
8781 // reverse merge, so that currentBlockQualifier now has all layout information
8782 // (can't use defaultQualification directly, it's missing other non-layout-default-class qualifiers)
8783 mergeObjectLayoutQualifiers(type.getQualifier(), defaultQualification, true);
8784
8785 //
8786 // Build and add the interface block as a new type named 'blockName'
8787 //
8788
8789 // Use the instance name as the interface name if one exists, else the block name.
8790 const TString& interfaceName = (instanceName && !instanceName->empty()) ? *instanceName : type.getTypeName();
8791
8792 TType blockType(&typeList, interfaceName, type.getQualifier());
8793 if (type.isArray())
8794 blockType.transferArraySizes(type.getArraySizes());
8795
8796 // Add the variable, as anonymous or named instanceName.
8797 // Make an anonymous variable if no name was provided.
8798 if (instanceName == nullptr)
8799 instanceName = NewPoolTString("");
8800
8801 TVariable& variable = *new TVariable(instanceName, blockType);
8802 if (! symbolTable.insert(variable)) {
8803 if (*instanceName == "")
8804 error(loc, "nameless block contains a member that already has a name at global scope",
8805 "" /* blockName->c_str() */, "");
8806 else
8807 error(loc, "block instance name redefinition", variable.getName().c_str(), "");
8808
8809 return;
8810 }
8811
8812 // Save it in the AST for linker use.
8813 if (symbolTable.atGlobalLevel())
8814 trackLinkage(variable);
8815 }
8816
8817 //
8818 // "For a block, this process applies to the entire block, or until the first member
8819 // is reached that has a location layout qualifier. When a block member is declared with a location
8820 // qualifier, its location comes from that qualifier: The member's location qualifier overrides the block-level
8821 // declaration. Subsequent members are again assigned consecutive locations, based on the newest location,
8822 // until the next member declared with a location qualifier. The values used for locations do not have to be
8823 // declared in increasing order."
fixBlockLocations(const TSourceLoc & loc,TQualifier & qualifier,TTypeList & typeList,bool memberWithLocation,bool memberWithoutLocation)8824 void HlslParseContext::fixBlockLocations(const TSourceLoc& loc, TQualifier& qualifier, TTypeList& typeList, bool memberWithLocation, bool memberWithoutLocation)
8825 {
8826 // "If a block has no block-level location layout qualifier, it is required that either all or none of its members
8827 // have a location layout qualifier, or a compile-time error results."
8828 if (! qualifier.hasLocation() && memberWithLocation && memberWithoutLocation)
8829 error(loc, "either the block needs a location, or all members need a location, or no members have a location", "location", "");
8830 else {
8831 if (memberWithLocation) {
8832 // remove any block-level location and make it per *every* member
8833 int nextLocation = 0; // by the rule above, initial value is not relevant
8834 if (qualifier.hasAnyLocation()) {
8835 nextLocation = qualifier.layoutLocation;
8836 qualifier.layoutLocation = TQualifier::layoutLocationEnd;
8837 if (qualifier.hasComponent()) {
8838 // "It is a compile-time error to apply the *component* qualifier to a ... block"
8839 error(loc, "cannot apply to a block", "component", "");
8840 }
8841 if (qualifier.hasIndex()) {
8842 error(loc, "cannot apply to a block", "index", "");
8843 }
8844 }
8845 for (unsigned int member = 0; member < typeList.size(); ++member) {
8846 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8847 const TSourceLoc& memberLoc = typeList[member].loc;
8848 if (! memberQualifier.hasLocation()) {
8849 if (nextLocation >= (int)TQualifier::layoutLocationEnd)
8850 error(memberLoc, "location is too large", "location", "");
8851 memberQualifier.layoutLocation = nextLocation;
8852 memberQualifier.layoutComponent = 0;
8853 }
8854 nextLocation = memberQualifier.layoutLocation +
8855 intermediate.computeTypeLocationSize(*typeList[member].type, language);
8856 }
8857 }
8858 }
8859 }
8860
fixXfbOffsets(TQualifier & qualifier,TTypeList & typeList)8861 void HlslParseContext::fixXfbOffsets(TQualifier& qualifier, TTypeList& typeList)
8862 {
8863 // "If a block is qualified with xfb_offset, all its
8864 // members are assigned transform feedback buffer offsets. If a block is not qualified with xfb_offset, any
8865 // members of that block not qualified with an xfb_offset will not be assigned transform feedback buffer
8866 // offsets."
8867
8868 if (! qualifier.hasXfbBuffer() || ! qualifier.hasXfbOffset())
8869 return;
8870
8871 int nextOffset = qualifier.layoutXfbOffset;
8872 for (unsigned int member = 0; member < typeList.size(); ++member) {
8873 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8874 bool contains64BitType = false;
8875 bool contains32BitType = false;
8876 bool contains16BitType = false;
8877 int memberSize = intermediate.computeTypeXfbSize(*typeList[member].type, contains64BitType, contains32BitType, contains16BitType);
8878 // see if we need to auto-assign an offset to this member
8879 if (! memberQualifier.hasXfbOffset()) {
8880 // "if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8"
8881 if (contains64BitType)
8882 RoundToPow2(nextOffset, 8);
8883 else if (contains32BitType)
8884 RoundToPow2(nextOffset, 4);
8885 // "if applied to an aggregate containing a half float or 16-bit integer, the offset must also be a multiple of 2"
8886 else if (contains16BitType)
8887 RoundToPow2(nextOffset, 2);
8888 memberQualifier.layoutXfbOffset = nextOffset;
8889 } else
8890 nextOffset = memberQualifier.layoutXfbOffset;
8891 nextOffset += memberSize;
8892 }
8893
8894 // The above gave all block members an offset, so we can take it off the block now,
8895 // which will avoid double counting the offset usage.
8896 qualifier.layoutXfbOffset = TQualifier::layoutXfbOffsetEnd;
8897 }
8898
8899 // Calculate and save the offset of each block member, using the recursively
8900 // defined block offset rules and the user-provided offset and align.
8901 //
8902 // Also, compute and save the total size of the block. For the block's size, arrayness
8903 // is not taken into account, as each element is backed by a separate buffer.
8904 //
fixBlockUniformOffsets(const TQualifier & qualifier,TTypeList & typeList)8905 void HlslParseContext::fixBlockUniformOffsets(const TQualifier& qualifier, TTypeList& typeList)
8906 {
8907 if (! qualifier.isUniformOrBuffer())
8908 return;
8909 if (qualifier.layoutPacking != ElpStd140 && qualifier.layoutPacking != ElpStd430 && qualifier.layoutPacking != ElpScalar)
8910 return;
8911
8912 int offset = 0;
8913 int memberSize;
8914 for (unsigned int member = 0; member < typeList.size(); ++member) {
8915 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8916 const TSourceLoc& memberLoc = typeList[member].loc;
8917
8918 // "When align is applied to an array, it effects only the start of the array, not the array's internal stride."
8919
8920 // modify just the children's view of matrix layout, if there is one for this member
8921 TLayoutMatrix subMatrixLayout = typeList[member].type->getQualifier().layoutMatrix;
8922 int dummyStride;
8923 int memberAlignment = intermediate.getMemberAlignment(*typeList[member].type, memberSize, dummyStride,
8924 qualifier.layoutPacking,
8925 subMatrixLayout != ElmNone
8926 ? subMatrixLayout == ElmRowMajor
8927 : qualifier.layoutMatrix == ElmRowMajor);
8928 if (memberQualifier.hasOffset()) {
8929 // "The specified offset must be a multiple
8930 // of the base alignment of the type of the block member it qualifies, or a compile-time error results."
8931 if (! IsMultipleOfPow2(memberQualifier.layoutOffset, memberAlignment))
8932 error(memberLoc, "must be a multiple of the member's alignment", "offset", "");
8933
8934 // "The offset qualifier forces the qualified member to start at or after the specified
8935 // integral-constant expression, which will be its byte offset from the beginning of the buffer.
8936 // "The actual offset of a member is computed as
8937 // follows: If offset was declared, start with that offset, otherwise start with the next available offset."
8938 offset = std::max(offset, memberQualifier.layoutOffset);
8939 }
8940
8941 // "The actual alignment of a member will be the greater of the specified align alignment and the standard
8942 // (e.g., std140) base alignment for the member's type."
8943 if (memberQualifier.hasAlign())
8944 memberAlignment = std::max(memberAlignment, memberQualifier.layoutAlign);
8945
8946 // "If the resulting offset is not a multiple of the actual alignment,
8947 // increase it to the first offset that is a multiple of
8948 // the actual alignment."
8949 RoundToPow2(offset, memberAlignment);
8950 typeList[member].type->getQualifier().layoutOffset = offset;
8951 offset += memberSize;
8952 }
8953 }
8954
8955 // For an identifier that is already declared, add more qualification to it.
addQualifierToExisting(const TSourceLoc & loc,TQualifier qualifier,const TString & identifier)8956 void HlslParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, const TString& identifier)
8957 {
8958 TSymbol* symbol = symbolTable.find(identifier);
8959 if (symbol == nullptr) {
8960 error(loc, "identifier not previously declared", identifier.c_str(), "");
8961 return;
8962 }
8963 if (symbol->getAsFunction()) {
8964 error(loc, "cannot re-qualify a function name", identifier.c_str(), "");
8965 return;
8966 }
8967
8968 if (qualifier.isAuxiliary() ||
8969 qualifier.isMemory() ||
8970 qualifier.isInterpolation() ||
8971 qualifier.hasLayout() ||
8972 qualifier.storage != EvqTemporary ||
8973 qualifier.precision != EpqNone) {
8974 error(loc, "cannot add storage, auxiliary, memory, interpolation, layout, or precision qualifier to an existing variable", identifier.c_str(), "");
8975 return;
8976 }
8977
8978 // For read-only built-ins, add a new symbol for holding the modified qualifier.
8979 // This will bring up an entire block, if a block type has to be modified (e.g., gl_Position inside a block)
8980 if (symbol->isReadOnly())
8981 symbol = symbolTable.copyUp(symbol);
8982
8983 if (qualifier.invariant) {
8984 if (intermediate.inIoAccessed(identifier))
8985 error(loc, "cannot change qualification after use", "invariant", "");
8986 symbol->getWritableType().getQualifier().invariant = true;
8987 } else if (qualifier.noContraction) {
8988 if (intermediate.inIoAccessed(identifier))
8989 error(loc, "cannot change qualification after use", "precise", "");
8990 symbol->getWritableType().getQualifier().noContraction = true;
8991 } else if (qualifier.specConstant) {
8992 symbol->getWritableType().getQualifier().makeSpecConstant();
8993 if (qualifier.hasSpecConstantId())
8994 symbol->getWritableType().getQualifier().layoutSpecConstantId = qualifier.layoutSpecConstantId;
8995 } else
8996 warn(loc, "unknown requalification", "", "");
8997 }
8998
addQualifierToExisting(const TSourceLoc & loc,TQualifier qualifier,TIdentifierList & identifiers)8999 void HlslParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, TIdentifierList& identifiers)
9000 {
9001 for (unsigned int i = 0; i < identifiers.size(); ++i)
9002 addQualifierToExisting(loc, qualifier, *identifiers[i]);
9003 }
9004
9005 //
9006 // Update the intermediate for the given input geometry
9007 //
handleInputGeometry(const TSourceLoc & loc,const TLayoutGeometry & geometry)9008 bool HlslParseContext::handleInputGeometry(const TSourceLoc& loc, const TLayoutGeometry& geometry)
9009 {
9010 // these can be declared on non-entry-points, in which case they lose their meaning
9011 if (! parsingEntrypointParameters)
9012 return true;
9013
9014 switch (geometry) {
9015 case ElgPoints: // fall through
9016 case ElgLines: // ...
9017 case ElgTriangles: // ...
9018 case ElgLinesAdjacency: // ...
9019 case ElgTrianglesAdjacency: // ...
9020 if (! intermediate.setInputPrimitive(geometry)) {
9021 error(loc, "input primitive geometry redefinition", TQualifier::getGeometryString(geometry), "");
9022 return false;
9023 }
9024 break;
9025
9026 default:
9027 error(loc, "cannot apply to 'in'", TQualifier::getGeometryString(geometry), "");
9028 return false;
9029 }
9030
9031 return true;
9032 }
9033
9034 //
9035 // Update the intermediate for the given output geometry
9036 //
handleOutputGeometry(const TSourceLoc & loc,const TLayoutGeometry & geometry)9037 bool HlslParseContext::handleOutputGeometry(const TSourceLoc& loc, const TLayoutGeometry& geometry)
9038 {
9039 // If this is not a geometry shader, ignore. It might be a mixed shader including several stages.
9040 // Since that's an OK situation, return true for success.
9041 if (language != EShLangGeometry)
9042 return true;
9043
9044 // these can be declared on non-entry-points, in which case they lose their meaning
9045 if (! parsingEntrypointParameters)
9046 return true;
9047
9048 switch (geometry) {
9049 case ElgPoints:
9050 case ElgLineStrip:
9051 case ElgTriangleStrip:
9052 if (! intermediate.setOutputPrimitive(geometry)) {
9053 error(loc, "output primitive geometry redefinition", TQualifier::getGeometryString(geometry), "");
9054 return false;
9055 }
9056 break;
9057 default:
9058 error(loc, "cannot apply to 'out'", TQualifier::getGeometryString(geometry), "");
9059 return false;
9060 }
9061
9062 return true;
9063 }
9064
9065 //
9066 // Selection attributes
9067 //
handleSelectionAttributes(const TSourceLoc & loc,TIntermSelection * selection,const TAttributes & attributes)9068 void HlslParseContext::handleSelectionAttributes(const TSourceLoc& loc, TIntermSelection* selection,
9069 const TAttributes& attributes)
9070 {
9071 if (selection == nullptr)
9072 return;
9073
9074 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9075 switch (it->name) {
9076 case EatFlatten:
9077 selection->setFlatten();
9078 break;
9079 case EatBranch:
9080 selection->setDontFlatten();
9081 break;
9082 default:
9083 warn(loc, "attribute does not apply to a selection", "", "");
9084 break;
9085 }
9086 }
9087 }
9088
9089 //
9090 // Switch attributes
9091 //
handleSwitchAttributes(const TSourceLoc & loc,TIntermSwitch * selection,const TAttributes & attributes)9092 void HlslParseContext::handleSwitchAttributes(const TSourceLoc& loc, TIntermSwitch* selection,
9093 const TAttributes& attributes)
9094 {
9095 if (selection == nullptr)
9096 return;
9097
9098 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9099 switch (it->name) {
9100 case EatFlatten:
9101 selection->setFlatten();
9102 break;
9103 case EatBranch:
9104 selection->setDontFlatten();
9105 break;
9106 default:
9107 warn(loc, "attribute does not apply to a switch", "", "");
9108 break;
9109 }
9110 }
9111 }
9112
9113 //
9114 // Loop attributes
9115 //
handleLoopAttributes(const TSourceLoc & loc,TIntermLoop * loop,const TAttributes & attributes)9116 void HlslParseContext::handleLoopAttributes(const TSourceLoc& loc, TIntermLoop* loop,
9117 const TAttributes& attributes)
9118 {
9119 if (loop == nullptr)
9120 return;
9121
9122 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9123 switch (it->name) {
9124 case EatUnroll:
9125 loop->setUnroll();
9126 break;
9127 case EatLoop:
9128 loop->setDontUnroll();
9129 break;
9130 default:
9131 warn(loc, "attribute does not apply to a loop", "", "");
9132 break;
9133 }
9134 }
9135 }
9136
9137 //
9138 // Updating default qualifier for the case of a declaration with just a qualifier,
9139 // no type, block, or identifier.
9140 //
updateStandaloneQualifierDefaults(const TSourceLoc & loc,const TPublicType & publicType)9141 void HlslParseContext::updateStandaloneQualifierDefaults(const TSourceLoc& loc, const TPublicType& publicType)
9142 {
9143 if (publicType.shaderQualifiers.vertices != TQualifier::layoutNotSet) {
9144 assert(language == EShLangTessControl || language == EShLangGeometry);
9145 // const char* id = (language == EShLangTessControl) ? "vertices" : "max_vertices";
9146 }
9147 if (publicType.shaderQualifiers.invocations != TQualifier::layoutNotSet) {
9148 if (! intermediate.setInvocations(publicType.shaderQualifiers.invocations))
9149 error(loc, "cannot change previously set layout value", "invocations", "");
9150 }
9151 if (publicType.shaderQualifiers.geometry != ElgNone) {
9152 if (publicType.qualifier.storage == EvqVaryingIn) {
9153 switch (publicType.shaderQualifiers.geometry) {
9154 case ElgPoints:
9155 case ElgLines:
9156 case ElgLinesAdjacency:
9157 case ElgTriangles:
9158 case ElgTrianglesAdjacency:
9159 case ElgQuads:
9160 case ElgIsolines:
9161 break;
9162 default:
9163 error(loc, "cannot apply to input", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry),
9164 "");
9165 }
9166 } else if (publicType.qualifier.storage == EvqVaryingOut) {
9167 handleOutputGeometry(loc, publicType.shaderQualifiers.geometry);
9168 } else
9169 error(loc, "cannot apply to:", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry),
9170 GetStorageQualifierString(publicType.qualifier.storage));
9171 }
9172 if (publicType.shaderQualifiers.spacing != EvsNone)
9173 intermediate.setVertexSpacing(publicType.shaderQualifiers.spacing);
9174 if (publicType.shaderQualifiers.order != EvoNone)
9175 intermediate.setVertexOrder(publicType.shaderQualifiers.order);
9176 if (publicType.shaderQualifiers.pointMode)
9177 intermediate.setPointMode();
9178 for (int i = 0; i < 3; ++i) {
9179 if (publicType.shaderQualifiers.localSize[i] > 1) {
9180 int max = 0;
9181 switch (i) {
9182 case 0: max = resources.maxComputeWorkGroupSizeX; break;
9183 case 1: max = resources.maxComputeWorkGroupSizeY; break;
9184 case 2: max = resources.maxComputeWorkGroupSizeZ; break;
9185 default: break;
9186 }
9187 if (intermediate.getLocalSize(i) > (unsigned int)max)
9188 error(loc, "too large; see gl_MaxComputeWorkGroupSize", "local_size", "");
9189
9190 // Fix the existing constant gl_WorkGroupSize with this new information.
9191 TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
9192 workGroupSize->getWritableConstArray()[i].setUConst(intermediate.getLocalSize(i));
9193 }
9194 if (publicType.shaderQualifiers.localSizeSpecId[i] != TQualifier::layoutNotSet) {
9195 intermediate.setLocalSizeSpecId(i, publicType.shaderQualifiers.localSizeSpecId[i]);
9196 // Set the workgroup built-in variable as a specialization constant
9197 TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
9198 workGroupSize->getWritableType().getQualifier().specConstant = true;
9199 }
9200 }
9201 if (publicType.shaderQualifiers.earlyFragmentTests)
9202 intermediate.setEarlyFragmentTests();
9203
9204 const TQualifier& qualifier = publicType.qualifier;
9205
9206 switch (qualifier.storage) {
9207 case EvqUniform:
9208 if (qualifier.hasMatrix())
9209 globalUniformDefaults.layoutMatrix = qualifier.layoutMatrix;
9210 if (qualifier.hasPacking())
9211 globalUniformDefaults.layoutPacking = qualifier.layoutPacking;
9212 break;
9213 case EvqBuffer:
9214 if (qualifier.hasMatrix())
9215 globalBufferDefaults.layoutMatrix = qualifier.layoutMatrix;
9216 if (qualifier.hasPacking())
9217 globalBufferDefaults.layoutPacking = qualifier.layoutPacking;
9218 break;
9219 case EvqVaryingIn:
9220 break;
9221 case EvqVaryingOut:
9222 if (qualifier.hasStream())
9223 globalOutputDefaults.layoutStream = qualifier.layoutStream;
9224 if (qualifier.hasXfbBuffer())
9225 globalOutputDefaults.layoutXfbBuffer = qualifier.layoutXfbBuffer;
9226 if (globalOutputDefaults.hasXfbBuffer() && qualifier.hasXfbStride()) {
9227 if (! intermediate.setXfbBufferStride(globalOutputDefaults.layoutXfbBuffer, qualifier.layoutXfbStride))
9228 error(loc, "all stride settings must match for xfb buffer", "xfb_stride", "%d",
9229 qualifier.layoutXfbBuffer);
9230 }
9231 break;
9232 default:
9233 error(loc, "default qualifier requires 'uniform', 'buffer', 'in', or 'out' storage qualification", "", "");
9234 return;
9235 }
9236 }
9237
9238 //
9239 // Take the sequence of statements that has been built up since the last case/default,
9240 // put it on the list of top-level nodes for the current (inner-most) switch statement,
9241 // and follow that by the case/default we are on now. (See switch topology comment on
9242 // TIntermSwitch.)
9243 //
wrapupSwitchSubsequence(TIntermAggregate * statements,TIntermNode * branchNode)9244 void HlslParseContext::wrapupSwitchSubsequence(TIntermAggregate* statements, TIntermNode* branchNode)
9245 {
9246 TIntermSequence* switchSequence = switchSequenceStack.back();
9247
9248 if (statements) {
9249 statements->setOperator(EOpSequence);
9250 switchSequence->push_back(statements);
9251 }
9252 if (branchNode) {
9253 // check all previous cases for the same label (or both are 'default')
9254 for (unsigned int s = 0; s < switchSequence->size(); ++s) {
9255 TIntermBranch* prevBranch = (*switchSequence)[s]->getAsBranchNode();
9256 if (prevBranch) {
9257 TIntermTyped* prevExpression = prevBranch->getExpression();
9258 TIntermTyped* newExpression = branchNode->getAsBranchNode()->getExpression();
9259 if (prevExpression == nullptr && newExpression == nullptr)
9260 error(branchNode->getLoc(), "duplicate label", "default", "");
9261 else if (prevExpression != nullptr &&
9262 newExpression != nullptr &&
9263 prevExpression->getAsConstantUnion() &&
9264 newExpression->getAsConstantUnion() &&
9265 prevExpression->getAsConstantUnion()->getConstArray()[0].getIConst() ==
9266 newExpression->getAsConstantUnion()->getConstArray()[0].getIConst())
9267 error(branchNode->getLoc(), "duplicated value", "case", "");
9268 }
9269 }
9270 switchSequence->push_back(branchNode);
9271 }
9272 }
9273
9274 //
9275 // Turn the top-level node sequence built up of wrapupSwitchSubsequence
9276 // into a switch node.
9277 //
addSwitch(const TSourceLoc & loc,TIntermTyped * expression,TIntermAggregate * lastStatements,const TAttributes & attributes)9278 TIntermNode* HlslParseContext::addSwitch(const TSourceLoc& loc, TIntermTyped* expression,
9279 TIntermAggregate* lastStatements, const TAttributes& attributes)
9280 {
9281 wrapupSwitchSubsequence(lastStatements, nullptr);
9282
9283 if (expression == nullptr ||
9284 (expression->getBasicType() != EbtInt && expression->getBasicType() != EbtUint) ||
9285 expression->getType().isArray() || expression->getType().isMatrix() || expression->getType().isVector())
9286 error(loc, "condition must be a scalar integer expression", "switch", "");
9287
9288 // If there is nothing to do, drop the switch but still execute the expression
9289 TIntermSequence* switchSequence = switchSequenceStack.back();
9290 if (switchSequence->size() == 0)
9291 return expression;
9292
9293 if (lastStatements == nullptr) {
9294 // emulate a break for error recovery
9295 lastStatements = intermediate.makeAggregate(intermediate.addBranch(EOpBreak, loc));
9296 lastStatements->setOperator(EOpSequence);
9297 switchSequence->push_back(lastStatements);
9298 }
9299
9300 TIntermAggregate* body = new TIntermAggregate(EOpSequence);
9301 body->getSequence() = *switchSequenceStack.back();
9302 body->setLoc(loc);
9303
9304 TIntermSwitch* switchNode = new TIntermSwitch(expression, body);
9305 switchNode->setLoc(loc);
9306 handleSwitchAttributes(loc, switchNode, attributes);
9307
9308 return switchNode;
9309 }
9310
9311 // Make a new symbol-table level that is made out of the members of a structure.
9312 // This should be done as an anonymous struct (name is "") so that the symbol table
9313 // finds the members with no explicit reference to a 'this' variable.
pushThisScope(const TType & thisStruct,const TVector<TFunctionDeclarator> & functionDeclarators)9314 void HlslParseContext::pushThisScope(const TType& thisStruct, const TVector<TFunctionDeclarator>& functionDeclarators)
9315 {
9316 // member variables
9317 TVariable& thisVariable = *new TVariable(NewPoolTString(""), thisStruct);
9318 symbolTable.pushThis(thisVariable);
9319
9320 // member functions
9321 for (auto it = functionDeclarators.begin(); it != functionDeclarators.end(); ++it) {
9322 // member should have a prefix matching currentTypePrefix.back()
9323 // but, symbol lookup within the class scope will just use the
9324 // unprefixed name. Hence, there are two: one fully prefixed and
9325 // one with no prefix.
9326 TFunction& member = *it->function->clone();
9327 member.removePrefix(currentTypePrefix.back());
9328 symbolTable.insert(member);
9329 }
9330 }
9331
9332 // Track levels of class/struct/namespace nesting with a prefix string using
9333 // the type names separated by the scoping operator. E.g., two levels
9334 // would look like:
9335 //
9336 // outer::inner
9337 //
9338 // The string is empty when at normal global level.
9339 //
pushNamespace(const TString & typeName)9340 void HlslParseContext::pushNamespace(const TString& typeName)
9341 {
9342 // make new type prefix
9343 TString newPrefix;
9344 if (currentTypePrefix.size() > 0)
9345 newPrefix = currentTypePrefix.back();
9346 newPrefix.append(typeName);
9347 newPrefix.append(scopeMangler);
9348 currentTypePrefix.push_back(newPrefix);
9349 }
9350
9351 // Opposite of pushNamespace(), see above
popNamespace()9352 void HlslParseContext::popNamespace()
9353 {
9354 currentTypePrefix.pop_back();
9355 }
9356
9357 // Use the class/struct nesting string to create a global name for
9358 // a member of a class/struct.
getFullNamespaceName(TString * & name) const9359 void HlslParseContext::getFullNamespaceName(TString*& name) const
9360 {
9361 if (currentTypePrefix.size() == 0)
9362 return;
9363
9364 TString* fullName = NewPoolTString(currentTypePrefix.back().c_str());
9365 fullName->append(*name);
9366 name = fullName;
9367 }
9368
9369 // Helper function to add the namespace scope mangling syntax to a string.
addScopeMangler(TString & name)9370 void HlslParseContext::addScopeMangler(TString& name)
9371 {
9372 name.append(scopeMangler);
9373 }
9374
9375 // Return true if this has uniform-interface like decorations.
hasUniform(const TQualifier & qualifier) const9376 bool HlslParseContext::hasUniform(const TQualifier& qualifier) const
9377 {
9378 return qualifier.hasUniformLayout() ||
9379 qualifier.layoutPushConstant;
9380 }
9381
9382 // Potentially not the opposite of hasUniform(), as if some characteristic is
9383 // ever used for more than one thing (e.g., uniform or input), hasUniform() should
9384 // say it exists, but clearUniform() should leave it in place.
clearUniform(TQualifier & qualifier)9385 void HlslParseContext::clearUniform(TQualifier& qualifier)
9386 {
9387 qualifier.clearUniformLayout();
9388 qualifier.layoutPushConstant = false;
9389 }
9390
9391 // Return false if builtIn by itself doesn't force this qualifier to be an input qualifier.
isInputBuiltIn(const TQualifier & qualifier) const9392 bool HlslParseContext::isInputBuiltIn(const TQualifier& qualifier) const
9393 {
9394 switch (qualifier.builtIn) {
9395 case EbvPosition:
9396 case EbvPointSize:
9397 return language != EShLangVertex && language != EShLangCompute && language != EShLangFragment;
9398 case EbvClipDistance:
9399 case EbvCullDistance:
9400 return language != EShLangVertex && language != EShLangCompute;
9401 case EbvFragCoord:
9402 case EbvFace:
9403 case EbvHelperInvocation:
9404 case EbvLayer:
9405 case EbvPointCoord:
9406 case EbvSampleId:
9407 case EbvSampleMask:
9408 case EbvSamplePosition:
9409 case EbvViewportIndex:
9410 return language == EShLangFragment;
9411 case EbvGlobalInvocationId:
9412 case EbvLocalInvocationIndex:
9413 case EbvLocalInvocationId:
9414 case EbvNumWorkGroups:
9415 case EbvWorkGroupId:
9416 case EbvWorkGroupSize:
9417 return language == EShLangCompute;
9418 case EbvInvocationId:
9419 return language == EShLangTessControl || language == EShLangTessEvaluation || language == EShLangGeometry;
9420 case EbvPatchVertices:
9421 return language == EShLangTessControl || language == EShLangTessEvaluation;
9422 case EbvInstanceId:
9423 case EbvInstanceIndex:
9424 case EbvVertexId:
9425 case EbvVertexIndex:
9426 return language == EShLangVertex;
9427 case EbvPrimitiveId:
9428 return language == EShLangGeometry || language == EShLangFragment || language == EShLangTessControl;
9429 case EbvTessLevelInner:
9430 case EbvTessLevelOuter:
9431 return language == EShLangTessEvaluation;
9432 case EbvTessCoord:
9433 return language == EShLangTessEvaluation;
9434 default:
9435 return false;
9436 }
9437 }
9438
9439 // Return true if there are decorations to preserve for input-like storage.
hasInput(const TQualifier & qualifier) const9440 bool HlslParseContext::hasInput(const TQualifier& qualifier) const
9441 {
9442 if (qualifier.hasAnyLocation())
9443 return true;
9444
9445 if (language == EShLangFragment && (qualifier.isInterpolation() || qualifier.centroid || qualifier.sample))
9446 return true;
9447
9448 if (language == EShLangTessEvaluation && qualifier.patch)
9449 return true;
9450
9451 if (isInputBuiltIn(qualifier))
9452 return true;
9453
9454 return false;
9455 }
9456
9457 // Return false if builtIn by itself doesn't force this qualifier to be an output qualifier.
isOutputBuiltIn(const TQualifier & qualifier) const9458 bool HlslParseContext::isOutputBuiltIn(const TQualifier& qualifier) const
9459 {
9460 switch (qualifier.builtIn) {
9461 case EbvPosition:
9462 case EbvPointSize:
9463 case EbvClipVertex:
9464 case EbvClipDistance:
9465 case EbvCullDistance:
9466 return language != EShLangFragment && language != EShLangCompute;
9467 case EbvFragDepth:
9468 case EbvFragDepthGreater:
9469 case EbvFragDepthLesser:
9470 case EbvSampleMask:
9471 return language == EShLangFragment;
9472 case EbvLayer:
9473 case EbvViewportIndex:
9474 return language == EShLangGeometry || language == EShLangVertex;
9475 case EbvPrimitiveId:
9476 return language == EShLangGeometry;
9477 case EbvTessLevelInner:
9478 case EbvTessLevelOuter:
9479 return language == EShLangTessControl;
9480 default:
9481 return false;
9482 }
9483 }
9484
9485 // Return true if there are decorations to preserve for output-like storage.
hasOutput(const TQualifier & qualifier) const9486 bool HlslParseContext::hasOutput(const TQualifier& qualifier) const
9487 {
9488 if (qualifier.hasAnyLocation())
9489 return true;
9490
9491 if (language != EShLangFragment && language != EShLangCompute && qualifier.hasXfb())
9492 return true;
9493
9494 if (language == EShLangTessControl && qualifier.patch)
9495 return true;
9496
9497 if (language == EShLangGeometry && qualifier.hasStream())
9498 return true;
9499
9500 if (isOutputBuiltIn(qualifier))
9501 return true;
9502
9503 return false;
9504 }
9505
9506 // Make the IO decorations etc. be appropriate only for an input interface.
correctInput(TQualifier & qualifier)9507 void HlslParseContext::correctInput(TQualifier& qualifier)
9508 {
9509 clearUniform(qualifier);
9510 if (language == EShLangVertex)
9511 qualifier.clearInterstage();
9512 if (language != EShLangTessEvaluation)
9513 qualifier.patch = false;
9514 if (language != EShLangFragment) {
9515 qualifier.clearInterpolation();
9516 qualifier.sample = false;
9517 }
9518
9519 qualifier.clearStreamLayout();
9520 qualifier.clearXfbLayout();
9521
9522 if (! isInputBuiltIn(qualifier))
9523 qualifier.builtIn = EbvNone;
9524 }
9525
9526 // Make the IO decorations etc. be appropriate only for an output interface.
correctOutput(TQualifier & qualifier)9527 void HlslParseContext::correctOutput(TQualifier& qualifier)
9528 {
9529 clearUniform(qualifier);
9530 if (language == EShLangFragment)
9531 qualifier.clearInterstage();
9532 if (language != EShLangGeometry)
9533 qualifier.clearStreamLayout();
9534 if (language == EShLangFragment)
9535 qualifier.clearXfbLayout();
9536 if (language != EShLangTessControl)
9537 qualifier.patch = false;
9538
9539 switch (qualifier.builtIn) {
9540 case EbvFragDepth:
9541 intermediate.setDepthReplacing();
9542 intermediate.setDepth(EldAny);
9543 break;
9544 case EbvFragDepthGreater:
9545 intermediate.setDepthReplacing();
9546 intermediate.setDepth(EldGreater);
9547 qualifier.builtIn = EbvFragDepth;
9548 break;
9549 case EbvFragDepthLesser:
9550 intermediate.setDepthReplacing();
9551 intermediate.setDepth(EldLess);
9552 qualifier.builtIn = EbvFragDepth;
9553 break;
9554 default:
9555 break;
9556 }
9557
9558 if (! isOutputBuiltIn(qualifier))
9559 qualifier.builtIn = EbvNone;
9560 }
9561
9562 // Make the IO decorations etc. be appropriate only for uniform type interfaces.
correctUniform(TQualifier & qualifier)9563 void HlslParseContext::correctUniform(TQualifier& qualifier)
9564 {
9565 if (qualifier.declaredBuiltIn == EbvNone)
9566 qualifier.declaredBuiltIn = qualifier.builtIn;
9567
9568 qualifier.builtIn = EbvNone;
9569 qualifier.clearInterstage();
9570 qualifier.clearInterstageLayout();
9571 }
9572
9573 // Clear out all IO/Uniform stuff, so this has nothing to do with being an IO interface.
clearUniformInputOutput(TQualifier & qualifier)9574 void HlslParseContext::clearUniformInputOutput(TQualifier& qualifier)
9575 {
9576 clearUniform(qualifier);
9577 correctUniform(qualifier);
9578 }
9579
9580
9581 // Set texture return type. Returns success (not all types are valid).
setTextureReturnType(TSampler & sampler,const TType & retType,const TSourceLoc & loc)9582 bool HlslParseContext::setTextureReturnType(TSampler& sampler, const TType& retType, const TSourceLoc& loc)
9583 {
9584 // Seed the output with an invalid index. We will set it to a valid one if we can.
9585 sampler.structReturnIndex = TSampler::noReturnStruct;
9586
9587 // Arrays aren't supported.
9588 if (retType.isArray()) {
9589 error(loc, "Arrays not supported in texture template types", "", "");
9590 return false;
9591 }
9592
9593 // If return type is a vector, remember the vector size in the sampler, and return.
9594 if (retType.isVector() || retType.isScalar()) {
9595 sampler.vectorSize = retType.getVectorSize();
9596 return true;
9597 }
9598
9599 // If it wasn't a vector, it must be a struct meeting certain requirements. The requirements
9600 // are checked below: just check for struct-ness here.
9601 if (!retType.isStruct()) {
9602 error(loc, "Invalid texture template type", "", "");
9603 return false;
9604 }
9605
9606 // TODO: Subpass doesn't handle struct returns, due to some oddities with fn overloading.
9607 if (sampler.isSubpass()) {
9608 error(loc, "Unimplemented: structure template type in subpass input", "", "");
9609 return false;
9610 }
9611
9612 TTypeList* members = retType.getWritableStruct();
9613
9614 // Check for too many or not enough structure members.
9615 if (members->size() > 4 || members->size() == 0) {
9616 error(loc, "Invalid member count in texture template structure", "", "");
9617 return false;
9618 }
9619
9620 // Error checking: We must have <= 4 total components, all of the same basic type.
9621 unsigned totalComponents = 0;
9622 for (unsigned m = 0; m < members->size(); ++m) {
9623 // Check for bad member types
9624 if (!(*members)[m].type->isScalar() && !(*members)[m].type->isVector()) {
9625 error(loc, "Invalid texture template struct member type", "", "");
9626 return false;
9627 }
9628
9629 const unsigned memberVectorSize = (*members)[m].type->getVectorSize();
9630 totalComponents += memberVectorSize;
9631
9632 // too many total member components
9633 if (totalComponents > 4) {
9634 error(loc, "Too many components in texture template structure type", "", "");
9635 return false;
9636 }
9637
9638 // All members must be of a common basic type
9639 if ((*members)[m].type->getBasicType() != (*members)[0].type->getBasicType()) {
9640 error(loc, "Texture template structure members must same basic type", "", "");
9641 return false;
9642 }
9643 }
9644
9645 // If the structure in the return type already exists in the table, we'll use it. Otherwise, we'll make
9646 // a new entry. This is a linear search, but it hardly ever happens, and the list cannot be very large.
9647 for (unsigned int idx = 0; idx < textureReturnStruct.size(); ++idx) {
9648 if (textureReturnStruct[idx] == members) {
9649 sampler.structReturnIndex = idx;
9650 return true;
9651 }
9652 }
9653
9654 // It wasn't found as an existing entry. See if we have room for a new one.
9655 if (textureReturnStruct.size() >= TSampler::structReturnSlots) {
9656 error(loc, "Texture template struct return slots exceeded", "", "");
9657 return false;
9658 }
9659
9660 // Insert it in the vector that tracks struct return types.
9661 sampler.structReturnIndex = unsigned(textureReturnStruct.size());
9662 textureReturnStruct.push_back(members);
9663
9664 // Success!
9665 return true;
9666 }
9667
9668 // Return the sampler return type in retType.
getTextureReturnType(const TSampler & sampler,TType & retType) const9669 void HlslParseContext::getTextureReturnType(const TSampler& sampler, TType& retType) const
9670 {
9671 if (sampler.hasReturnStruct()) {
9672 assert(textureReturnStruct.size() >= sampler.structReturnIndex);
9673
9674 // We land here if the texture return is a structure.
9675 TTypeList* blockStruct = textureReturnStruct[sampler.structReturnIndex];
9676
9677 const TType resultType(blockStruct, "");
9678 retType.shallowCopy(resultType);
9679 } else {
9680 // We land here if the texture return is a vector or scalar.
9681 const TType resultType(sampler.type, EvqTemporary, sampler.getVectorSize());
9682 retType.shallowCopy(resultType);
9683 }
9684 }
9685
9686
9687 // Return a symbol for the tessellation linkage variable of the given TBuiltInVariable type
findTessLinkageSymbol(TBuiltInVariable biType) const9688 TIntermSymbol* HlslParseContext::findTessLinkageSymbol(TBuiltInVariable biType) const
9689 {
9690 const auto it = builtInTessLinkageSymbols.find(biType);
9691 if (it == builtInTessLinkageSymbols.end()) // if it wasn't declared by the user, return nullptr
9692 return nullptr;
9693
9694 return intermediate.addSymbol(*it->second->getAsVariable());
9695 }
9696
9697 // Find the patch constant function (issues error, returns nullptr if not found)
findPatchConstantFunction(const TSourceLoc & loc)9698 const TFunction* HlslParseContext::findPatchConstantFunction(const TSourceLoc& loc)
9699 {
9700 if (symbolTable.isFunctionNameVariable(patchConstantFunctionName)) {
9701 error(loc, "can't use variable in patch constant function", patchConstantFunctionName.c_str(), "");
9702 return nullptr;
9703 }
9704
9705 const TString mangledName = patchConstantFunctionName + "(";
9706
9707 // create list of PCF candidates
9708 TVector<const TFunction*> candidateList;
9709 bool builtIn;
9710 symbolTable.findFunctionNameList(mangledName, candidateList, builtIn);
9711
9712 // We have to have one and only one, or we don't know which to pick: the patchconstantfunc does not
9713 // allow any disambiguation of overloads.
9714 if (candidateList.empty()) {
9715 error(loc, "patch constant function not found", patchConstantFunctionName.c_str(), "");
9716 return nullptr;
9717 }
9718
9719 // Based on directed experiments, it appears that if there are overloaded patchconstantfunctions,
9720 // HLSL picks the last one in shader source order. Since that isn't yet implemented here, error
9721 // out if there is more than one candidate.
9722 if (candidateList.size() > 1) {
9723 error(loc, "ambiguous patch constant function", patchConstantFunctionName.c_str(), "");
9724 return nullptr;
9725 }
9726
9727 return candidateList[0];
9728 }
9729
9730 // Finalization step: Add patch constant function invocation
addPatchConstantInvocation()9731 void HlslParseContext::addPatchConstantInvocation()
9732 {
9733 TSourceLoc loc;
9734 loc.init();
9735
9736 // If there's no patch constant function, or we're not a HS, do nothing.
9737 if (patchConstantFunctionName.empty() || language != EShLangTessControl)
9738 return;
9739
9740 // Look for built-in variables in a function's parameter list.
9741 const auto findBuiltIns = [&](const TFunction& function, std::set<tInterstageIoData>& builtIns) {
9742 for (int p=0; p<function.getParamCount(); ++p) {
9743 TStorageQualifier storage = function[p].type->getQualifier().storage;
9744
9745 if (storage == EvqConstReadOnly) // treated identically to input
9746 storage = EvqIn;
9747
9748 if (function[p].getDeclaredBuiltIn() != EbvNone)
9749 builtIns.insert(HlslParseContext::tInterstageIoData(function[p].getDeclaredBuiltIn(), storage));
9750 else
9751 builtIns.insert(HlslParseContext::tInterstageIoData(function[p].type->getQualifier().builtIn, storage));
9752 }
9753 };
9754
9755 // If we synthesize a built-in interface variable, we must add it to the linkage.
9756 const auto addToLinkage = [&](const TType& type, const TString* name, TIntermSymbol** symbolNode) {
9757 if (name == nullptr) {
9758 error(loc, "unable to locate patch function parameter name", "", "");
9759 return;
9760 } else {
9761 TVariable& variable = *new TVariable(name, type);
9762 if (! symbolTable.insert(variable)) {
9763 error(loc, "unable to declare patch constant function interface variable", name->c_str(), "");
9764 return;
9765 }
9766
9767 globalQualifierFix(loc, variable.getWritableType().getQualifier());
9768
9769 if (symbolNode != nullptr)
9770 *symbolNode = intermediate.addSymbol(variable);
9771
9772 trackLinkage(variable);
9773 }
9774 };
9775
9776 const auto isOutputPatch = [](TFunction& patchConstantFunction, int param) {
9777 const TType& type = *patchConstantFunction[param].type;
9778 const TBuiltInVariable biType = patchConstantFunction[param].getDeclaredBuiltIn();
9779
9780 return type.isSizedArray() && biType == EbvOutputPatch;
9781 };
9782
9783 // We will perform these steps. Each is in a scoped block for separation: they could
9784 // become separate functions to make addPatchConstantInvocation shorter.
9785 //
9786 // 1. Union the interfaces, and create built-ins for anything present in the PCF and
9787 // declared as a built-in variable that isn't present in the entry point's signature.
9788 //
9789 // 2. Synthesizes a call to the patchconstfunction using built-in variables from either main,
9790 // or the ones we created. Matching is based on built-in type. We may use synthesized
9791 // variables from (1) above.
9792 //
9793 // 2B: Synthesize per control point invocations of wrapped entry point if the PCF requires them.
9794 //
9795 // 3. Create a return sequence: copy the return value (if any) from the PCF to a
9796 // (non-sanitized) output variable. In case this may involve multiple copies, such as for
9797 // an arrayed variable, a temporary copy of the PCF output is created to avoid multiple
9798 // indirections into a complex R-value coming from the call to the PCF.
9799 //
9800 // 4. Create a barrier.
9801 //
9802 // 5/5B. Call the PCF inside an if test for (invocation id == 0).
9803
9804 TFunction* patchConstantFunctionPtr = const_cast<TFunction*>(findPatchConstantFunction(loc));
9805
9806 if (patchConstantFunctionPtr == nullptr)
9807 return;
9808
9809 TFunction& patchConstantFunction = *patchConstantFunctionPtr;
9810
9811 const int pcfParamCount = patchConstantFunction.getParamCount();
9812 TIntermSymbol* invocationIdSym = findTessLinkageSymbol(EbvInvocationId);
9813 TIntermSequence& epBodySeq = entryPointFunctionBody->getAsAggregate()->getSequence();
9814
9815 int outPatchParam = -1; // -1 means there isn't one.
9816
9817 // ================ Step 1A: Union Interfaces ================
9818 // Our patch constant function.
9819 {
9820 std::set<tInterstageIoData> pcfBuiltIns; // patch constant function built-ins
9821 std::set<tInterstageIoData> epfBuiltIns; // entry point function built-ins
9822
9823 assert(entryPointFunction);
9824 assert(entryPointFunctionBody);
9825
9826 findBuiltIns(patchConstantFunction, pcfBuiltIns);
9827 findBuiltIns(*entryPointFunction, epfBuiltIns);
9828
9829 // Find the set of built-ins in the PCF that are not present in the entry point.
9830 std::set<tInterstageIoData> notInEntryPoint;
9831
9832 notInEntryPoint = pcfBuiltIns;
9833
9834 // std::set_difference not usable on unordered containers
9835 for (auto bi = epfBuiltIns.begin(); bi != epfBuiltIns.end(); ++bi)
9836 notInEntryPoint.erase(*bi);
9837
9838 // Now we'll add those to the entry and to the linkage.
9839 for (int p=0; p<pcfParamCount; ++p) {
9840 const TBuiltInVariable biType = patchConstantFunction[p].getDeclaredBuiltIn();
9841 TStorageQualifier storage = patchConstantFunction[p].type->getQualifier().storage;
9842
9843 // Track whether there is an output patch param
9844 if (isOutputPatch(patchConstantFunction, p)) {
9845 if (outPatchParam >= 0) {
9846 // Presently we only support one per ctrl pt input.
9847 error(loc, "unimplemented: multiple output patches in patch constant function", "", "");
9848 return;
9849 }
9850 outPatchParam = p;
9851 }
9852
9853 if (biType != EbvNone) {
9854 TType* paramType = patchConstantFunction[p].type->clone();
9855
9856 if (storage == EvqConstReadOnly) // treated identically to input
9857 storage = EvqIn;
9858
9859 // Presently, the only non-built-in we support is InputPatch, which is treated as
9860 // a pseudo-built-in.
9861 if (biType == EbvInputPatch) {
9862 builtInTessLinkageSymbols[biType] = inputPatch;
9863 } else if (biType == EbvOutputPatch) {
9864 // Nothing...
9865 } else {
9866 // Use the original declaration type for the linkage
9867 paramType->getQualifier().builtIn = biType;
9868 if (biType == EbvTessLevelInner || biType == EbvTessLevelInner)
9869 paramType->getQualifier().patch = true;
9870
9871 if (notInEntryPoint.count(tInterstageIoData(biType, storage)) == 1)
9872 addToLinkage(*paramType, patchConstantFunction[p].name, nullptr);
9873 }
9874 }
9875 }
9876
9877 // If we didn't find it because the shader made one, add our own.
9878 if (invocationIdSym == nullptr) {
9879 TType invocationIdType(EbtUint, EvqIn, 1);
9880 TString* invocationIdName = NewPoolTString("InvocationId");
9881 invocationIdType.getQualifier().builtIn = EbvInvocationId;
9882 addToLinkage(invocationIdType, invocationIdName, &invocationIdSym);
9883 }
9884
9885 assert(invocationIdSym);
9886 }
9887
9888 TIntermTyped* pcfArguments = nullptr;
9889 TVariable* perCtrlPtVar = nullptr;
9890
9891 // ================ Step 1B: Argument synthesis ================
9892 // Create pcfArguments for synthesis of patchconstantfunction invocation
9893 {
9894 for (int p=0; p<pcfParamCount; ++p) {
9895 TIntermTyped* inputArg = nullptr;
9896
9897 if (p == outPatchParam) {
9898 if (perCtrlPtVar == nullptr) {
9899 perCtrlPtVar = makeInternalVariable(*patchConstantFunction[outPatchParam].name,
9900 *patchConstantFunction[outPatchParam].type);
9901
9902 perCtrlPtVar->getWritableType().getQualifier().makeTemporary();
9903 }
9904 inputArg = intermediate.addSymbol(*perCtrlPtVar, loc);
9905 } else {
9906 // find which built-in it is
9907 const TBuiltInVariable biType = patchConstantFunction[p].getDeclaredBuiltIn();
9908
9909 if (biType == EbvInputPatch && inputPatch == nullptr) {
9910 error(loc, "unimplemented: PCF input patch without entry point input patch parameter", "", "");
9911 return;
9912 }
9913
9914 inputArg = findTessLinkageSymbol(biType);
9915
9916 if (inputArg == nullptr) {
9917 error(loc, "unable to find patch constant function built-in variable", "", "");
9918 return;
9919 }
9920 }
9921
9922 if (pcfParamCount == 1)
9923 pcfArguments = inputArg;
9924 else
9925 pcfArguments = intermediate.growAggregate(pcfArguments, inputArg);
9926 }
9927 }
9928
9929 // ================ Step 2: Synthesize call to PCF ================
9930 TIntermAggregate* pcfCallSequence = nullptr;
9931 TIntermTyped* pcfCall = nullptr;
9932
9933 {
9934 // Create a function call to the patchconstantfunction
9935 if (pcfArguments)
9936 addInputArgumentConversions(patchConstantFunction, pcfArguments);
9937
9938 // Synthetic call.
9939 pcfCall = intermediate.setAggregateOperator(pcfArguments, EOpFunctionCall, patchConstantFunction.getType(), loc);
9940 pcfCall->getAsAggregate()->setUserDefined();
9941 pcfCall->getAsAggregate()->setName(patchConstantFunction.getMangledName());
9942 intermediate.addToCallGraph(infoSink, intermediate.getEntryPointMangledName().c_str(),
9943 patchConstantFunction.getMangledName());
9944
9945 if (pcfCall->getAsAggregate()) {
9946 TQualifierList& qualifierList = pcfCall->getAsAggregate()->getQualifierList();
9947 for (int i = 0; i < patchConstantFunction.getParamCount(); ++i) {
9948 TStorageQualifier qual = patchConstantFunction[i].type->getQualifier().storage;
9949 qualifierList.push_back(qual);
9950 }
9951 pcfCall = addOutputArgumentConversions(patchConstantFunction, *pcfCall->getAsOperator());
9952 }
9953 }
9954
9955 // ================ Step 2B: Per Control Point synthesis ================
9956 // If there is per control point data, we must either emulate that with multiple
9957 // invocations of the entry point to build up an array, or (TODO:) use a yet
9958 // unavailable extension to look across the SIMD lanes. This is the former
9959 // as a placeholder for the latter.
9960 if (outPatchParam >= 0) {
9961 // We must introduce a local temp variable of the type wanted by the PCF input.
9962 const int arraySize = patchConstantFunction[outPatchParam].type->getOuterArraySize();
9963
9964 if (entryPointFunction->getType().getBasicType() == EbtVoid) {
9965 error(loc, "entry point must return a value for use with patch constant function", "", "");
9966 return;
9967 }
9968
9969 // Create calls to wrapped main to fill in the array. We will substitute fixed values
9970 // of invocation ID when calling the wrapped main.
9971
9972 // This is the type of the each member of the per ctrl point array.
9973 const TType derefType(perCtrlPtVar->getType(), 0);
9974
9975 for (int cpt = 0; cpt < arraySize; ++cpt) {
9976 // TODO: improve. substr(1) here is to avoid the '@' that was grafted on but isn't in the symtab
9977 // for this function.
9978 const TString origName = entryPointFunction->getName().substr(1);
9979 TFunction callee(&origName, TType(EbtVoid));
9980 TIntermTyped* callingArgs = nullptr;
9981
9982 for (int i = 0; i < entryPointFunction->getParamCount(); i++) {
9983 TParameter& param = (*entryPointFunction)[i];
9984 TType& paramType = *param.type;
9985
9986 if (paramType.getQualifier().isParamOutput()) {
9987 error(loc, "unimplemented: entry point outputs in patch constant function invocation", "", "");
9988 return;
9989 }
9990
9991 if (paramType.getQualifier().isParamInput()) {
9992 TIntermTyped* arg = nullptr;
9993 if ((*entryPointFunction)[i].getDeclaredBuiltIn() == EbvInvocationId) {
9994 // substitute invocation ID with the array element ID
9995 arg = intermediate.addConstantUnion(cpt, loc);
9996 } else {
9997 TVariable* argVar = makeInternalVariable(*param.name, *param.type);
9998 argVar->getWritableType().getQualifier().makeTemporary();
9999 arg = intermediate.addSymbol(*argVar);
10000 }
10001
10002 handleFunctionArgument(&callee, callingArgs, arg);
10003 }
10004 }
10005
10006 // Call and assign to per ctrl point variable
10007 currentCaller = intermediate.getEntryPointMangledName().c_str();
10008 TIntermTyped* callReturn = handleFunctionCall(loc, &callee, callingArgs);
10009 TIntermTyped* index = intermediate.addConstantUnion(cpt, loc);
10010 TIntermSymbol* perCtrlPtSym = intermediate.addSymbol(*perCtrlPtVar, loc);
10011 TIntermTyped* element = intermediate.addIndex(EOpIndexDirect, perCtrlPtSym, index, loc);
10012 element->setType(derefType);
10013 element->setLoc(loc);
10014
10015 pcfCallSequence = intermediate.growAggregate(pcfCallSequence,
10016 handleAssign(loc, EOpAssign, element, callReturn));
10017 }
10018 }
10019
10020 // ================ Step 3: Create return Sequence ================
10021 // Return sequence: copy PCF result to a temporary, then to shader output variable.
10022 if (pcfCall->getBasicType() != EbtVoid) {
10023 const TType* retType = &patchConstantFunction.getType(); // return type from the PCF
10024 TType outType; // output type that goes with the return type.
10025 outType.shallowCopy(*retType);
10026
10027 // substitute the output type
10028 const auto newLists = ioTypeMap.find(retType->getStruct());
10029 if (newLists != ioTypeMap.end())
10030 outType.setStruct(newLists->second.output);
10031
10032 // Substitute the top level type's built-in type
10033 if (patchConstantFunction.getDeclaredBuiltInType() != EbvNone)
10034 outType.getQualifier().builtIn = patchConstantFunction.getDeclaredBuiltInType();
10035
10036 outType.getQualifier().patch = true; // make it a per-patch variable
10037
10038 TVariable* pcfOutput = makeInternalVariable("@patchConstantOutput", outType);
10039 pcfOutput->getWritableType().getQualifier().storage = EvqVaryingOut;
10040
10041 if (pcfOutput->getType().isStruct())
10042 flatten(*pcfOutput, false);
10043
10044 assignToInterface(*pcfOutput);
10045
10046 TIntermSymbol* pcfOutputSym = intermediate.addSymbol(*pcfOutput, loc);
10047
10048 // The call to the PCF is a complex R-value: we want to store it in a temp to avoid
10049 // repeated calls to the PCF:
10050 TVariable* pcfCallResult = makeInternalVariable("@patchConstantResult", *retType);
10051 pcfCallResult->getWritableType().getQualifier().makeTemporary();
10052
10053 TIntermSymbol* pcfResultVar = intermediate.addSymbol(*pcfCallResult, loc);
10054 TIntermNode* pcfResultAssign = handleAssign(loc, EOpAssign, pcfResultVar, pcfCall);
10055 TIntermNode* pcfResultToOut = handleAssign(loc, EOpAssign, pcfOutputSym,
10056 intermediate.addSymbol(*pcfCallResult, loc));
10057
10058 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfResultAssign);
10059 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfResultToOut);
10060 } else {
10061 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfCall);
10062 }
10063
10064 // ================ Step 4: Barrier ================
10065 TIntermTyped* barrier = new TIntermAggregate(EOpBarrier);
10066 barrier->setLoc(loc);
10067 barrier->setType(TType(EbtVoid));
10068 epBodySeq.insert(epBodySeq.end(), barrier);
10069
10070 // ================ Step 5: Test on invocation ID ================
10071 TIntermTyped* zero = intermediate.addConstantUnion(0, loc, true);
10072 TIntermTyped* cmp = intermediate.addBinaryNode(EOpEqual, invocationIdSym, zero, loc, TType(EbtBool));
10073
10074
10075 // ================ Step 5B: Create if statement on Invocation ID == 0 ================
10076 intermediate.setAggregateOperator(pcfCallSequence, EOpSequence, TType(EbtVoid), loc);
10077 TIntermTyped* invocationIdTest = new TIntermSelection(cmp, pcfCallSequence, nullptr);
10078 invocationIdTest->setLoc(loc);
10079
10080 // add our test sequence before the return.
10081 epBodySeq.insert(epBodySeq.end(), invocationIdTest);
10082 }
10083
10084 // Finalization step: remove unused buffer blocks from linkage (we don't know until the
10085 // shader is entirely compiled).
10086 // Preserve order of remaining symbols.
removeUnusedStructBufferCounters()10087 void HlslParseContext::removeUnusedStructBufferCounters()
10088 {
10089 const auto endIt = std::remove_if(linkageSymbols.begin(), linkageSymbols.end(),
10090 [this](const TSymbol* sym) {
10091 const auto sbcIt = structBufferCounter.find(sym->getName());
10092 return sbcIt != structBufferCounter.end() && !sbcIt->second;
10093 });
10094
10095 linkageSymbols.erase(endIt, linkageSymbols.end());
10096 }
10097
10098 // Finalization step: patch texture shadow modes to match samplers they were combined with
fixTextureShadowModes()10099 void HlslParseContext::fixTextureShadowModes()
10100 {
10101 for (auto symbol = linkageSymbols.begin(); symbol != linkageSymbols.end(); ++symbol) {
10102 TSampler& sampler = (*symbol)->getWritableType().getSampler();
10103
10104 if (sampler.isTexture()) {
10105 const auto shadowMode = textureShadowVariant.find((*symbol)->getUniqueId());
10106 if (shadowMode != textureShadowVariant.end()) {
10107
10108 if (shadowMode->second->overloaded())
10109 // Texture needs legalization if it's been seen with both shadow and non-shadow modes.
10110 intermediate.setNeedsLegalization();
10111
10112 sampler.shadow = shadowMode->second->isShadowId((*symbol)->getUniqueId());
10113 }
10114 }
10115 }
10116 }
10117
10118 // Finalization step: patch append methods to use proper stream output, which isn't known until
10119 // main is parsed, which could happen after the append method is parsed.
finalizeAppendMethods()10120 void HlslParseContext::finalizeAppendMethods()
10121 {
10122 TSourceLoc loc;
10123 loc.init();
10124
10125 // Nothing to do: bypass test for valid stream output.
10126 if (gsAppends.empty())
10127 return;
10128
10129 if (gsStreamOutput == nullptr) {
10130 error(loc, "unable to find output symbol for Append()", "", "");
10131 return;
10132 }
10133
10134 // Patch append sequences, now that we know the stream output symbol.
10135 for (auto append = gsAppends.begin(); append != gsAppends.end(); ++append) {
10136 append->node->getSequence()[0] =
10137 handleAssign(append->loc, EOpAssign,
10138 intermediate.addSymbol(*gsStreamOutput, append->loc),
10139 append->node->getSequence()[0]->getAsTyped());
10140 }
10141 }
10142
10143 // post-processing
finish()10144 void HlslParseContext::finish()
10145 {
10146 // Error check: There was a dangling .mips operator. These are not nested constructs in the grammar, so
10147 // cannot be detected there. This is not strictly needed in a non-validating parser; it's just helpful.
10148 if (! mipsOperatorMipArg.empty()) {
10149 error(mipsOperatorMipArg.back().loc, "unterminated mips operator:", "", "");
10150 }
10151
10152 removeUnusedStructBufferCounters();
10153 addPatchConstantInvocation();
10154 fixTextureShadowModes();
10155 finalizeAppendMethods();
10156
10157 // Communicate out (esp. for command line) that we formed AST that will make
10158 // illegal AST SPIR-V and it needs transforms to legalize it.
10159 if (intermediate.needsLegalization() && (messages & EShMsgHlslLegalization))
10160 infoSink.info << "WARNING: AST will form illegal SPIR-V; need to transform to legalize";
10161
10162 TParseContextBase::finish();
10163 }
10164
10165 } // end namespace glslang
10166