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 // SM 4.0 and above guarantees roundEven semantics for round()
5496 if (!hlslDX9Compatible() && op == EOpRound)
5497 op = EOpRoundEven;
5498
5499 // A function call mapped to a built-in operation.
5500 result = intermediate.addBuiltInFunctionCall(loc, op, fnCandidate->getParamCount() == 1, arguments,
5501 fnCandidate->getType());
5502 if (result == nullptr) {
5503 error(arguments->getLoc(), " wrong operand type", "Internal Error",
5504 "built in unary operator function. Type: %s",
5505 static_cast<TIntermTyped*>(arguments)->getCompleteString().c_str());
5506 } else if (result->getAsOperator()) {
5507 builtInOpCheck(loc, *fnCandidate, *result->getAsOperator());
5508 }
5509 } else {
5510 // This is a function call not mapped to built-in operator.
5511 // It could still be a built-in function, but only if PureOperatorBuiltins == false.
5512 result = intermediate.setAggregateOperator(arguments, EOpFunctionCall, fnCandidate->getType(), loc);
5513 TIntermAggregate* call = result->getAsAggregate();
5514 call->setName(callerName);
5515
5516 // this is how we know whether the given function is a built-in function or a user-defined function
5517 // if builtIn == false, it's a userDefined -> could be an overloaded built-in function also
5518 // if builtIn == true, it's definitely a built-in function with EOpNull
5519 if (! builtIn) {
5520 call->setUserDefined();
5521 intermediate.addToCallGraph(infoSink, currentCaller, callerName);
5522 }
5523 }
5524
5525 // for decompositions, since we want to operate on the function node, not the aggregate holding
5526 // output conversions.
5527 const TIntermTyped* fnNode = result;
5528
5529 decomposeStructBufferMethods(loc, result, arguments); // HLSL->AST struct buffer method decompositions
5530 decomposeIntrinsic(loc, result, arguments); // HLSL->AST intrinsic decompositions
5531 decomposeSampleMethods(loc, result, arguments); // HLSL->AST sample method decompositions
5532 decomposeGeometryMethods(loc, result, arguments); // HLSL->AST geometry method decompositions
5533
5534 // Create the qualifier list, carried in the AST for the call.
5535 // Because some arguments expand to multiple arguments, the qualifier list will
5536 // be longer than the formal parameter list.
5537 if (result == fnNode && result->getAsAggregate()) {
5538 TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
5539 for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
5540 TStorageQualifier qual = (*fnCandidate)[i].type->getQualifier().storage;
5541 if (hasStructBuffCounter(*(*fnCandidate)[i].type)) {
5542 // add buffer and counter buffer argument qualifier
5543 qualifierList.push_back(qual);
5544 qualifierList.push_back(qual);
5545 } else if (shouldFlatten(*(*fnCandidate)[i].type, (*fnCandidate)[i].type->getQualifier().storage,
5546 true)) {
5547 // add structure member expansion
5548 for (int memb = 0; memb < (int)(*fnCandidate)[i].type->getStruct()->size(); ++memb)
5549 qualifierList.push_back(qual);
5550 } else {
5551 // Normal 1:1 case
5552 qualifierList.push_back(qual);
5553 }
5554 }
5555 }
5556
5557 // Convert 'out' arguments. If it was a constant folded built-in, it won't be an aggregate anymore.
5558 // Built-ins with a single argument aren't called with an aggregate, but they also don't have an output.
5559 // Also, build the qualifier list for user function calls, which are always called with an aggregate.
5560 // We don't do this is if there has been a decomposition, which will have added its own conversions
5561 // for output parameters.
5562 if (result == fnNode && result->getAsAggregate())
5563 result = addOutputArgumentConversions(*fnCandidate, *result->getAsOperator());
5564 }
5565 }
5566
5567 // generic error recovery
5568 // TODO: simplification: localize all the error recoveries that look like this, and taking type into account to
5569 // reduce cascades
5570 if (result == nullptr)
5571 result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
5572
5573 return result;
5574 }
5575
5576 // An initial argument list is difficult: it can be null, or a single node,
5577 // or an aggregate if more than one argument. Add one to the front, maintaining
5578 // this lack of uniformity.
pushFrontArguments(TIntermTyped * front,TIntermTyped * & arguments)5579 void HlslParseContext::pushFrontArguments(TIntermTyped* front, TIntermTyped*& arguments)
5580 {
5581 if (arguments == nullptr)
5582 arguments = front;
5583 else if (arguments->getAsAggregate() != nullptr)
5584 arguments->getAsAggregate()->getSequence().insert(arguments->getAsAggregate()->getSequence().begin(), front);
5585 else
5586 arguments = intermediate.growAggregate(front, arguments);
5587 }
5588
5589 //
5590 // HLSL allows mismatched dimensions on vec*mat, mat*vec, vec*vec, and mat*mat. This is a
5591 // situation not well suited to resolution in intrinsic selection, but we can do so here, since we
5592 // can look at both arguments insert explicit shape changes if required.
5593 //
addGenMulArgumentConversion(const TSourceLoc & loc,TFunction & call,TIntermTyped * & args)5594 void HlslParseContext::addGenMulArgumentConversion(const TSourceLoc& loc, TFunction& call, TIntermTyped*& args)
5595 {
5596 TIntermAggregate* argAggregate = args ? args->getAsAggregate() : nullptr;
5597
5598 if (argAggregate == nullptr || argAggregate->getSequence().size() != 2) {
5599 // It really ought to have two arguments.
5600 error(loc, "expected: mul arguments", "", "");
5601 return;
5602 }
5603
5604 TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
5605 TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
5606
5607 if (arg0->isVector() && arg1->isVector()) {
5608 // For:
5609 // vec * vec: it's handled during intrinsic selection, so while we could do it here,
5610 // we can also ignore it, which is easier.
5611 } else if (arg0->isVector() && arg1->isMatrix()) {
5612 // vec * mat: we clamp the vec if the mat col is smaller, else clamp the mat col.
5613 if (arg0->getVectorSize() < arg1->getMatrixCols()) {
5614 // vec is smaller, so truncate larger mat dimension
5615 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5616 0, arg0->getVectorSize(), arg1->getMatrixRows());
5617 arg1 = addConstructor(loc, arg1, truncType);
5618 } else if (arg0->getVectorSize() > arg1->getMatrixCols()) {
5619 // vec is larger, so truncate vec to mat size
5620 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5621 arg1->getMatrixCols());
5622 arg0 = addConstructor(loc, arg0, truncType);
5623 }
5624 } else if (arg0->isMatrix() && arg1->isVector()) {
5625 // mat * vec: we clamp the vec if the mat col is smaller, else clamp the mat col.
5626 if (arg1->getVectorSize() < arg0->getMatrixRows()) {
5627 // vec is smaller, so truncate larger mat dimension
5628 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5629 0, arg0->getMatrixCols(), arg1->getVectorSize());
5630 arg0 = addConstructor(loc, arg0, truncType);
5631 } else if (arg1->getVectorSize() > arg0->getMatrixRows()) {
5632 // vec is larger, so truncate vec to mat size
5633 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5634 arg0->getMatrixRows());
5635 arg1 = addConstructor(loc, arg1, truncType);
5636 }
5637 } else if (arg0->isMatrix() && arg1->isMatrix()) {
5638 // mat * mat: we clamp the smaller inner dimension to match the other matrix size.
5639 // Remember, HLSL Mrc = GLSL/SPIRV Mcr.
5640 if (arg0->getMatrixRows() > arg1->getMatrixCols()) {
5641 const TType truncType(arg0->getBasicType(), arg0->getQualifier().storage, arg0->getQualifier().precision,
5642 0, arg0->getMatrixCols(), arg1->getMatrixCols());
5643 arg0 = addConstructor(loc, arg0, truncType);
5644 } else if (arg0->getMatrixRows() < arg1->getMatrixCols()) {
5645 const TType truncType(arg1->getBasicType(), arg1->getQualifier().storage, arg1->getQualifier().precision,
5646 0, arg0->getMatrixRows(), arg1->getMatrixRows());
5647 arg1 = addConstructor(loc, arg1, truncType);
5648 }
5649 } else {
5650 // It's something with scalars: we'll just leave it alone. Function selection will handle it
5651 // downstream.
5652 }
5653
5654 // Warn if we altered one of the arguments
5655 if (arg0 != argAggregate->getSequence()[0] || arg1 != argAggregate->getSequence()[1])
5656 warn(loc, "mul() matrix size mismatch", "", "");
5657
5658 // Put arguments back. (They might be unchanged, in which case this is harmless).
5659 argAggregate->getSequence()[0] = arg0;
5660 argAggregate->getSequence()[1] = arg1;
5661
5662 call[0].type = &arg0->getWritableType();
5663 call[1].type = &arg1->getWritableType();
5664 }
5665
5666 //
5667 // Add any needed implicit conversions for function-call arguments to input parameters.
5668 //
addInputArgumentConversions(const TFunction & function,TIntermTyped * & arguments)5669 void HlslParseContext::addInputArgumentConversions(const TFunction& function, TIntermTyped*& arguments)
5670 {
5671 TIntermAggregate* aggregate = arguments->getAsAggregate();
5672
5673 // Replace a single argument with a single argument.
5674 const auto setArg = [&](int paramNum, TIntermTyped* arg) {
5675 if (function.getParamCount() == 1)
5676 arguments = arg;
5677 else {
5678 if (aggregate == nullptr)
5679 arguments = arg;
5680 else
5681 aggregate->getSequence()[paramNum] = arg;
5682 }
5683 };
5684
5685 // Process each argument's conversion
5686 for (int param = 0; param < function.getParamCount(); ++param) {
5687 if (! function[param].type->getQualifier().isParamInput())
5688 continue;
5689
5690 // At this early point there is a slight ambiguity between whether an aggregate 'arguments'
5691 // is the single argument itself or its children are the arguments. Only one argument
5692 // means take 'arguments' itself as the one argument.
5693 TIntermTyped* arg = function.getParamCount() == 1
5694 ? arguments->getAsTyped()
5695 : (aggregate ?
5696 aggregate->getSequence()[param]->getAsTyped() :
5697 arguments->getAsTyped());
5698 if (*function[param].type != arg->getType()) {
5699 // In-qualified arguments just need an extra node added above the argument to
5700 // convert to the correct type.
5701 TIntermTyped* convArg = intermediate.addConversion(EOpFunctionCall, *function[param].type, arg);
5702 if (convArg != nullptr)
5703 convArg = intermediate.addUniShapeConversion(EOpFunctionCall, *function[param].type, convArg);
5704 if (convArg != nullptr)
5705 setArg(param, convArg);
5706 else
5707 error(arg->getLoc(), "cannot convert input argument, argument", "", "%d", param);
5708 } else {
5709 if (wasFlattened(arg)) {
5710 // If both formal and calling arg are to be flattened, leave that to argument
5711 // expansion, not conversion.
5712 if (!shouldFlatten(*function[param].type, function[param].type->getQualifier().storage, true)) {
5713 // Will make a two-level subtree.
5714 // The deepest will copy member-by-member to build the structure to pass.
5715 // The level above that will be a two-operand EOpComma sequence that follows the copy by the
5716 // object itself.
5717 TVariable* internalAggregate = makeInternalVariable("aggShadow", *function[param].type);
5718 internalAggregate->getWritableType().getQualifier().makeTemporary();
5719 TIntermSymbol* internalSymbolNode = new TIntermSymbol(internalAggregate->getUniqueId(),
5720 internalAggregate->getName(),
5721 internalAggregate->getType());
5722 internalSymbolNode->setLoc(arg->getLoc());
5723 // This makes the deepest level, the member-wise copy
5724 TIntermAggregate* assignAgg = handleAssign(arg->getLoc(), EOpAssign,
5725 internalSymbolNode, arg)->getAsAggregate();
5726
5727 // Now, pair that with the resulting aggregate.
5728 assignAgg = intermediate.growAggregate(assignAgg, internalSymbolNode, arg->getLoc());
5729 assignAgg->setOperator(EOpComma);
5730 assignAgg->setType(internalAggregate->getType());
5731 setArg(param, assignAgg);
5732 }
5733 }
5734 }
5735 }
5736 }
5737
5738 //
5739 // Add any needed implicit expansion of calling arguments from what the shader listed to what's
5740 // internally needed for the AST (given the constraints downstream).
5741 //
expandArguments(const TSourceLoc & loc,const TFunction & function,TIntermTyped * & arguments)5742 void HlslParseContext::expandArguments(const TSourceLoc& loc, const TFunction& function, TIntermTyped*& arguments)
5743 {
5744 TIntermAggregate* aggregate = arguments->getAsAggregate();
5745 int functionParamNumberOffset = 0;
5746
5747 // Replace a single argument with a single argument.
5748 const auto setArg = [&](int paramNum, TIntermTyped* arg) {
5749 if (function.getParamCount() + functionParamNumberOffset == 1)
5750 arguments = arg;
5751 else {
5752 if (aggregate == nullptr)
5753 arguments = arg;
5754 else
5755 aggregate->getSequence()[paramNum] = arg;
5756 }
5757 };
5758
5759 // Replace a single argument with a list of arguments
5760 const auto setArgList = [&](int paramNum, const TVector<TIntermTyped*>& args) {
5761 if (args.size() == 1)
5762 setArg(paramNum, args.front());
5763 else if (args.size() > 1) {
5764 if (function.getParamCount() + functionParamNumberOffset == 1) {
5765 arguments = intermediate.makeAggregate(args.front());
5766 std::for_each(args.begin() + 1, args.end(),
5767 [&](TIntermTyped* arg) {
5768 arguments = intermediate.growAggregate(arguments, arg);
5769 });
5770 } else {
5771 auto it = aggregate->getSequence().erase(aggregate->getSequence().begin() + paramNum);
5772 aggregate->getSequence().insert(it, args.begin(), args.end());
5773 }
5774 functionParamNumberOffset += (int)(args.size() - 1);
5775 }
5776 };
5777
5778 // Process each argument's conversion
5779 for (int param = 0; param < function.getParamCount(); ++param) {
5780 // At this early point there is a slight ambiguity between whether an aggregate 'arguments'
5781 // is the single argument itself or its children are the arguments. Only one argument
5782 // means take 'arguments' itself as the one argument.
5783 TIntermTyped* arg = function.getParamCount() == 1
5784 ? arguments->getAsTyped()
5785 : (aggregate ?
5786 aggregate->getSequence()[param + functionParamNumberOffset]->getAsTyped() :
5787 arguments->getAsTyped());
5788
5789 if (wasFlattened(arg) && shouldFlatten(*function[param].type, function[param].type->getQualifier().storage, true)) {
5790 // Need to pass the structure members instead of the structure.
5791 TVector<TIntermTyped*> memberArgs;
5792 for (int memb = 0; memb < (int)arg->getType().getStruct()->size(); ++memb)
5793 memberArgs.push_back(flattenAccess(arg, memb));
5794 setArgList(param + functionParamNumberOffset, memberArgs);
5795 }
5796 }
5797
5798 // TODO: if we need both hidden counter args (below) and struct expansion (above)
5799 // the two algorithms need to be merged: Each assumes the list starts out 1:1 between
5800 // parameters and arguments.
5801
5802 // If any argument is a pass-by-reference struct buffer with an associated counter
5803 // buffer, we have to add another hidden parameter for that counter.
5804 if (aggregate)
5805 addStructBuffArguments(loc, aggregate);
5806 }
5807
5808 //
5809 // Add any needed implicit output conversions for function-call arguments. This
5810 // can require a new tree topology, complicated further by whether the function
5811 // has a return value.
5812 //
5813 // Returns a node of a subtree that evaluates to the return value of the function.
5814 //
addOutputArgumentConversions(const TFunction & function,TIntermOperator & intermNode)5815 TIntermTyped* HlslParseContext::addOutputArgumentConversions(const TFunction& function, TIntermOperator& intermNode)
5816 {
5817 assert (intermNode.getAsAggregate() != nullptr || intermNode.getAsUnaryNode() != nullptr);
5818
5819 const TSourceLoc& loc = intermNode.getLoc();
5820
5821 TIntermSequence argSequence; // temp sequence for unary node args
5822
5823 if (intermNode.getAsUnaryNode())
5824 argSequence.push_back(intermNode.getAsUnaryNode()->getOperand());
5825
5826 TIntermSequence& arguments = argSequence.empty() ? intermNode.getAsAggregate()->getSequence() : argSequence;
5827
5828 const auto needsConversion = [&](int argNum) {
5829 return function[argNum].type->getQualifier().isParamOutput() &&
5830 (*function[argNum].type != arguments[argNum]->getAsTyped()->getType() ||
5831 shouldConvertLValue(arguments[argNum]) ||
5832 wasFlattened(arguments[argNum]->getAsTyped()));
5833 };
5834
5835 // Will there be any output conversions?
5836 bool outputConversions = false;
5837 for (int i = 0; i < function.getParamCount(); ++i) {
5838 if (needsConversion(i)) {
5839 outputConversions = true;
5840 break;
5841 }
5842 }
5843
5844 if (! outputConversions)
5845 return &intermNode;
5846
5847 // Setup for the new tree, if needed:
5848 //
5849 // Output conversions need a different tree topology.
5850 // Out-qualified arguments need a temporary of the correct type, with the call
5851 // followed by an assignment of the temporary to the original argument:
5852 // void: function(arg, ...) -> ( function(tempArg, ...), arg = tempArg, ...)
5853 // ret = function(arg, ...) -> ret = (tempRet = function(tempArg, ...), arg = tempArg, ..., tempRet)
5854 // Where the "tempArg" type needs no conversion as an argument, but will convert on assignment.
5855 TIntermTyped* conversionTree = nullptr;
5856 TVariable* tempRet = nullptr;
5857 if (intermNode.getBasicType() != EbtVoid) {
5858 // do the "tempRet = function(...), " bit from above
5859 tempRet = makeInternalVariable("tempReturn", intermNode.getType());
5860 TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, loc);
5861 conversionTree = intermediate.addAssign(EOpAssign, tempRetNode, &intermNode, loc);
5862 } else
5863 conversionTree = &intermNode;
5864
5865 conversionTree = intermediate.makeAggregate(conversionTree);
5866
5867 // Process each argument's conversion
5868 for (int i = 0; i < function.getParamCount(); ++i) {
5869 if (needsConversion(i)) {
5870 // Out-qualified arguments needing conversion need to use the topology setup above.
5871 // Do the " ...(tempArg, ...), arg = tempArg" bit from above.
5872
5873 // Make a temporary for what the function expects the argument to look like.
5874 TVariable* tempArg = makeInternalVariable("tempArg", *function[i].type);
5875 tempArg->getWritableType().getQualifier().makeTemporary();
5876 TIntermSymbol* tempArgNode = intermediate.addSymbol(*tempArg, loc);
5877
5878 // This makes the deepest level, the member-wise copy
5879 TIntermTyped* tempAssign = handleAssign(arguments[i]->getLoc(), EOpAssign, arguments[i]->getAsTyped(),
5880 tempArgNode);
5881 tempAssign = handleLvalue(arguments[i]->getLoc(), "assign", tempAssign);
5882 conversionTree = intermediate.growAggregate(conversionTree, tempAssign, arguments[i]->getLoc());
5883
5884 // replace the argument with another node for the same tempArg variable
5885 arguments[i] = intermediate.addSymbol(*tempArg, loc);
5886 }
5887 }
5888
5889 // Finalize the tree topology (see bigger comment above).
5890 if (tempRet) {
5891 // do the "..., tempRet" bit from above
5892 TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, loc);
5893 conversionTree = intermediate.growAggregate(conversionTree, tempRetNode, loc);
5894 }
5895
5896 conversionTree = intermediate.setAggregateOperator(conversionTree, EOpComma, intermNode.getType(), loc);
5897
5898 return conversionTree;
5899 }
5900
5901 //
5902 // Add any needed "hidden" counter buffer arguments for function calls.
5903 //
5904 // Modifies the 'aggregate' argument if needed. Otherwise, is no-op.
5905 //
addStructBuffArguments(const TSourceLoc & loc,TIntermAggregate * & aggregate)5906 void HlslParseContext::addStructBuffArguments(const TSourceLoc& loc, TIntermAggregate*& aggregate)
5907 {
5908 // See if there are any SB types with counters.
5909 const bool hasStructBuffArg =
5910 std::any_of(aggregate->getSequence().begin(),
5911 aggregate->getSequence().end(),
5912 [this](const TIntermNode* node) {
5913 return (node && node->getAsTyped() != nullptr) && hasStructBuffCounter(node->getAsTyped()->getType());
5914 });
5915
5916 // Nothing to do, if we didn't find one.
5917 if (! hasStructBuffArg)
5918 return;
5919
5920 TIntermSequence argsWithCounterBuffers;
5921
5922 for (int param = 0; param < int(aggregate->getSequence().size()); ++param) {
5923 argsWithCounterBuffers.push_back(aggregate->getSequence()[param]);
5924
5925 if (hasStructBuffCounter(aggregate->getSequence()[param]->getAsTyped()->getType())) {
5926 const TIntermSymbol* blockSym = aggregate->getSequence()[param]->getAsSymbolNode();
5927 if (blockSym != nullptr) {
5928 TType counterType;
5929 counterBufferType(loc, counterType);
5930
5931 const TString counterBlockName(intermediate.addCounterBufferName(blockSym->getName()));
5932
5933 TVariable* variable = makeInternalVariable(counterBlockName, counterType);
5934
5935 // Mark this buffer's counter block as being in use
5936 structBufferCounter[counterBlockName] = true;
5937
5938 TIntermSymbol* sym = intermediate.addSymbol(*variable, loc);
5939 argsWithCounterBuffers.push_back(sym);
5940 }
5941 }
5942 }
5943
5944 // Swap with the temp list we've built up.
5945 aggregate->getSequence().swap(argsWithCounterBuffers);
5946 }
5947
5948
5949 //
5950 // Do additional checking of built-in function calls that is not caught
5951 // by normal semantic checks on argument type, extension tagging, etc.
5952 //
5953 // Assumes there has been a semantically correct match to a built-in function prototype.
5954 //
builtInOpCheck(const TSourceLoc & loc,const TFunction & fnCandidate,TIntermOperator & callNode)5955 void HlslParseContext::builtInOpCheck(const TSourceLoc& loc, const TFunction& fnCandidate, TIntermOperator& callNode)
5956 {
5957 // Set up convenience accessors to the argument(s). There is almost always
5958 // multiple arguments for the cases below, but when there might be one,
5959 // check the unaryArg first.
5960 const TIntermSequence* argp = nullptr; // confusing to use [] syntax on a pointer, so this is to help get a reference
5961 const TIntermTyped* unaryArg = nullptr;
5962 const TIntermTyped* arg0 = nullptr;
5963 if (callNode.getAsAggregate()) {
5964 argp = &callNode.getAsAggregate()->getSequence();
5965 if (argp->size() > 0)
5966 arg0 = (*argp)[0]->getAsTyped();
5967 } else {
5968 assert(callNode.getAsUnaryNode());
5969 unaryArg = callNode.getAsUnaryNode()->getOperand();
5970 arg0 = unaryArg;
5971 }
5972 const TIntermSequence& aggArgs = *argp; // only valid when unaryArg is nullptr
5973
5974 switch (callNode.getOp()) {
5975 case EOpTextureGather:
5976 case EOpTextureGatherOffset:
5977 case EOpTextureGatherOffsets:
5978 {
5979 // Figure out which variants are allowed by what extensions,
5980 // and what arguments must be constant for which situations.
5981
5982 TString featureString = fnCandidate.getName() + "(...)";
5983 const char* feature = featureString.c_str();
5984 int compArg = -1; // track which argument, if any, is the constant component argument
5985 switch (callNode.getOp()) {
5986 case EOpTextureGather:
5987 // More than two arguments needs gpu_shader5, and rectangular or shadow needs gpu_shader5,
5988 // otherwise, need GL_ARB_texture_gather.
5989 if (fnCandidate.getParamCount() > 2 || fnCandidate[0].type->getSampler().dim == EsdRect ||
5990 fnCandidate[0].type->getSampler().shadow) {
5991 if (! fnCandidate[0].type->getSampler().shadow)
5992 compArg = 2;
5993 }
5994 break;
5995 case EOpTextureGatherOffset:
5996 // GL_ARB_texture_gather is good enough for 2D non-shadow textures with no component argument
5997 if (! fnCandidate[0].type->getSampler().shadow)
5998 compArg = 3;
5999 break;
6000 case EOpTextureGatherOffsets:
6001 if (! fnCandidate[0].type->getSampler().shadow)
6002 compArg = 3;
6003 break;
6004 default:
6005 break;
6006 }
6007
6008 if (compArg > 0 && compArg < fnCandidate.getParamCount()) {
6009 if (aggArgs[compArg]->getAsConstantUnion()) {
6010 int value = aggArgs[compArg]->getAsConstantUnion()->getConstArray()[0].getIConst();
6011 if (value < 0 || value > 3)
6012 error(loc, "must be 0, 1, 2, or 3:", feature, "component argument");
6013 } else
6014 error(loc, "must be a compile-time constant:", feature, "component argument");
6015 }
6016
6017 break;
6018 }
6019
6020 case EOpTextureOffset:
6021 case EOpTextureFetchOffset:
6022 case EOpTextureProjOffset:
6023 case EOpTextureLodOffset:
6024 case EOpTextureProjLodOffset:
6025 case EOpTextureGradOffset:
6026 case EOpTextureProjGradOffset:
6027 {
6028 // Handle texture-offset limits checking
6029 // Pick which argument has to hold constant offsets
6030 int arg = -1;
6031 switch (callNode.getOp()) {
6032 case EOpTextureOffset: arg = 2; break;
6033 case EOpTextureFetchOffset: arg = (arg0->getType().getSampler().dim != EsdRect) ? 3 : 2; break;
6034 case EOpTextureProjOffset: arg = 2; break;
6035 case EOpTextureLodOffset: arg = 3; break;
6036 case EOpTextureProjLodOffset: arg = 3; break;
6037 case EOpTextureGradOffset: arg = 4; break;
6038 case EOpTextureProjGradOffset: arg = 4; break;
6039 default:
6040 assert(0);
6041 break;
6042 }
6043
6044 if (arg > 0) {
6045 if (aggArgs[arg]->getAsConstantUnion() == nullptr)
6046 error(loc, "argument must be compile-time constant", "texel offset", "");
6047 else {
6048 const TType& type = aggArgs[arg]->getAsTyped()->getType();
6049 for (int c = 0; c < type.getVectorSize(); ++c) {
6050 int offset = aggArgs[arg]->getAsConstantUnion()->getConstArray()[c].getIConst();
6051 if (offset > resources.maxProgramTexelOffset || offset < resources.minProgramTexelOffset)
6052 error(loc, "value is out of range:", "texel offset",
6053 "[gl_MinProgramTexelOffset, gl_MaxProgramTexelOffset]");
6054 }
6055 }
6056 }
6057
6058 break;
6059 }
6060
6061 case EOpTextureQuerySamples:
6062 case EOpImageQuerySamples:
6063 break;
6064
6065 case EOpImageAtomicAdd:
6066 case EOpImageAtomicMin:
6067 case EOpImageAtomicMax:
6068 case EOpImageAtomicAnd:
6069 case EOpImageAtomicOr:
6070 case EOpImageAtomicXor:
6071 case EOpImageAtomicExchange:
6072 case EOpImageAtomicCompSwap:
6073 break;
6074
6075 case EOpInterpolateAtCentroid:
6076 case EOpInterpolateAtSample:
6077 case EOpInterpolateAtOffset:
6078 // Make sure the first argument is an interpolant, or an array element of an interpolant
6079 if (arg0->getType().getQualifier().storage != EvqVaryingIn) {
6080 // It might still be an array element.
6081 //
6082 // We could check more, but the semantics of the first argument are already met; the
6083 // only way to turn an array into a float/vec* is array dereference and swizzle.
6084 //
6085 // ES and desktop 4.3 and earlier: swizzles may not be used
6086 // desktop 4.4 and later: swizzles may be used
6087 const TIntermTyped* base = TIntermediate::findLValueBase(arg0, true);
6088 if (base == nullptr || base->getType().getQualifier().storage != EvqVaryingIn)
6089 error(loc, "first argument must be an interpolant, or interpolant-array element",
6090 fnCandidate.getName().c_str(), "");
6091 }
6092 break;
6093
6094 default:
6095 break;
6096 }
6097 }
6098
6099 //
6100 // Handle seeing something in a grammar production that can be done by calling
6101 // a constructor.
6102 //
6103 // The constructor still must be "handled" by handleFunctionCall(), which will
6104 // then call handleConstructor().
6105 //
makeConstructorCall(const TSourceLoc & loc,const TType & type)6106 TFunction* HlslParseContext::makeConstructorCall(const TSourceLoc& loc, const TType& type)
6107 {
6108 TOperator op = intermediate.mapTypeToConstructorOp(type);
6109
6110 if (op == EOpNull) {
6111 error(loc, "cannot construct this type", type.getBasicString(), "");
6112 return nullptr;
6113 }
6114
6115 TString empty("");
6116
6117 return new TFunction(&empty, type, op);
6118 }
6119
6120 //
6121 // Handle seeing a "COLON semantic" at the end of a type declaration,
6122 // by updating the type according to the semantic.
6123 //
handleSemantic(TSourceLoc loc,TQualifier & qualifier,TBuiltInVariable builtIn,const TString & upperCase)6124 void HlslParseContext::handleSemantic(TSourceLoc loc, TQualifier& qualifier, TBuiltInVariable builtIn,
6125 const TString& upperCase)
6126 {
6127 // Parse and return semantic number. If limit is 0, it will be ignored. Otherwise, if the parsed
6128 // semantic number is >= limit, errorMsg is issued and 0 is returned.
6129 // TODO: it would be nicer if limit and errorMsg had default parameters, but some compilers don't yet
6130 // accept those in lambda functions.
6131 const auto getSemanticNumber = [this, loc](const TString& semantic, unsigned int limit, const char* errorMsg) -> unsigned int {
6132 size_t pos = semantic.find_last_not_of("0123456789");
6133 if (pos == std::string::npos)
6134 return 0u;
6135
6136 unsigned int semanticNum = (unsigned int)atoi(semantic.c_str() + pos + 1);
6137
6138 if (limit != 0 && semanticNum >= limit) {
6139 error(loc, errorMsg, semantic.c_str(), "");
6140 return 0u;
6141 }
6142
6143 return semanticNum;
6144 };
6145
6146 if (builtIn == EbvNone && hlslDX9Compatible()) {
6147 if (language == EShLangVertex) {
6148 if (qualifier.isParamOutput()) {
6149 if (upperCase == "POSITION") {
6150 builtIn = EbvPosition;
6151 }
6152 if (upperCase == "PSIZE") {
6153 builtIn = EbvPointSize;
6154 }
6155 }
6156 } else if (language == EShLangFragment) {
6157 if (qualifier.isParamInput() && upperCase == "VPOS") {
6158 builtIn = EbvFragCoord;
6159 }
6160 if (qualifier.isParamOutput()) {
6161 if (upperCase.compare(0, 5, "COLOR") == 0) {
6162 qualifier.layoutLocation = getSemanticNumber(upperCase, 0, nullptr);
6163 nextOutLocation = std::max(nextOutLocation, qualifier.layoutLocation + 1u);
6164 }
6165 if (upperCase == "DEPTH") {
6166 builtIn = EbvFragDepth;
6167 }
6168 }
6169 }
6170 }
6171
6172 switch(builtIn) {
6173 case EbvNone:
6174 // Get location numbers from fragment outputs, instead of
6175 // auto-assigning them.
6176 if (language == EShLangFragment && upperCase.compare(0, 9, "SV_TARGET") == 0) {
6177 qualifier.layoutLocation = getSemanticNumber(upperCase, 0, nullptr);
6178 nextOutLocation = std::max(nextOutLocation, qualifier.layoutLocation + 1u);
6179 } else if (upperCase.compare(0, 15, "SV_CLIPDISTANCE") == 0) {
6180 builtIn = EbvClipDistance;
6181 qualifier.layoutLocation = getSemanticNumber(upperCase, maxClipCullRegs, "invalid clip semantic");
6182 } else if (upperCase.compare(0, 15, "SV_CULLDISTANCE") == 0) {
6183 builtIn = EbvCullDistance;
6184 qualifier.layoutLocation = getSemanticNumber(upperCase, maxClipCullRegs, "invalid cull semantic");
6185 }
6186 break;
6187 case EbvPosition:
6188 // adjust for stage in/out
6189 if (language == EShLangFragment)
6190 builtIn = EbvFragCoord;
6191 break;
6192 case EbvFragStencilRef:
6193 error(loc, "unimplemented; need ARB_shader_stencil_export", "SV_STENCILREF", "");
6194 break;
6195 case EbvTessLevelInner:
6196 case EbvTessLevelOuter:
6197 qualifier.patch = true;
6198 break;
6199 default:
6200 break;
6201 }
6202
6203 if (qualifier.builtIn == EbvNone)
6204 qualifier.builtIn = builtIn;
6205 qualifier.semanticName = intermediate.addSemanticName(upperCase);
6206 }
6207
6208 //
6209 // Handle seeing something like "PACKOFFSET LEFT_PAREN c[Subcomponent][.component] RIGHT_PAREN"
6210 //
6211 // 'location' has the "c[Subcomponent]" part.
6212 // 'component' points to the "component" part, or nullptr if not present.
6213 //
handlePackOffset(const TSourceLoc & loc,TQualifier & qualifier,const glslang::TString & location,const glslang::TString * component)6214 void HlslParseContext::handlePackOffset(const TSourceLoc& loc, TQualifier& qualifier, const glslang::TString& location,
6215 const glslang::TString* component)
6216 {
6217 if (location.size() == 0 || location[0] != 'c') {
6218 error(loc, "expected 'c'", "packoffset", "");
6219 return;
6220 }
6221 if (location.size() == 1)
6222 return;
6223 if (! isdigit(location[1])) {
6224 error(loc, "expected number after 'c'", "packoffset", "");
6225 return;
6226 }
6227
6228 qualifier.layoutOffset = 16 * atoi(location.substr(1, location.size()).c_str());
6229 if (component != nullptr) {
6230 int componentOffset = 0;
6231 switch ((*component)[0]) {
6232 case 'x': componentOffset = 0; break;
6233 case 'y': componentOffset = 4; break;
6234 case 'z': componentOffset = 8; break;
6235 case 'w': componentOffset = 12; break;
6236 default:
6237 componentOffset = -1;
6238 break;
6239 }
6240 if (componentOffset < 0 || component->size() > 1) {
6241 error(loc, "expected {x, y, z, w} for component", "packoffset", "");
6242 return;
6243 }
6244 qualifier.layoutOffset += componentOffset;
6245 }
6246 }
6247
6248 //
6249 // Handle seeing something like "REGISTER LEFT_PAREN [shader_profile,] Type# RIGHT_PAREN"
6250 //
6251 // 'profile' points to the shader_profile part, or nullptr if not present.
6252 // 'desc' is the type# part.
6253 //
handleRegister(const TSourceLoc & loc,TQualifier & qualifier,const glslang::TString * profile,const glslang::TString & desc,int subComponent,const glslang::TString * spaceDesc)6254 void HlslParseContext::handleRegister(const TSourceLoc& loc, TQualifier& qualifier, const glslang::TString* profile,
6255 const glslang::TString& desc, int subComponent, const glslang::TString* spaceDesc)
6256 {
6257 if (profile != nullptr)
6258 warn(loc, "ignoring shader_profile", "register", "");
6259
6260 if (desc.size() < 1) {
6261 error(loc, "expected register type", "register", "");
6262 return;
6263 }
6264
6265 int regNumber = 0;
6266 if (desc.size() > 1) {
6267 if (isdigit(desc[1]))
6268 regNumber = atoi(desc.substr(1, desc.size()).c_str());
6269 else {
6270 error(loc, "expected register number after register type", "register", "");
6271 return;
6272 }
6273 }
6274
6275 // more information about register types see
6276 // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-register
6277 const std::vector<std::string>& resourceInfo = intermediate.getResourceSetBinding();
6278 switch (std::tolower(desc[0])) {
6279 case 'c':
6280 // c register is the register slot in the global const buffer
6281 // each slot is a vector of 4 32 bit components
6282 qualifier.layoutOffset = regNumber * 4 * 4;
6283 break;
6284 // const buffer register slot
6285 case 'b':
6286 // textrues and structured buffers
6287 case 't':
6288 // samplers
6289 case 's':
6290 // uav resources
6291 case 'u':
6292 // if nothing else has set the binding, do so now
6293 // (other mechanisms override this one)
6294 if (!qualifier.hasBinding())
6295 qualifier.layoutBinding = regNumber + subComponent;
6296
6297 // This handles per-register layout sets numbers. For the global mode which sets
6298 // every symbol to the same value, see setLinkageLayoutSets().
6299 if ((resourceInfo.size() % 3) == 0) {
6300 // Apply per-symbol resource set and binding.
6301 for (auto it = resourceInfo.cbegin(); it != resourceInfo.cend(); it = it + 3) {
6302 if (strcmp(desc.c_str(), it[0].c_str()) == 0) {
6303 qualifier.layoutSet = atoi(it[1].c_str());
6304 qualifier.layoutBinding = atoi(it[2].c_str()) + subComponent;
6305 break;
6306 }
6307 }
6308 }
6309 break;
6310 default:
6311 warn(loc, "ignoring unrecognized register type", "register", "%c", desc[0]);
6312 break;
6313 }
6314
6315 // space
6316 unsigned int setNumber;
6317 const auto crackSpace = [&]() -> bool {
6318 const int spaceLen = 5;
6319 if (spaceDesc->size() < spaceLen + 1)
6320 return false;
6321 if (spaceDesc->compare(0, spaceLen, "space") != 0)
6322 return false;
6323 if (! isdigit((*spaceDesc)[spaceLen]))
6324 return false;
6325 setNumber = atoi(spaceDesc->substr(spaceLen, spaceDesc->size()).c_str());
6326 return true;
6327 };
6328
6329 // if nothing else has set the set, do so now
6330 // (other mechanisms override this one)
6331 if (spaceDesc && !qualifier.hasSet()) {
6332 if (! crackSpace()) {
6333 error(loc, "expected spaceN", "register", "");
6334 return;
6335 }
6336 qualifier.layoutSet = setNumber;
6337 }
6338 }
6339
6340 // Convert to a scalar boolean, or if not allowed by HLSL semantics,
6341 // report an error and return nullptr.
convertConditionalExpression(const TSourceLoc & loc,TIntermTyped * condition,bool mustBeScalar)6342 TIntermTyped* HlslParseContext::convertConditionalExpression(const TSourceLoc& loc, TIntermTyped* condition,
6343 bool mustBeScalar)
6344 {
6345 if (mustBeScalar && !condition->getType().isScalarOrVec1()) {
6346 error(loc, "requires a scalar", "conditional expression", "");
6347 return nullptr;
6348 }
6349
6350 return intermediate.addConversion(EOpConstructBool, TType(EbtBool, EvqTemporary, condition->getVectorSize()),
6351 condition);
6352 }
6353
6354 //
6355 // Same error message for all places assignments don't work.
6356 //
assignError(const TSourceLoc & loc,const char * op,TString left,TString right)6357 void HlslParseContext::assignError(const TSourceLoc& loc, const char* op, TString left, TString right)
6358 {
6359 error(loc, "", op, "cannot convert from '%s' to '%s'",
6360 right.c_str(), left.c_str());
6361 }
6362
6363 //
6364 // Same error message for all places unary operations don't work.
6365 //
unaryOpError(const TSourceLoc & loc,const char * op,TString operand)6366 void HlslParseContext::unaryOpError(const TSourceLoc& loc, const char* op, TString operand)
6367 {
6368 error(loc, " wrong operand type", op,
6369 "no operation '%s' exists that takes an operand of type %s (or there is no acceptable conversion)",
6370 op, operand.c_str());
6371 }
6372
6373 //
6374 // Same error message for all binary operations don't work.
6375 //
binaryOpError(const TSourceLoc & loc,const char * op,TString left,TString right)6376 void HlslParseContext::binaryOpError(const TSourceLoc& loc, const char* op, TString left, TString right)
6377 {
6378 error(loc, " wrong operand types:", op,
6379 "no operation '%s' exists that takes a left-hand operand of type '%s' and "
6380 "a right operand of type '%s' (or there is no acceptable conversion)",
6381 op, left.c_str(), right.c_str());
6382 }
6383
6384 //
6385 // A basic type of EbtVoid is a key that the name string was seen in the source, but
6386 // it was not found as a variable in the symbol table. If so, give the error
6387 // message and insert a dummy variable in the symbol table to prevent future errors.
6388 //
variableCheck(TIntermTyped * & nodePtr)6389 void HlslParseContext::variableCheck(TIntermTyped*& nodePtr)
6390 {
6391 TIntermSymbol* symbol = nodePtr->getAsSymbolNode();
6392 if (! symbol)
6393 return;
6394
6395 if (symbol->getType().getBasicType() == EbtVoid) {
6396 error(symbol->getLoc(), "undeclared identifier", symbol->getName().c_str(), "");
6397
6398 // Add to symbol table to prevent future error messages on the same name
6399 if (symbol->getName().size() > 0) {
6400 TVariable* fakeVariable = new TVariable(&symbol->getName(), TType(EbtFloat));
6401 symbolTable.insert(*fakeVariable);
6402
6403 // substitute a symbol node for this new variable
6404 nodePtr = intermediate.addSymbol(*fakeVariable, symbol->getLoc());
6405 }
6406 }
6407 }
6408
6409 //
6410 // Both test, and if necessary spit out an error, to see if the node is really
6411 // a constant.
6412 //
constantValueCheck(TIntermTyped * node,const char * token)6413 void HlslParseContext::constantValueCheck(TIntermTyped* node, const char* token)
6414 {
6415 if (node->getQualifier().storage != EvqConst)
6416 error(node->getLoc(), "constant expression required", token, "");
6417 }
6418
6419 //
6420 // Both test, and if necessary spit out an error, to see if the node is really
6421 // an integer.
6422 //
integerCheck(const TIntermTyped * node,const char * token)6423 void HlslParseContext::integerCheck(const TIntermTyped* node, const char* token)
6424 {
6425 if ((node->getBasicType() == EbtInt || node->getBasicType() == EbtUint) && node->isScalar())
6426 return;
6427
6428 error(node->getLoc(), "scalar integer expression required", token, "");
6429 }
6430
6431 //
6432 // Both test, and if necessary spit out an error, to see if we are currently
6433 // globally scoped.
6434 //
globalCheck(const TSourceLoc & loc,const char * token)6435 void HlslParseContext::globalCheck(const TSourceLoc& loc, const char* token)
6436 {
6437 if (! symbolTable.atGlobalLevel())
6438 error(loc, "not allowed in nested scope", token, "");
6439 }
6440
builtInName(const TString &)6441 bool HlslParseContext::builtInName(const TString& /*identifier*/)
6442 {
6443 return false;
6444 }
6445
6446 //
6447 // Make sure there is enough data and not too many arguments provided to the
6448 // constructor to build something of the type of the constructor. Also returns
6449 // the type of the constructor.
6450 //
6451 // Returns true if there was an error in construction.
6452 //
constructorError(const TSourceLoc & loc,TIntermNode * node,TFunction & function,TOperator op,TType & type)6453 bool HlslParseContext::constructorError(const TSourceLoc& loc, TIntermNode* node, TFunction& function,
6454 TOperator op, TType& type)
6455 {
6456 type.shallowCopy(function.getType());
6457
6458 bool constructingMatrix = false;
6459 switch (op) {
6460 case EOpConstructTextureSampler:
6461 error(loc, "unhandled texture constructor", "constructor", "");
6462 return true;
6463 case EOpConstructMat2x2:
6464 case EOpConstructMat2x3:
6465 case EOpConstructMat2x4:
6466 case EOpConstructMat3x2:
6467 case EOpConstructMat3x3:
6468 case EOpConstructMat3x4:
6469 case EOpConstructMat4x2:
6470 case EOpConstructMat4x3:
6471 case EOpConstructMat4x4:
6472 case EOpConstructDMat2x2:
6473 case EOpConstructDMat2x3:
6474 case EOpConstructDMat2x4:
6475 case EOpConstructDMat3x2:
6476 case EOpConstructDMat3x3:
6477 case EOpConstructDMat3x4:
6478 case EOpConstructDMat4x2:
6479 case EOpConstructDMat4x3:
6480 case EOpConstructDMat4x4:
6481 case EOpConstructIMat2x2:
6482 case EOpConstructIMat2x3:
6483 case EOpConstructIMat2x4:
6484 case EOpConstructIMat3x2:
6485 case EOpConstructIMat3x3:
6486 case EOpConstructIMat3x4:
6487 case EOpConstructIMat4x2:
6488 case EOpConstructIMat4x3:
6489 case EOpConstructIMat4x4:
6490 case EOpConstructUMat2x2:
6491 case EOpConstructUMat2x3:
6492 case EOpConstructUMat2x4:
6493 case EOpConstructUMat3x2:
6494 case EOpConstructUMat3x3:
6495 case EOpConstructUMat3x4:
6496 case EOpConstructUMat4x2:
6497 case EOpConstructUMat4x3:
6498 case EOpConstructUMat4x4:
6499 case EOpConstructBMat2x2:
6500 case EOpConstructBMat2x3:
6501 case EOpConstructBMat2x4:
6502 case EOpConstructBMat3x2:
6503 case EOpConstructBMat3x3:
6504 case EOpConstructBMat3x4:
6505 case EOpConstructBMat4x2:
6506 case EOpConstructBMat4x3:
6507 case EOpConstructBMat4x4:
6508 constructingMatrix = true;
6509 break;
6510 default:
6511 break;
6512 }
6513
6514 //
6515 // Walk the arguments for first-pass checks and collection of information.
6516 //
6517
6518 int size = 0;
6519 bool constType = true;
6520 bool full = false;
6521 bool overFull = false;
6522 bool matrixInMatrix = false;
6523 bool arrayArg = false;
6524 for (int arg = 0; arg < function.getParamCount(); ++arg) {
6525 if (function[arg].type->isArray()) {
6526 if (function[arg].type->isUnsizedArray()) {
6527 // Can't construct from an unsized array.
6528 error(loc, "array argument must be sized", "constructor", "");
6529 return true;
6530 }
6531 arrayArg = true;
6532 }
6533 if (constructingMatrix && function[arg].type->isMatrix())
6534 matrixInMatrix = true;
6535
6536 // 'full' will go to true when enough args have been seen. If we loop
6537 // again, there is an extra argument.
6538 if (full) {
6539 // For vectors and matrices, it's okay to have too many components
6540 // available, but not okay to have unused arguments.
6541 overFull = true;
6542 }
6543
6544 size += function[arg].type->computeNumComponents();
6545 if (op != EOpConstructStruct && ! type.isArray() && size >= type.computeNumComponents())
6546 full = true;
6547
6548 if (function[arg].type->getQualifier().storage != EvqConst)
6549 constType = false;
6550 }
6551
6552 if (constType)
6553 type.getQualifier().storage = EvqConst;
6554
6555 if (type.isArray()) {
6556 if (function.getParamCount() == 0) {
6557 error(loc, "array constructor must have at least one argument", "constructor", "");
6558 return true;
6559 }
6560
6561 if (type.isUnsizedArray()) {
6562 // auto adapt the constructor type to the number of arguments
6563 type.changeOuterArraySize(function.getParamCount());
6564 } else if (type.getOuterArraySize() != function.getParamCount() && type.computeNumComponents() > size) {
6565 error(loc, "array constructor needs one argument per array element", "constructor", "");
6566 return true;
6567 }
6568
6569 if (type.isArrayOfArrays()) {
6570 // Types have to match, but we're still making the type.
6571 // Finish making the type, and the comparison is done later
6572 // when checking for conversion.
6573 TArraySizes& arraySizes = *type.getArraySizes();
6574
6575 // At least the dimensionalities have to match.
6576 if (! function[0].type->isArray() ||
6577 arraySizes.getNumDims() != function[0].type->getArraySizes()->getNumDims() + 1) {
6578 error(loc, "array constructor argument not correct type to construct array element", "constructor", "");
6579 return true;
6580 }
6581
6582 if (arraySizes.isInnerUnsized()) {
6583 // "Arrays of arrays ..., and the size for any dimension is optional"
6584 // That means we need to adopt (from the first argument) the other array sizes into the type.
6585 for (int d = 1; d < arraySizes.getNumDims(); ++d) {
6586 if (arraySizes.getDimSize(d) == UnsizedArraySize) {
6587 arraySizes.setDimSize(d, function[0].type->getArraySizes()->getDimSize(d - 1));
6588 }
6589 }
6590 }
6591 }
6592 }
6593
6594 // Some array -> array type casts are okay
6595 if (arrayArg && function.getParamCount() == 1 && op != EOpConstructStruct && type.isArray() &&
6596 !type.isArrayOfArrays() && !function[0].type->isArrayOfArrays() &&
6597 type.getVectorSize() >= 1 && function[0].type->getVectorSize() >= 1)
6598 return false;
6599
6600 if (arrayArg && op != EOpConstructStruct && ! type.isArrayOfArrays()) {
6601 error(loc, "constructing non-array constituent from array argument", "constructor", "");
6602 return true;
6603 }
6604
6605 if (matrixInMatrix && ! type.isArray()) {
6606 return false;
6607 }
6608
6609 if (overFull) {
6610 error(loc, "too many arguments", "constructor", "");
6611 return true;
6612 }
6613
6614 if (op == EOpConstructStruct && ! type.isArray()) {
6615 if (isScalarConstructor(node))
6616 return false;
6617
6618 // Self-type construction: e.g, we can construct a struct from a single identically typed object.
6619 if (function.getParamCount() == 1 && type == *function[0].type)
6620 return false;
6621
6622 if ((int)type.getStruct()->size() != function.getParamCount()) {
6623 error(loc, "Number of constructor parameters does not match the number of structure fields", "constructor", "");
6624 return true;
6625 }
6626 }
6627
6628 if ((op != EOpConstructStruct && size != 1 && size < type.computeNumComponents()) ||
6629 (op == EOpConstructStruct && size < type.computeNumComponents())) {
6630 error(loc, "not enough data provided for construction", "constructor", "");
6631 return true;
6632 }
6633
6634 return false;
6635 }
6636
6637 // See if 'node', in the context of constructing aggregates, is a scalar argument
6638 // to a constructor.
6639 //
isScalarConstructor(const TIntermNode * node)6640 bool HlslParseContext::isScalarConstructor(const TIntermNode* node)
6641 {
6642 // Obviously, it must be a scalar, but an aggregate node might not be fully
6643 // completed yet: holding a sequence of initializers under an aggregate
6644 // would not yet be typed, so don't check it's type. This corresponds to
6645 // the aggregate operator also not being set yet. (An aggregate operation
6646 // that legitimately yields a scalar will have a getOp() of that operator,
6647 // not EOpNull.)
6648
6649 return node->getAsTyped() != nullptr &&
6650 node->getAsTyped()->isScalar() &&
6651 (node->getAsAggregate() == nullptr || node->getAsAggregate()->getOp() != EOpNull);
6652 }
6653
6654 // Checks to see if a void variable has been declared and raise an error message for such a case
6655 //
6656 // returns true in case of an error
6657 //
voidErrorCheck(const TSourceLoc & loc,const TString & identifier,const TBasicType basicType)6658 bool HlslParseContext::voidErrorCheck(const TSourceLoc& loc, const TString& identifier, const TBasicType basicType)
6659 {
6660 if (basicType == EbtVoid) {
6661 error(loc, "illegal use of type 'void'", identifier.c_str(), "");
6662 return true;
6663 }
6664
6665 return false;
6666 }
6667
6668 //
6669 // Fix just a full qualifier (no variables or types yet, but qualifier is complete) at global level.
6670 //
globalQualifierFix(const TSourceLoc &,TQualifier & qualifier)6671 void HlslParseContext::globalQualifierFix(const TSourceLoc&, TQualifier& qualifier)
6672 {
6673 // move from parameter/unknown qualifiers to pipeline in/out qualifiers
6674 switch (qualifier.storage) {
6675 case EvqIn:
6676 qualifier.storage = EvqVaryingIn;
6677 break;
6678 case EvqOut:
6679 qualifier.storage = EvqVaryingOut;
6680 break;
6681 default:
6682 break;
6683 }
6684 }
6685
6686 //
6687 // Merge characteristics of the 'src' qualifier into the 'dst'.
6688 // If there is duplication, issue error messages, unless 'force'
6689 // is specified, which means to just override default settings.
6690 //
6691 // Also, when force is false, it will be assumed that 'src' follows
6692 // 'dst', for the purpose of error checking order for versions
6693 // that require specific orderings of qualifiers.
6694 //
mergeQualifiers(TQualifier & dst,const TQualifier & src)6695 void HlslParseContext::mergeQualifiers(TQualifier& dst, const TQualifier& src)
6696 {
6697 // Storage qualification
6698 if (dst.storage == EvqTemporary || dst.storage == EvqGlobal)
6699 dst.storage = src.storage;
6700 else if ((dst.storage == EvqIn && src.storage == EvqOut) ||
6701 (dst.storage == EvqOut && src.storage == EvqIn))
6702 dst.storage = EvqInOut;
6703 else if ((dst.storage == EvqIn && src.storage == EvqConst) ||
6704 (dst.storage == EvqConst && src.storage == EvqIn))
6705 dst.storage = EvqConstReadOnly;
6706
6707 // Layout qualifiers
6708 mergeObjectLayoutQualifiers(dst, src, false);
6709
6710 // individual qualifiers
6711 bool repeated = false;
6712 #define MERGE_SINGLETON(field) repeated |= dst.field && src.field; dst.field |= src.field;
6713 MERGE_SINGLETON(invariant);
6714 MERGE_SINGLETON(noContraction);
6715 MERGE_SINGLETON(centroid);
6716 MERGE_SINGLETON(smooth);
6717 MERGE_SINGLETON(flat);
6718 MERGE_SINGLETON(nopersp);
6719 MERGE_SINGLETON(patch);
6720 MERGE_SINGLETON(sample);
6721 MERGE_SINGLETON(coherent);
6722 MERGE_SINGLETON(volatil);
6723 MERGE_SINGLETON(restrict);
6724 MERGE_SINGLETON(readonly);
6725 MERGE_SINGLETON(writeonly);
6726 MERGE_SINGLETON(specConstant);
6727 MERGE_SINGLETON(nonUniform);
6728 }
6729
6730 // used to flatten the sampler type space into a single dimension
6731 // correlates with the declaration of defaultSamplerPrecision[]
computeSamplerTypeIndex(TSampler & sampler)6732 int HlslParseContext::computeSamplerTypeIndex(TSampler& sampler)
6733 {
6734 int arrayIndex = sampler.arrayed ? 1 : 0;
6735 int shadowIndex = sampler.shadow ? 1 : 0;
6736 int externalIndex = sampler.external ? 1 : 0;
6737
6738 return EsdNumDims *
6739 (EbtNumTypes * (2 * (2 * arrayIndex + shadowIndex) + externalIndex) + sampler.type) + sampler.dim;
6740 }
6741
6742 //
6743 // Do size checking for an array type's size.
6744 //
arraySizeCheck(const TSourceLoc & loc,TIntermTyped * expr,TArraySize & sizePair)6745 void HlslParseContext::arraySizeCheck(const TSourceLoc& loc, TIntermTyped* expr, TArraySize& sizePair)
6746 {
6747 bool isConst = false;
6748 sizePair.size = 1;
6749 sizePair.node = nullptr;
6750
6751 TIntermConstantUnion* constant = expr->getAsConstantUnion();
6752 if (constant) {
6753 // handle true (non-specialization) constant
6754 sizePair.size = constant->getConstArray()[0].getIConst();
6755 isConst = true;
6756 } else {
6757 // see if it's a specialization constant instead
6758 if (expr->getQualifier().isSpecConstant()) {
6759 isConst = true;
6760 sizePair.node = expr;
6761 TIntermSymbol* symbol = expr->getAsSymbolNode();
6762 if (symbol && symbol->getConstArray().size() > 0)
6763 sizePair.size = symbol->getConstArray()[0].getIConst();
6764 }
6765 }
6766
6767 if (! isConst || (expr->getBasicType() != EbtInt && expr->getBasicType() != EbtUint)) {
6768 error(loc, "array size must be a constant integer expression", "", "");
6769 return;
6770 }
6771
6772 if (sizePair.size <= 0) {
6773 error(loc, "array size must be a positive integer", "", "");
6774 return;
6775 }
6776 }
6777
6778 //
6779 // Require array to be completely sized
6780 //
arraySizeRequiredCheck(const TSourceLoc & loc,const TArraySizes & arraySizes)6781 void HlslParseContext::arraySizeRequiredCheck(const TSourceLoc& loc, const TArraySizes& arraySizes)
6782 {
6783 if (arraySizes.hasUnsized())
6784 error(loc, "array size required", "", "");
6785 }
6786
structArrayCheck(const TSourceLoc &,const TType & type)6787 void HlslParseContext::structArrayCheck(const TSourceLoc& /*loc*/, const TType& type)
6788 {
6789 const TTypeList& structure = *type.getStruct();
6790 for (int m = 0; m < (int)structure.size(); ++m) {
6791 const TType& member = *structure[m].type;
6792 if (member.isArray())
6793 arraySizeRequiredCheck(structure[m].loc, *member.getArraySizes());
6794 }
6795 }
6796
6797 //
6798 // Do all the semantic checking for declaring or redeclaring an array, with and
6799 // without a size, and make the right changes to the symbol table.
6800 //
declareArray(const TSourceLoc & loc,const TString & identifier,const TType & type,TSymbol * & symbol,bool track)6801 void HlslParseContext::declareArray(const TSourceLoc& loc, const TString& identifier, const TType& type,
6802 TSymbol*& symbol, bool track)
6803 {
6804 if (symbol == nullptr) {
6805 bool currentScope;
6806 symbol = symbolTable.find(identifier, nullptr, ¤tScope);
6807
6808 if (symbol && builtInName(identifier) && ! symbolTable.atBuiltInLevel()) {
6809 // bad shader (errors already reported) trying to redeclare a built-in name as an array
6810 return;
6811 }
6812 if (symbol == nullptr || ! currentScope) {
6813 //
6814 // Successfully process a new definition.
6815 // (Redeclarations have to take place at the same scope; otherwise they are hiding declarations)
6816 //
6817 symbol = new TVariable(&identifier, type);
6818 symbolTable.insert(*symbol);
6819 if (track && symbolTable.atGlobalLevel())
6820 trackLinkage(*symbol);
6821
6822 return;
6823 }
6824 if (symbol->getAsAnonMember()) {
6825 error(loc, "cannot redeclare a user-block member array", identifier.c_str(), "");
6826 symbol = nullptr;
6827 return;
6828 }
6829 }
6830
6831 //
6832 // Process a redeclaration.
6833 //
6834
6835 if (symbol == nullptr) {
6836 error(loc, "array variable name expected", identifier.c_str(), "");
6837 return;
6838 }
6839
6840 // redeclareBuiltinVariable() should have already done the copyUp()
6841 TType& existingType = symbol->getWritableType();
6842
6843 if (existingType.isSizedArray()) {
6844 // be more lenient for input arrays to geometry shaders and tessellation control outputs,
6845 // where the redeclaration is the same size
6846 return;
6847 }
6848
6849 existingType.updateArraySizes(type);
6850 }
6851
6852 //
6853 // Enforce non-initializer type/qualifier rules.
6854 //
fixConstInit(const TSourceLoc & loc,const TString & identifier,TType & type,TIntermTyped * & initializer)6855 void HlslParseContext::fixConstInit(const TSourceLoc& loc, const TString& identifier, TType& type,
6856 TIntermTyped*& initializer)
6857 {
6858 //
6859 // Make the qualifier make sense, given that there is an initializer.
6860 //
6861 if (initializer == nullptr) {
6862 if (type.getQualifier().storage == EvqConst ||
6863 type.getQualifier().storage == EvqConstReadOnly) {
6864 initializer = intermediate.makeAggregate(loc);
6865 warn(loc, "variable with qualifier 'const' not initialized; zero initializing", identifier.c_str(), "");
6866 }
6867 }
6868 }
6869
6870 //
6871 // See if the identifier is a built-in symbol that can be redeclared, and if so,
6872 // copy the symbol table's read-only built-in variable to the current
6873 // global level, where it can be modified based on the passed in type.
6874 //
6875 // Returns nullptr if no redeclaration took place; meaning a normal declaration still
6876 // needs to occur for it, not necessarily an error.
6877 //
6878 // Returns a redeclared and type-modified variable if a redeclared occurred.
6879 //
redeclareBuiltinVariable(const TSourceLoc &,const TString & identifier,const TQualifier &,const TShaderQualifiers &)6880 TSymbol* HlslParseContext::redeclareBuiltinVariable(const TSourceLoc& /*loc*/, const TString& identifier,
6881 const TQualifier& /*qualifier*/,
6882 const TShaderQualifiers& /*publicType*/)
6883 {
6884 if (! builtInName(identifier) || symbolTable.atBuiltInLevel() || ! symbolTable.atGlobalLevel())
6885 return nullptr;
6886
6887 return nullptr;
6888 }
6889
6890 //
6891 // Generate index to the array element in a structure buffer (SSBO)
6892 //
indexStructBufferContent(const TSourceLoc & loc,TIntermTyped * buffer) const6893 TIntermTyped* HlslParseContext::indexStructBufferContent(const TSourceLoc& loc, TIntermTyped* buffer) const
6894 {
6895 // Bail out if not a struct buffer
6896 if (buffer == nullptr || ! isStructBufferType(buffer->getType()))
6897 return nullptr;
6898
6899 // Runtime sized array is always the last element.
6900 const TTypeList* bufferStruct = buffer->getType().getStruct();
6901 TIntermTyped* arrayPosition = intermediate.addConstantUnion(unsigned(bufferStruct->size()-1), loc);
6902
6903 TIntermTyped* argArray = intermediate.addIndex(EOpIndexDirectStruct, buffer, arrayPosition, loc);
6904 argArray->setType(*(*bufferStruct)[bufferStruct->size()-1].type);
6905
6906 return argArray;
6907 }
6908
6909 //
6910 // IFF type is a structuredbuffer/byteaddressbuffer type, return the content
6911 // (template) type. E.g, StructuredBuffer<MyType> -> MyType. Else return nullptr.
6912 //
getStructBufferContentType(const TType & type) const6913 TType* HlslParseContext::getStructBufferContentType(const TType& type) const
6914 {
6915 if (type.getBasicType() != EbtBlock || type.getQualifier().storage != EvqBuffer)
6916 return nullptr;
6917
6918 const int memberCount = (int)type.getStruct()->size();
6919 assert(memberCount > 0);
6920
6921 TType* contentType = (*type.getStruct())[memberCount-1].type;
6922
6923 return contentType->isUnsizedArray() ? contentType : nullptr;
6924 }
6925
6926 //
6927 // If an existing struct buffer has a sharable type, then share it.
6928 //
shareStructBufferType(TType & type)6929 void HlslParseContext::shareStructBufferType(TType& type)
6930 {
6931 // PackOffset must be equivalent to share types on a per-member basis.
6932 // Note: cannot use auto type due to recursion. Thus, this is a std::function.
6933 const std::function<bool(TType& lhs, TType& rhs)>
6934 compareQualifiers = [&](TType& lhs, TType& rhs) -> bool {
6935 if (lhs.getQualifier().layoutOffset != rhs.getQualifier().layoutOffset)
6936 return false;
6937
6938 if (lhs.isStruct() != rhs.isStruct())
6939 return false;
6940
6941 if (lhs.isStruct() && rhs.isStruct()) {
6942 if (lhs.getStruct()->size() != rhs.getStruct()->size())
6943 return false;
6944
6945 for (int i = 0; i < int(lhs.getStruct()->size()); ++i)
6946 if (!compareQualifiers(*(*lhs.getStruct())[i].type, *(*rhs.getStruct())[i].type))
6947 return false;
6948 }
6949
6950 return true;
6951 };
6952
6953 // We need to compare certain qualifiers in addition to the type.
6954 const auto typeEqual = [compareQualifiers](TType& lhs, TType& rhs) -> bool {
6955 if (lhs.getQualifier().readonly != rhs.getQualifier().readonly)
6956 return false;
6957
6958 // If both are structures, recursively look for packOffset equality
6959 // as well as type equality.
6960 return compareQualifiers(lhs, rhs) && lhs == rhs;
6961 };
6962
6963 // This is an exhaustive O(N) search, but real world shaders have
6964 // only a small number of these.
6965 for (int idx = 0; idx < int(structBufferTypes.size()); ++idx) {
6966 // If the deep structure matches, modulo qualifiers, use it
6967 if (typeEqual(*structBufferTypes[idx], type)) {
6968 type.shallowCopy(*structBufferTypes[idx]);
6969 return;
6970 }
6971 }
6972
6973 // Otherwise, remember it:
6974 TType* typeCopy = new TType;
6975 typeCopy->shallowCopy(type);
6976 structBufferTypes.push_back(typeCopy);
6977 }
6978
paramFix(TType & type)6979 void HlslParseContext::paramFix(TType& type)
6980 {
6981 switch (type.getQualifier().storage) {
6982 case EvqConst:
6983 type.getQualifier().storage = EvqConstReadOnly;
6984 break;
6985 case EvqGlobal:
6986 case EvqTemporary:
6987 type.getQualifier().storage = EvqIn;
6988 break;
6989 case EvqBuffer:
6990 {
6991 // SSBO parameter. These do not go through the declareBlock path since they are fn parameters.
6992 correctUniform(type.getQualifier());
6993 TQualifier bufferQualifier = globalBufferDefaults;
6994 mergeObjectLayoutQualifiers(bufferQualifier, type.getQualifier(), true);
6995 bufferQualifier.storage = type.getQualifier().storage;
6996 bufferQualifier.readonly = type.getQualifier().readonly;
6997 bufferQualifier.coherent = type.getQualifier().coherent;
6998 bufferQualifier.declaredBuiltIn = type.getQualifier().declaredBuiltIn;
6999 type.getQualifier() = bufferQualifier;
7000 break;
7001 }
7002 default:
7003 break;
7004 }
7005 }
7006
specializationCheck(const TSourceLoc & loc,const TType & type,const char * op)7007 void HlslParseContext::specializationCheck(const TSourceLoc& loc, const TType& type, const char* op)
7008 {
7009 if (type.containsSpecializationSize())
7010 error(loc, "can't use with types containing arrays sized with a specialization constant", op, "");
7011 }
7012
7013 //
7014 // Layout qualifier stuff.
7015 //
7016
7017 // Put the id's layout qualification into the public type, for qualifiers not having a number set.
7018 // This is before we know any type information for error checking.
setLayoutQualifier(const TSourceLoc & loc,TQualifier & qualifier,TString & id)7019 void HlslParseContext::setLayoutQualifier(const TSourceLoc& loc, TQualifier& qualifier, TString& id)
7020 {
7021 std::transform(id.begin(), id.end(), id.begin(), ::tolower);
7022
7023 if (id == TQualifier::getLayoutMatrixString(ElmColumnMajor)) {
7024 qualifier.layoutMatrix = ElmRowMajor;
7025 return;
7026 }
7027 if (id == TQualifier::getLayoutMatrixString(ElmRowMajor)) {
7028 qualifier.layoutMatrix = ElmColumnMajor;
7029 return;
7030 }
7031 if (id == "push_constant") {
7032 requireVulkan(loc, "push_constant");
7033 qualifier.layoutPushConstant = true;
7034 return;
7035 }
7036 if (language == EShLangGeometry || language == EShLangTessEvaluation) {
7037 if (id == TQualifier::getGeometryString(ElgTriangles)) {
7038 // publicType.shaderQualifiers.geometry = ElgTriangles;
7039 warn(loc, "ignored", id.c_str(), "");
7040 return;
7041 }
7042 if (language == EShLangGeometry) {
7043 if (id == TQualifier::getGeometryString(ElgPoints)) {
7044 // publicType.shaderQualifiers.geometry = ElgPoints;
7045 warn(loc, "ignored", id.c_str(), "");
7046 return;
7047 }
7048 if (id == TQualifier::getGeometryString(ElgLineStrip)) {
7049 // publicType.shaderQualifiers.geometry = ElgLineStrip;
7050 warn(loc, "ignored", id.c_str(), "");
7051 return;
7052 }
7053 if (id == TQualifier::getGeometryString(ElgLines)) {
7054 // publicType.shaderQualifiers.geometry = ElgLines;
7055 warn(loc, "ignored", id.c_str(), "");
7056 return;
7057 }
7058 if (id == TQualifier::getGeometryString(ElgLinesAdjacency)) {
7059 // publicType.shaderQualifiers.geometry = ElgLinesAdjacency;
7060 warn(loc, "ignored", id.c_str(), "");
7061 return;
7062 }
7063 if (id == TQualifier::getGeometryString(ElgTrianglesAdjacency)) {
7064 // publicType.shaderQualifiers.geometry = ElgTrianglesAdjacency;
7065 warn(loc, "ignored", id.c_str(), "");
7066 return;
7067 }
7068 if (id == TQualifier::getGeometryString(ElgTriangleStrip)) {
7069 // publicType.shaderQualifiers.geometry = ElgTriangleStrip;
7070 warn(loc, "ignored", id.c_str(), "");
7071 return;
7072 }
7073 } else {
7074 assert(language == EShLangTessEvaluation);
7075
7076 // input primitive
7077 if (id == TQualifier::getGeometryString(ElgTriangles)) {
7078 // publicType.shaderQualifiers.geometry = ElgTriangles;
7079 warn(loc, "ignored", id.c_str(), "");
7080 return;
7081 }
7082 if (id == TQualifier::getGeometryString(ElgQuads)) {
7083 // publicType.shaderQualifiers.geometry = ElgQuads;
7084 warn(loc, "ignored", id.c_str(), "");
7085 return;
7086 }
7087 if (id == TQualifier::getGeometryString(ElgIsolines)) {
7088 // publicType.shaderQualifiers.geometry = ElgIsolines;
7089 warn(loc, "ignored", id.c_str(), "");
7090 return;
7091 }
7092
7093 // vertex spacing
7094 if (id == TQualifier::getVertexSpacingString(EvsEqual)) {
7095 // publicType.shaderQualifiers.spacing = EvsEqual;
7096 warn(loc, "ignored", id.c_str(), "");
7097 return;
7098 }
7099 if (id == TQualifier::getVertexSpacingString(EvsFractionalEven)) {
7100 // publicType.shaderQualifiers.spacing = EvsFractionalEven;
7101 warn(loc, "ignored", id.c_str(), "");
7102 return;
7103 }
7104 if (id == TQualifier::getVertexSpacingString(EvsFractionalOdd)) {
7105 // publicType.shaderQualifiers.spacing = EvsFractionalOdd;
7106 warn(loc, "ignored", id.c_str(), "");
7107 return;
7108 }
7109
7110 // triangle order
7111 if (id == TQualifier::getVertexOrderString(EvoCw)) {
7112 // publicType.shaderQualifiers.order = EvoCw;
7113 warn(loc, "ignored", id.c_str(), "");
7114 return;
7115 }
7116 if (id == TQualifier::getVertexOrderString(EvoCcw)) {
7117 // publicType.shaderQualifiers.order = EvoCcw;
7118 warn(loc, "ignored", id.c_str(), "");
7119 return;
7120 }
7121
7122 // point mode
7123 if (id == "point_mode") {
7124 // publicType.shaderQualifiers.pointMode = true;
7125 warn(loc, "ignored", id.c_str(), "");
7126 return;
7127 }
7128 }
7129 }
7130 if (language == EShLangFragment) {
7131 if (id == "origin_upper_left") {
7132 // publicType.shaderQualifiers.originUpperLeft = true;
7133 warn(loc, "ignored", id.c_str(), "");
7134 return;
7135 }
7136 if (id == "pixel_center_integer") {
7137 // publicType.shaderQualifiers.pixelCenterInteger = true;
7138 warn(loc, "ignored", id.c_str(), "");
7139 return;
7140 }
7141 if (id == "early_fragment_tests") {
7142 // publicType.shaderQualifiers.earlyFragmentTests = true;
7143 warn(loc, "ignored", id.c_str(), "");
7144 return;
7145 }
7146 for (TLayoutDepth depth = (TLayoutDepth)(EldNone + 1); depth < EldCount; depth = (TLayoutDepth)(depth + 1)) {
7147 if (id == TQualifier::getLayoutDepthString(depth)) {
7148 // publicType.shaderQualifiers.layoutDepth = depth;
7149 warn(loc, "ignored", id.c_str(), "");
7150 return;
7151 }
7152 }
7153 if (id.compare(0, 13, "blend_support") == 0) {
7154 bool found = false;
7155 for (TBlendEquationShift be = (TBlendEquationShift)0; be < EBlendCount; be = (TBlendEquationShift)(be + 1)) {
7156 if (id == TQualifier::getBlendEquationString(be)) {
7157 requireExtensions(loc, 1, &E_GL_KHR_blend_equation_advanced, "blend equation");
7158 intermediate.addBlendEquation(be);
7159 // publicType.shaderQualifiers.blendEquation = true;
7160 warn(loc, "ignored", id.c_str(), "");
7161 found = true;
7162 break;
7163 }
7164 }
7165 if (! found)
7166 error(loc, "unknown blend equation", "blend_support", "");
7167 return;
7168 }
7169 }
7170 error(loc, "unrecognized layout identifier, or qualifier requires assignment (e.g., binding = 4)", id.c_str(), "");
7171 }
7172
7173 // Put the id's layout qualifier value into the public type, for qualifiers having a number set.
7174 // This is before we know any type information for error checking.
setLayoutQualifier(const TSourceLoc & loc,TQualifier & qualifier,TString & id,const TIntermTyped * node)7175 void HlslParseContext::setLayoutQualifier(const TSourceLoc& loc, TQualifier& qualifier, TString& id,
7176 const TIntermTyped* node)
7177 {
7178 const char* feature = "layout-id value";
7179 // const char* nonLiteralFeature = "non-literal layout-id value";
7180
7181 integerCheck(node, feature);
7182 const TIntermConstantUnion* constUnion = node->getAsConstantUnion();
7183 int value = 0;
7184 if (constUnion) {
7185 value = constUnion->getConstArray()[0].getIConst();
7186 }
7187
7188 std::transform(id.begin(), id.end(), id.begin(), ::tolower);
7189
7190 if (id == "offset") {
7191 qualifier.layoutOffset = value;
7192 return;
7193 } else if (id == "align") {
7194 // "The specified alignment must be a power of 2, or a compile-time error results."
7195 if (! IsPow2(value))
7196 error(loc, "must be a power of 2", "align", "");
7197 else
7198 qualifier.layoutAlign = value;
7199 return;
7200 } else if (id == "location") {
7201 if ((unsigned int)value >= TQualifier::layoutLocationEnd)
7202 error(loc, "location is too large", id.c_str(), "");
7203 else
7204 qualifier.layoutLocation = value;
7205 return;
7206 } else if (id == "set") {
7207 if ((unsigned int)value >= TQualifier::layoutSetEnd)
7208 error(loc, "set is too large", id.c_str(), "");
7209 else
7210 qualifier.layoutSet = value;
7211 return;
7212 } else if (id == "binding") {
7213 if ((unsigned int)value >= TQualifier::layoutBindingEnd)
7214 error(loc, "binding is too large", id.c_str(), "");
7215 else
7216 qualifier.layoutBinding = value;
7217 return;
7218 } else if (id == "component") {
7219 if ((unsigned)value >= TQualifier::layoutComponentEnd)
7220 error(loc, "component is too large", id.c_str(), "");
7221 else
7222 qualifier.layoutComponent = value;
7223 return;
7224 } else if (id.compare(0, 4, "xfb_") == 0) {
7225 // "Any shader making any static use (after preprocessing) of any of these
7226 // *xfb_* qualifiers will cause the shader to be in a transform feedback
7227 // capturing mode and hence responsible for describing the transform feedback
7228 // setup."
7229 intermediate.setXfbMode();
7230 if (id == "xfb_buffer") {
7231 // "It is a compile-time error to specify an *xfb_buffer* that is greater than
7232 // the implementation-dependent constant gl_MaxTransformFeedbackBuffers."
7233 if (value >= resources.maxTransformFeedbackBuffers)
7234 error(loc, "buffer is too large:", id.c_str(), "gl_MaxTransformFeedbackBuffers is %d",
7235 resources.maxTransformFeedbackBuffers);
7236 if (value >= (int)TQualifier::layoutXfbBufferEnd)
7237 error(loc, "buffer is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbBufferEnd - 1);
7238 else
7239 qualifier.layoutXfbBuffer = value;
7240 return;
7241 } else if (id == "xfb_offset") {
7242 if (value >= (int)TQualifier::layoutXfbOffsetEnd)
7243 error(loc, "offset is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbOffsetEnd - 1);
7244 else
7245 qualifier.layoutXfbOffset = value;
7246 return;
7247 } else if (id == "xfb_stride") {
7248 // "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the
7249 // implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents."
7250 if (value > 4 * resources.maxTransformFeedbackInterleavedComponents)
7251 error(loc, "1/4 stride is too large:", id.c_str(), "gl_MaxTransformFeedbackInterleavedComponents is %d",
7252 resources.maxTransformFeedbackInterleavedComponents);
7253 else if (value >= (int)TQualifier::layoutXfbStrideEnd)
7254 error(loc, "stride is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbStrideEnd - 1);
7255 if (value < (int)TQualifier::layoutXfbStrideEnd)
7256 qualifier.layoutXfbStride = value;
7257 return;
7258 }
7259 }
7260
7261 if (id == "input_attachment_index") {
7262 requireVulkan(loc, "input_attachment_index");
7263 if (value >= (int)TQualifier::layoutAttachmentEnd)
7264 error(loc, "attachment index is too large", id.c_str(), "");
7265 else
7266 qualifier.layoutAttachment = value;
7267 return;
7268 }
7269 if (id == "constant_id") {
7270 setSpecConstantId(loc, qualifier, value);
7271 return;
7272 }
7273
7274 switch (language) {
7275 case EShLangVertex:
7276 break;
7277
7278 case EShLangTessControl:
7279 if (id == "vertices") {
7280 if (value == 0)
7281 error(loc, "must be greater than 0", "vertices", "");
7282 else
7283 // publicType.shaderQualifiers.vertices = value;
7284 warn(loc, "ignored", id.c_str(), "");
7285 return;
7286 }
7287 break;
7288
7289 case EShLangTessEvaluation:
7290 break;
7291
7292 case EShLangGeometry:
7293 if (id == "invocations") {
7294 if (value == 0)
7295 error(loc, "must be at least 1", "invocations", "");
7296 else
7297 // publicType.shaderQualifiers.invocations = value;
7298 warn(loc, "ignored", id.c_str(), "");
7299 return;
7300 }
7301 if (id == "max_vertices") {
7302 // publicType.shaderQualifiers.vertices = value;
7303 warn(loc, "ignored", id.c_str(), "");
7304 if (value > resources.maxGeometryOutputVertices)
7305 error(loc, "too large, must be less than gl_MaxGeometryOutputVertices", "max_vertices", "");
7306 return;
7307 }
7308 if (id == "stream") {
7309 qualifier.layoutStream = value;
7310 return;
7311 }
7312 break;
7313
7314 case EShLangFragment:
7315 if (id == "index") {
7316 qualifier.layoutIndex = value;
7317 return;
7318 }
7319 break;
7320
7321 case EShLangCompute:
7322 if (id.compare(0, 11, "local_size_") == 0) {
7323 if (id == "local_size_x") {
7324 // publicType.shaderQualifiers.localSize[0] = value;
7325 warn(loc, "ignored", id.c_str(), "");
7326 return;
7327 }
7328 if (id == "local_size_y") {
7329 // publicType.shaderQualifiers.localSize[1] = value;
7330 warn(loc, "ignored", id.c_str(), "");
7331 return;
7332 }
7333 if (id == "local_size_z") {
7334 // publicType.shaderQualifiers.localSize[2] = value;
7335 warn(loc, "ignored", id.c_str(), "");
7336 return;
7337 }
7338 if (spvVersion.spv != 0) {
7339 if (id == "local_size_x_id") {
7340 // publicType.shaderQualifiers.localSizeSpecId[0] = value;
7341 warn(loc, "ignored", id.c_str(), "");
7342 return;
7343 }
7344 if (id == "local_size_y_id") {
7345 // publicType.shaderQualifiers.localSizeSpecId[1] = value;
7346 warn(loc, "ignored", id.c_str(), "");
7347 return;
7348 }
7349 if (id == "local_size_z_id") {
7350 // publicType.shaderQualifiers.localSizeSpecId[2] = value;
7351 warn(loc, "ignored", id.c_str(), "");
7352 return;
7353 }
7354 }
7355 }
7356 break;
7357
7358 default:
7359 break;
7360 }
7361
7362 error(loc, "there is no such layout identifier for this stage taking an assigned value", id.c_str(), "");
7363 }
7364
setSpecConstantId(const TSourceLoc & loc,TQualifier & qualifier,int value)7365 void HlslParseContext::setSpecConstantId(const TSourceLoc& loc, TQualifier& qualifier, int value)
7366 {
7367 if (value >= (int)TQualifier::layoutSpecConstantIdEnd) {
7368 error(loc, "specialization-constant id is too large", "constant_id", "");
7369 } else {
7370 qualifier.layoutSpecConstantId = value;
7371 qualifier.specConstant = true;
7372 if (! intermediate.addUsedConstantId(value))
7373 error(loc, "specialization-constant id already used", "constant_id", "");
7374 }
7375 return;
7376 }
7377
7378 // Merge any layout qualifier information from src into dst, leaving everything else in dst alone
7379 //
7380 // "More than one layout qualifier may appear in a single declaration.
7381 // Additionally, the same layout-qualifier-name can occur multiple times
7382 // within a layout qualifier or across multiple layout qualifiers in the
7383 // same declaration. When the same layout-qualifier-name occurs
7384 // multiple times, in a single declaration, the last occurrence overrides
7385 // the former occurrence(s). Further, if such a layout-qualifier-name
7386 // will effect subsequent declarations or other observable behavior, it
7387 // is only the last occurrence that will have any effect, behaving as if
7388 // the earlier occurrence(s) within the declaration are not present.
7389 // This is also true for overriding layout-qualifier-names, where one
7390 // overrides the other (e.g., row_major vs. column_major); only the last
7391 // occurrence has any effect."
7392 //
mergeObjectLayoutQualifiers(TQualifier & dst,const TQualifier & src,bool inheritOnly)7393 void HlslParseContext::mergeObjectLayoutQualifiers(TQualifier& dst, const TQualifier& src, bool inheritOnly)
7394 {
7395 if (src.hasMatrix())
7396 dst.layoutMatrix = src.layoutMatrix;
7397 if (src.hasPacking())
7398 dst.layoutPacking = src.layoutPacking;
7399
7400 if (src.hasStream())
7401 dst.layoutStream = src.layoutStream;
7402
7403 if (src.hasFormat())
7404 dst.layoutFormat = src.layoutFormat;
7405
7406 if (src.hasXfbBuffer())
7407 dst.layoutXfbBuffer = src.layoutXfbBuffer;
7408
7409 if (src.hasAlign())
7410 dst.layoutAlign = src.layoutAlign;
7411
7412 if (! inheritOnly) {
7413 if (src.hasLocation())
7414 dst.layoutLocation = src.layoutLocation;
7415 if (src.hasComponent())
7416 dst.layoutComponent = src.layoutComponent;
7417 if (src.hasIndex())
7418 dst.layoutIndex = src.layoutIndex;
7419
7420 if (src.hasOffset())
7421 dst.layoutOffset = src.layoutOffset;
7422
7423 if (src.hasSet())
7424 dst.layoutSet = src.layoutSet;
7425 if (src.layoutBinding != TQualifier::layoutBindingEnd)
7426 dst.layoutBinding = src.layoutBinding;
7427
7428 if (src.hasXfbStride())
7429 dst.layoutXfbStride = src.layoutXfbStride;
7430 if (src.hasXfbOffset())
7431 dst.layoutXfbOffset = src.layoutXfbOffset;
7432 if (src.hasAttachment())
7433 dst.layoutAttachment = src.layoutAttachment;
7434 if (src.hasSpecConstantId())
7435 dst.layoutSpecConstantId = src.layoutSpecConstantId;
7436
7437 if (src.layoutPushConstant)
7438 dst.layoutPushConstant = true;
7439 }
7440 }
7441
7442
7443 //
7444 // Look up a function name in the symbol table, and make sure it is a function.
7445 //
7446 // First, look for an exact match. If there is none, use the generic selector
7447 // TParseContextBase::selectFunction() to find one, parameterized by the
7448 // convertible() and better() predicates defined below.
7449 //
7450 // Return the function symbol if found, otherwise nullptr.
7451 //
findFunction(const TSourceLoc & loc,TFunction & call,bool & builtIn,int & thisDepth,TIntermTyped * & args)7452 const TFunction* HlslParseContext::findFunction(const TSourceLoc& loc, TFunction& call, bool& builtIn, int& thisDepth,
7453 TIntermTyped*& args)
7454 {
7455 if (symbolTable.isFunctionNameVariable(call.getName())) {
7456 error(loc, "can't use function syntax on variable", call.getName().c_str(), "");
7457 return nullptr;
7458 }
7459
7460 // first, look for an exact match
7461 bool dummyScope;
7462 TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn, &dummyScope, &thisDepth);
7463 if (symbol)
7464 return symbol->getAsFunction();
7465
7466 // no exact match, use the generic selector, parameterized by the GLSL rules
7467
7468 // create list of candidates to send
7469 TVector<const TFunction*> candidateList;
7470 symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
7471
7472 // These built-in ops can accept any type, so we bypass the argument selection
7473 if (candidateList.size() == 1 && builtIn &&
7474 (candidateList[0]->getBuiltInOp() == EOpMethodAppend ||
7475 candidateList[0]->getBuiltInOp() == EOpMethodRestartStrip ||
7476 candidateList[0]->getBuiltInOp() == EOpMethodIncrementCounter ||
7477 candidateList[0]->getBuiltInOp() == EOpMethodDecrementCounter ||
7478 candidateList[0]->getBuiltInOp() == EOpMethodAppend ||
7479 candidateList[0]->getBuiltInOp() == EOpMethodConsume)) {
7480 return candidateList[0];
7481 }
7482
7483 bool allowOnlyUpConversions = true;
7484
7485 // can 'from' convert to 'to'?
7486 const auto convertible = [&](const TType& from, const TType& to, TOperator op, int arg) -> bool {
7487 if (from == to)
7488 return true;
7489
7490 // no aggregate conversions
7491 if (from.isArray() || to.isArray() ||
7492 from.isStruct() || to.isStruct())
7493 return false;
7494
7495 switch (op) {
7496 case EOpInterlockedAdd:
7497 case EOpInterlockedAnd:
7498 case EOpInterlockedCompareExchange:
7499 case EOpInterlockedCompareStore:
7500 case EOpInterlockedExchange:
7501 case EOpInterlockedMax:
7502 case EOpInterlockedMin:
7503 case EOpInterlockedOr:
7504 case EOpInterlockedXor:
7505 // We do not promote the texture or image type for these ocodes. Normally that would not
7506 // be an issue because it's a buffer, but we haven't decomposed the opcode yet, and at this
7507 // stage it's merely e.g, a basic integer type.
7508 //
7509 // Instead, we want to promote other arguments, but stay within the same family. In other
7510 // words, InterlockedAdd(RWBuffer<int>, ...) will always use the int flavor, never the uint flavor,
7511 // but it is allowed to promote its other arguments.
7512 if (arg == 0)
7513 return false;
7514 break;
7515 case EOpMethodSample:
7516 case EOpMethodSampleBias:
7517 case EOpMethodSampleCmp:
7518 case EOpMethodSampleCmpLevelZero:
7519 case EOpMethodSampleGrad:
7520 case EOpMethodSampleLevel:
7521 case EOpMethodLoad:
7522 case EOpMethodGetDimensions:
7523 case EOpMethodGetSamplePosition:
7524 case EOpMethodGather:
7525 case EOpMethodCalculateLevelOfDetail:
7526 case EOpMethodCalculateLevelOfDetailUnclamped:
7527 case EOpMethodGatherRed:
7528 case EOpMethodGatherGreen:
7529 case EOpMethodGatherBlue:
7530 case EOpMethodGatherAlpha:
7531 case EOpMethodGatherCmp:
7532 case EOpMethodGatherCmpRed:
7533 case EOpMethodGatherCmpGreen:
7534 case EOpMethodGatherCmpBlue:
7535 case EOpMethodGatherCmpAlpha:
7536 case EOpMethodAppend:
7537 case EOpMethodRestartStrip:
7538 // those are method calls, the object type can not be changed
7539 // they are equal if the dim and type match (is dim sufficient?)
7540 if (arg == 0)
7541 return from.getSampler().type == to.getSampler().type &&
7542 from.getSampler().arrayed == to.getSampler().arrayed &&
7543 from.getSampler().shadow == to.getSampler().shadow &&
7544 from.getSampler().ms == to.getSampler().ms &&
7545 from.getSampler().dim == to.getSampler().dim;
7546 break;
7547 default:
7548 break;
7549 }
7550
7551 // basic types have to be convertible
7552 if (allowOnlyUpConversions)
7553 if (! intermediate.canImplicitlyPromote(from.getBasicType(), to.getBasicType(), EOpFunctionCall))
7554 return false;
7555
7556 // shapes have to be convertible
7557 if ((from.isScalarOrVec1() && to.isScalarOrVec1()) ||
7558 (from.isScalarOrVec1() && to.isVector()) ||
7559 (from.isScalarOrVec1() && to.isMatrix()) ||
7560 (from.isVector() && to.isVector() && from.getVectorSize() >= to.getVectorSize()))
7561 return true;
7562
7563 // TODO: what are the matrix rules? they go here
7564
7565 return false;
7566 };
7567
7568 // Is 'to2' a better conversion than 'to1'?
7569 // Ties should not be considered as better.
7570 // Assumes 'convertible' already said true.
7571 const auto better = [](const TType& from, const TType& to1, const TType& to2) -> bool {
7572 // exact match is always better than mismatch
7573 if (from == to2)
7574 return from != to1;
7575 if (from == to1)
7576 return false;
7577
7578 // shape changes are always worse
7579 if (from.isScalar() || from.isVector()) {
7580 if (from.getVectorSize() == to2.getVectorSize() &&
7581 from.getVectorSize() != to1.getVectorSize())
7582 return true;
7583 if (from.getVectorSize() == to1.getVectorSize() &&
7584 from.getVectorSize() != to2.getVectorSize())
7585 return false;
7586 }
7587
7588 // Handle sampler betterness: An exact sampler match beats a non-exact match.
7589 // (If we just looked at basic type, all EbtSamplers would look the same).
7590 // If any type is not a sampler, just use the linearize function below.
7591 if (from.getBasicType() == EbtSampler && to1.getBasicType() == EbtSampler && to2.getBasicType() == EbtSampler) {
7592 // We can ignore the vector size in the comparison.
7593 TSampler to1Sampler = to1.getSampler();
7594 TSampler to2Sampler = to2.getSampler();
7595
7596 to1Sampler.vectorSize = to2Sampler.vectorSize = from.getSampler().vectorSize;
7597
7598 if (from.getSampler() == to2Sampler)
7599 return from.getSampler() != to1Sampler;
7600 if (from.getSampler() == to1Sampler)
7601 return false;
7602 }
7603
7604 // Might or might not be changing shape, which means basic type might
7605 // or might not match, so within that, the question is how big a
7606 // basic-type conversion is being done.
7607 //
7608 // Use a hierarchy of domains, translated to order of magnitude
7609 // in a linearized view:
7610 // - floating-point vs. integer
7611 // - 32 vs. 64 bit (or width in general)
7612 // - bool vs. non bool
7613 // - signed vs. not signed
7614 const auto linearize = [](const TBasicType& basicType) -> int {
7615 switch (basicType) {
7616 case EbtBool: return 1;
7617 case EbtInt: return 10;
7618 case EbtUint: return 11;
7619 case EbtInt64: return 20;
7620 case EbtUint64: return 21;
7621 case EbtFloat: return 100;
7622 case EbtDouble: return 110;
7623 default: return 0;
7624 }
7625 };
7626
7627 return abs(linearize(to2.getBasicType()) - linearize(from.getBasicType())) <
7628 abs(linearize(to1.getBasicType()) - linearize(from.getBasicType()));
7629 };
7630
7631 // for ambiguity reporting
7632 bool tie = false;
7633
7634 // send to the generic selector
7635 const TFunction* bestMatch = nullptr;
7636
7637 // printf has var args and is in the symbol table as "printf()",
7638 // mangled to "printf("
7639 if (call.getName() == "printf") {
7640 TSymbol* symbol = symbolTable.find("printf(", &builtIn);
7641 if (symbol)
7642 return symbol->getAsFunction();
7643 }
7644
7645 bestMatch = selectFunction(candidateList, call, convertible, better, tie);
7646
7647 if (bestMatch == nullptr) {
7648 // If there is nothing selected by allowing only up-conversions (to a larger linearize() value),
7649 // we instead try down-conversions, which are valid in HLSL, but not preferred if there are any
7650 // upconversions possible.
7651 allowOnlyUpConversions = false;
7652 bestMatch = selectFunction(candidateList, call, convertible, better, tie);
7653 }
7654
7655 if (bestMatch == nullptr) {
7656 error(loc, "no matching overloaded function found", call.getName().c_str(), "");
7657 return nullptr;
7658 }
7659
7660 // For built-ins, we can convert across the arguments. This will happen in several steps:
7661 // Step 1: If there's an exact match, use it.
7662 // Step 2a: Otherwise, get the operator from the best match and promote arguments:
7663 // Step 2b: reconstruct the TFunction based on the new arg types
7664 // Step 3: Re-select after type promotion is applied, to find proper candidate.
7665 if (builtIn) {
7666 // Step 1: If there's an exact match, use it.
7667 if (call.getMangledName() == bestMatch->getMangledName())
7668 return bestMatch;
7669
7670 // Step 2a: Otherwise, get the operator from the best match and promote arguments as if we
7671 // are that kind of operator.
7672 if (args != nullptr) {
7673 // The arg list can be a unary node, or an aggregate. We have to handle both.
7674 // We will use the normal promote() facilities, which require an interm node.
7675 TIntermOperator* promote = nullptr;
7676
7677 if (call.getParamCount() == 1) {
7678 promote = new TIntermUnary(bestMatch->getBuiltInOp());
7679 promote->getAsUnaryNode()->setOperand(args->getAsTyped());
7680 } else {
7681 promote = new TIntermAggregate(bestMatch->getBuiltInOp());
7682 promote->getAsAggregate()->getSequence().swap(args->getAsAggregate()->getSequence());
7683 }
7684
7685 if (! intermediate.promote(promote))
7686 return nullptr;
7687
7688 // Obtain the promoted arg list.
7689 if (call.getParamCount() == 1) {
7690 args = promote->getAsUnaryNode()->getOperand();
7691 } else {
7692 promote->getAsAggregate()->getSequence().swap(args->getAsAggregate()->getSequence());
7693 }
7694 }
7695
7696 // Step 2b: reconstruct the TFunction based on the new arg types
7697 TFunction convertedCall(&call.getName(), call.getType(), call.getBuiltInOp());
7698
7699 if (args->getAsAggregate()) {
7700 // Handle aggregates: put all args into the new function call
7701 for (int arg = 0; arg < int(args->getAsAggregate()->getSequence().size()); ++arg) {
7702 // TODO: But for constness, we could avoid the new & shallowCopy, and use the pointer directly.
7703 TParameter param = { 0, new TType, nullptr };
7704 param.type->shallowCopy(args->getAsAggregate()->getSequence()[arg]->getAsTyped()->getType());
7705 convertedCall.addParameter(param);
7706 }
7707 } else if (args->getAsUnaryNode()) {
7708 // Handle unaries: put all args into the new function call
7709 TParameter param = { 0, new TType, nullptr };
7710 param.type->shallowCopy(args->getAsUnaryNode()->getOperand()->getAsTyped()->getType());
7711 convertedCall.addParameter(param);
7712 } else if (args->getAsTyped()) {
7713 // Handle bare e.g, floats, not in an aggregate.
7714 TParameter param = { 0, new TType, nullptr };
7715 param.type->shallowCopy(args->getAsTyped()->getType());
7716 convertedCall.addParameter(param);
7717 } else {
7718 assert(0); // unknown argument list.
7719 return nullptr;
7720 }
7721
7722 // Step 3: Re-select after type promotion, to find proper candidate
7723 // send to the generic selector
7724 bestMatch = selectFunction(candidateList, convertedCall, convertible, better, tie);
7725
7726 // At this point, there should be no tie.
7727 }
7728
7729 if (tie)
7730 error(loc, "ambiguous best function under implicit type conversion", call.getName().c_str(), "");
7731
7732 // Append default parameter values if needed
7733 if (!tie && bestMatch != nullptr) {
7734 for (int defParam = call.getParamCount(); defParam < bestMatch->getParamCount(); ++defParam) {
7735 handleFunctionArgument(&call, args, (*bestMatch)[defParam].defaultValue);
7736 }
7737 }
7738
7739 return bestMatch;
7740 }
7741
7742 //
7743 // Do everything necessary to handle a typedef declaration, for a single symbol.
7744 //
7745 // 'parseType' is the type part of the declaration (to the left)
7746 // 'arraySizes' is the arrayness tagged on the identifier (to the right)
7747 //
declareTypedef(const TSourceLoc & loc,const TString & identifier,const TType & parseType)7748 void HlslParseContext::declareTypedef(const TSourceLoc& loc, const TString& identifier, const TType& parseType)
7749 {
7750 TVariable* typeSymbol = new TVariable(&identifier, parseType, true);
7751 if (! symbolTable.insert(*typeSymbol))
7752 error(loc, "name already defined", "typedef", identifier.c_str());
7753 }
7754
7755 // Do everything necessary to handle a struct declaration, including
7756 // making IO aliases because HLSL allows mixed IO in a struct that specializes
7757 // based on the usage (input, output, uniform, none).
declareStruct(const TSourceLoc & loc,TString & structName,TType & type)7758 void HlslParseContext::declareStruct(const TSourceLoc& loc, TString& structName, TType& type)
7759 {
7760 // If it was named, which means the type can be reused later, add
7761 // it to the symbol table. (Unless it's a block, in which
7762 // case the name is not a type.)
7763 if (type.getBasicType() == EbtBlock || structName.size() == 0)
7764 return;
7765
7766 TVariable* userTypeDef = new TVariable(&structName, type, true);
7767 if (! symbolTable.insert(*userTypeDef)) {
7768 error(loc, "redefinition", structName.c_str(), "struct");
7769 return;
7770 }
7771
7772 // See if we need IO aliases for the structure typeList
7773
7774 const auto condAlloc = [](bool pred, TTypeList*& list) {
7775 if (pred && list == nullptr)
7776 list = new TTypeList;
7777 };
7778
7779 tIoKinds newLists = { nullptr, nullptr, nullptr }; // allocate for each kind found
7780 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
7781 condAlloc(hasUniform(member->type->getQualifier()), newLists.uniform);
7782 condAlloc( hasInput(member->type->getQualifier()), newLists.input);
7783 condAlloc( hasOutput(member->type->getQualifier()), newLists.output);
7784
7785 if (member->type->isStruct()) {
7786 auto it = ioTypeMap.find(member->type->getStruct());
7787 if (it != ioTypeMap.end()) {
7788 condAlloc(it->second.uniform != nullptr, newLists.uniform);
7789 condAlloc(it->second.input != nullptr, newLists.input);
7790 condAlloc(it->second.output != nullptr, newLists.output);
7791 }
7792 }
7793 }
7794 if (newLists.uniform == nullptr &&
7795 newLists.input == nullptr &&
7796 newLists.output == nullptr) {
7797 // Won't do any IO caching, clear up the type and get out now.
7798 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member)
7799 clearUniformInputOutput(member->type->getQualifier());
7800 return;
7801 }
7802
7803 // We have IO involved.
7804
7805 // Make a pure typeList for the symbol table, and cache side copies of IO versions.
7806 for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
7807 const auto inheritStruct = [&](TTypeList* s, TTypeLoc& ioMember) {
7808 if (s != nullptr) {
7809 ioMember.type = new TType;
7810 ioMember.type->shallowCopy(*member->type);
7811 ioMember.type->setStruct(s);
7812 }
7813 };
7814 const auto newMember = [&](TTypeLoc& m) {
7815 if (m.type == nullptr) {
7816 m.type = new TType;
7817 m.type->shallowCopy(*member->type);
7818 }
7819 };
7820
7821 TTypeLoc newUniformMember = { nullptr, member->loc };
7822 TTypeLoc newInputMember = { nullptr, member->loc };
7823 TTypeLoc newOutputMember = { nullptr, member->loc };
7824 if (member->type->isStruct()) {
7825 // swap in an IO child if there is one
7826 auto it = ioTypeMap.find(member->type->getStruct());
7827 if (it != ioTypeMap.end()) {
7828 inheritStruct(it->second.uniform, newUniformMember);
7829 inheritStruct(it->second.input, newInputMember);
7830 inheritStruct(it->second.output, newOutputMember);
7831 }
7832 }
7833 if (newLists.uniform) {
7834 newMember(newUniformMember);
7835
7836 // inherit default matrix layout (changeable via #pragma pack_matrix), if none given.
7837 if (member->type->isMatrix() && member->type->getQualifier().layoutMatrix == ElmNone)
7838 newUniformMember.type->getQualifier().layoutMatrix = globalUniformDefaults.layoutMatrix;
7839
7840 correctUniform(newUniformMember.type->getQualifier());
7841 newLists.uniform->push_back(newUniformMember);
7842 }
7843 if (newLists.input) {
7844 newMember(newInputMember);
7845 correctInput(newInputMember.type->getQualifier());
7846 newLists.input->push_back(newInputMember);
7847 }
7848 if (newLists.output) {
7849 newMember(newOutputMember);
7850 correctOutput(newOutputMember.type->getQualifier());
7851 newLists.output->push_back(newOutputMember);
7852 }
7853
7854 // make original pure
7855 clearUniformInputOutput(member->type->getQualifier());
7856 }
7857 ioTypeMap[type.getStruct()] = newLists;
7858 }
7859
7860 // Lookup a user-type by name.
7861 // If found, fill in the type and return the defining symbol.
7862 // If not found, return nullptr.
lookupUserType(const TString & typeName,TType & type)7863 TSymbol* HlslParseContext::lookupUserType(const TString& typeName, TType& type)
7864 {
7865 TSymbol* symbol = symbolTable.find(typeName);
7866 if (symbol && symbol->getAsVariable() && symbol->getAsVariable()->isUserType()) {
7867 type.shallowCopy(symbol->getType());
7868 return symbol;
7869 } else
7870 return nullptr;
7871 }
7872
7873 //
7874 // Do everything necessary to handle a variable (non-block) declaration.
7875 // Either redeclaring a variable, or making a new one, updating the symbol
7876 // table, and all error checking.
7877 //
7878 // Returns a subtree node that computes an initializer, if needed.
7879 // Returns nullptr if there is no code to execute for initialization.
7880 //
7881 // 'parseType' is the type part of the declaration (to the left)
7882 // 'arraySizes' is the arrayness tagged on the identifier (to the right)
7883 //
declareVariable(const TSourceLoc & loc,const TString & identifier,TType & type,TIntermTyped * initializer)7884 TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, const TString& identifier, TType& type,
7885 TIntermTyped* initializer)
7886 {
7887 if (voidErrorCheck(loc, identifier, type.getBasicType()))
7888 return nullptr;
7889
7890 // Global consts with initializers that are non-const act like EvqGlobal in HLSL.
7891 // This test is implicitly recursive, because initializers propagate constness
7892 // up the aggregate node tree during creation. E.g, for:
7893 // { { 1, 2 }, { 3, 4 } }
7894 // the initializer list is marked EvqConst at the top node, and remains so here. However:
7895 // { 1, { myvar, 2 }, 3 }
7896 // is not a const intializer, and still becomes EvqGlobal here.
7897
7898 const bool nonConstInitializer = (initializer != nullptr && initializer->getQualifier().storage != EvqConst);
7899
7900 if (type.getQualifier().storage == EvqConst && symbolTable.atGlobalLevel() && nonConstInitializer) {
7901 // Force to global
7902 type.getQualifier().storage = EvqGlobal;
7903 }
7904
7905 // make const and initialization consistent
7906 fixConstInit(loc, identifier, type, initializer);
7907
7908 // Check for redeclaration of built-ins and/or attempting to declare a reserved name
7909 TSymbol* symbol = nullptr;
7910
7911 inheritGlobalDefaults(type.getQualifier());
7912
7913 const bool flattenVar = shouldFlatten(type, type.getQualifier().storage, true);
7914
7915 // correct IO in the type
7916 switch (type.getQualifier().storage) {
7917 case EvqGlobal:
7918 case EvqTemporary:
7919 clearUniformInputOutput(type.getQualifier());
7920 break;
7921 case EvqUniform:
7922 case EvqBuffer:
7923 correctUniform(type.getQualifier());
7924 if (type.isStruct()) {
7925 auto it = ioTypeMap.find(type.getStruct());
7926 if (it != ioTypeMap.end())
7927 type.setStruct(it->second.uniform);
7928 }
7929
7930 break;
7931 default:
7932 break;
7933 }
7934
7935 // Declare the variable
7936 if (type.isArray()) {
7937 // array case
7938 declareArray(loc, identifier, type, symbol, !flattenVar);
7939 } else {
7940 // non-array case
7941 if (symbol == nullptr)
7942 symbol = declareNonArray(loc, identifier, type, !flattenVar);
7943 else if (type != symbol->getType())
7944 error(loc, "cannot change the type of", "redeclaration", symbol->getName().c_str());
7945 }
7946
7947 if (symbol == nullptr)
7948 return nullptr;
7949
7950 if (flattenVar)
7951 flatten(*symbol->getAsVariable(), symbolTable.atGlobalLevel());
7952
7953 if (initializer == nullptr)
7954 return nullptr;
7955
7956 // Deal with initializer
7957 TVariable* variable = symbol->getAsVariable();
7958 if (variable == nullptr) {
7959 error(loc, "initializer requires a variable, not a member", identifier.c_str(), "");
7960 return nullptr;
7961 }
7962 return executeInitializer(loc, initializer, variable);
7963 }
7964
7965 // Pick up global defaults from the provide global defaults into dst.
inheritGlobalDefaults(TQualifier & dst) const7966 void HlslParseContext::inheritGlobalDefaults(TQualifier& dst) const
7967 {
7968 if (dst.storage == EvqVaryingOut) {
7969 if (! dst.hasStream() && language == EShLangGeometry)
7970 dst.layoutStream = globalOutputDefaults.layoutStream;
7971 if (! dst.hasXfbBuffer())
7972 dst.layoutXfbBuffer = globalOutputDefaults.layoutXfbBuffer;
7973 }
7974 }
7975
7976 //
7977 // Make an internal-only variable whose name is for debug purposes only
7978 // and won't be searched for. Callers will only use the return value to use
7979 // the variable, not the name to look it up. It is okay if the name
7980 // is the same as other names; there won't be any conflict.
7981 //
makeInternalVariable(const char * name,const TType & type) const7982 TVariable* HlslParseContext::makeInternalVariable(const char* name, const TType& type) const
7983 {
7984 TString* nameString = NewPoolTString(name);
7985 TVariable* variable = new TVariable(nameString, type);
7986 symbolTable.makeInternalVariable(*variable);
7987
7988 return variable;
7989 }
7990
7991 // Make a symbol node holding a new internal temporary variable.
makeInternalVariableNode(const TSourceLoc & loc,const char * name,const TType & type) const7992 TIntermSymbol* HlslParseContext::makeInternalVariableNode(const TSourceLoc& loc, const char* name,
7993 const TType& type) const
7994 {
7995 TVariable* tmpVar = makeInternalVariable(name, type);
7996 tmpVar->getWritableType().getQualifier().makeTemporary();
7997
7998 return intermediate.addSymbol(*tmpVar, loc);
7999 }
8000
8001 //
8002 // Declare a non-array variable, the main point being there is no redeclaration
8003 // for resizing allowed.
8004 //
8005 // Return the successfully declared variable.
8006 //
declareNonArray(const TSourceLoc & loc,const TString & identifier,const TType & type,bool track)8007 TVariable* HlslParseContext::declareNonArray(const TSourceLoc& loc, const TString& identifier, const TType& type,
8008 bool track)
8009 {
8010 // make a new variable
8011 TVariable* variable = new TVariable(&identifier, type);
8012
8013 // add variable to symbol table
8014 if (symbolTable.insert(*variable)) {
8015 if (track && symbolTable.atGlobalLevel())
8016 trackLinkage(*variable);
8017 return variable;
8018 }
8019
8020 error(loc, "redefinition", variable->getName().c_str(), "");
8021 return nullptr;
8022 }
8023
8024 //
8025 // Handle all types of initializers from the grammar.
8026 //
8027 // Returning nullptr just means there is no code to execute to handle the
8028 // initializer, which will, for example, be the case for constant initializers.
8029 //
8030 // Returns a subtree that accomplished the initialization.
8031 //
executeInitializer(const TSourceLoc & loc,TIntermTyped * initializer,TVariable * variable)8032 TIntermNode* HlslParseContext::executeInitializer(const TSourceLoc& loc, TIntermTyped* initializer, TVariable* variable)
8033 {
8034 //
8035 // Identifier must be of type constant, a global, or a temporary, and
8036 // starting at version 120, desktop allows uniforms to have initializers.
8037 //
8038 TStorageQualifier qualifier = variable->getType().getQualifier().storage;
8039
8040 //
8041 // If the initializer was from braces { ... }, we convert the whole subtree to a
8042 // constructor-style subtree, allowing the rest of the code to operate
8043 // identically for both kinds of initializers.
8044 //
8045 //
8046 // Type can't be deduced from the initializer list, so a skeletal type to
8047 // follow has to be passed in. Constness and specialization-constness
8048 // should be deduced bottom up, not dictated by the skeletal type.
8049 //
8050 TType skeletalType;
8051 skeletalType.shallowCopy(variable->getType());
8052 skeletalType.getQualifier().makeTemporary();
8053 if (initializer->getAsAggregate() && initializer->getAsAggregate()->getOp() == EOpNull)
8054 initializer = convertInitializerList(loc, skeletalType, initializer, nullptr);
8055 if (initializer == nullptr) {
8056 // error recovery; don't leave const without constant values
8057 if (qualifier == EvqConst)
8058 variable->getWritableType().getQualifier().storage = EvqTemporary;
8059 return nullptr;
8060 }
8061
8062 // Fix outer arrayness if variable is unsized, getting size from the initializer
8063 if (initializer->getType().isSizedArray() && variable->getType().isUnsizedArray())
8064 variable->getWritableType().changeOuterArraySize(initializer->getType().getOuterArraySize());
8065
8066 // Inner arrayness can also get set by an initializer
8067 if (initializer->getType().isArrayOfArrays() && variable->getType().isArrayOfArrays() &&
8068 initializer->getType().getArraySizes()->getNumDims() ==
8069 variable->getType().getArraySizes()->getNumDims()) {
8070 // adopt unsized sizes from the initializer's sizes
8071 for (int d = 1; d < variable->getType().getArraySizes()->getNumDims(); ++d) {
8072 if (variable->getType().getArraySizes()->getDimSize(d) == UnsizedArraySize) {
8073 variable->getWritableType().getArraySizes()->setDimSize(d,
8074 initializer->getType().getArraySizes()->getDimSize(d));
8075 }
8076 }
8077 }
8078
8079 // Uniform and global consts require a constant initializer
8080 if (qualifier == EvqUniform && initializer->getType().getQualifier().storage != EvqConst) {
8081 error(loc, "uniform initializers must be constant", "=", "'%s'", variable->getType().getCompleteString().c_str());
8082 variable->getWritableType().getQualifier().storage = EvqTemporary;
8083 return nullptr;
8084 }
8085
8086 // Const variables require a constant initializer
8087 if (qualifier == EvqConst) {
8088 if (initializer->getType().getQualifier().storage != EvqConst) {
8089 variable->getWritableType().getQualifier().storage = EvqConstReadOnly;
8090 qualifier = EvqConstReadOnly;
8091 }
8092 }
8093
8094 if (qualifier == EvqConst || qualifier == EvqUniform) {
8095 // Compile-time tagging of the variable with its constant value...
8096
8097 initializer = intermediate.addConversion(EOpAssign, variable->getType(), initializer);
8098 if (initializer != nullptr && variable->getType() != initializer->getType())
8099 initializer = intermediate.addUniShapeConversion(EOpAssign, variable->getType(), initializer);
8100 if (initializer == nullptr || !initializer->getAsConstantUnion() ||
8101 variable->getType() != initializer->getType()) {
8102 error(loc, "non-matching or non-convertible constant type for const initializer",
8103 variable->getType().getStorageQualifierString(), "");
8104 variable->getWritableType().getQualifier().storage = EvqTemporary;
8105 return nullptr;
8106 }
8107
8108 variable->setConstArray(initializer->getAsConstantUnion()->getConstArray());
8109 } else {
8110 // normal assigning of a value to a variable...
8111 specializationCheck(loc, initializer->getType(), "initializer");
8112 TIntermSymbol* intermSymbol = intermediate.addSymbol(*variable, loc);
8113 TIntermNode* initNode = handleAssign(loc, EOpAssign, intermSymbol, initializer);
8114 if (initNode == nullptr)
8115 assignError(loc, "=", intermSymbol->getCompleteString(), initializer->getCompleteString());
8116 return initNode;
8117 }
8118
8119 return nullptr;
8120 }
8121
8122 //
8123 // Reprocess any initializer-list { ... } parts of the initializer.
8124 // Need to hierarchically assign correct types and implicit
8125 // conversions. Will do this mimicking the same process used for
8126 // creating a constructor-style initializer, ensuring we get the
8127 // same form.
8128 //
8129 // Returns a node representing an expression for the initializer list expressed
8130 // as the correct type.
8131 //
8132 // Returns nullptr if there is an error.
8133 //
convertInitializerList(const TSourceLoc & loc,const TType & type,TIntermTyped * initializer,TIntermTyped * scalarInit)8134 TIntermTyped* HlslParseContext::convertInitializerList(const TSourceLoc& loc, const TType& type,
8135 TIntermTyped* initializer, TIntermTyped* scalarInit)
8136 {
8137 // Will operate recursively. Once a subtree is found that is constructor style,
8138 // everything below it is already good: Only the "top part" of the initializer
8139 // can be an initializer list, where "top part" can extend for several (or all) levels.
8140
8141 // see if we have bottomed out in the tree within the initializer-list part
8142 TIntermAggregate* initList = initializer->getAsAggregate();
8143 if (initList == nullptr || initList->getOp() != EOpNull) {
8144 // We don't have a list, but if it's a scalar and the 'type' is a
8145 // composite, we need to lengthen below to make it useful.
8146 // Otherwise, this is an already formed object to initialize with.
8147 if (type.isScalar() || !initializer->getType().isScalar())
8148 return initializer;
8149 else
8150 initList = intermediate.makeAggregate(initializer);
8151 }
8152
8153 // Of the initializer-list set of nodes, need to process bottom up,
8154 // so recurse deep, then process on the way up.
8155
8156 // Go down the tree here...
8157 if (type.isArray()) {
8158 // The type's array might be unsized, which could be okay, so base sizes on the size of the aggregate.
8159 // Later on, initializer execution code will deal with array size logic.
8160 TType arrayType;
8161 arrayType.shallowCopy(type); // sharing struct stuff is fine
8162 arrayType.copyArraySizes(*type.getArraySizes()); // but get a fresh copy of the array information, to edit below
8163
8164 // edit array sizes to fill in unsized dimensions
8165 if (type.isUnsizedArray())
8166 arrayType.changeOuterArraySize((int)initList->getSequence().size());
8167
8168 // set unsized array dimensions that can be derived from the initializer's first element
8169 if (arrayType.isArrayOfArrays() && initList->getSequence().size() > 0) {
8170 TIntermTyped* firstInit = initList->getSequence()[0]->getAsTyped();
8171 if (firstInit->getType().isArray() &&
8172 arrayType.getArraySizes()->getNumDims() == firstInit->getType().getArraySizes()->getNumDims() + 1) {
8173 for (int d = 1; d < arrayType.getArraySizes()->getNumDims(); ++d) {
8174 if (arrayType.getArraySizes()->getDimSize(d) == UnsizedArraySize)
8175 arrayType.getArraySizes()->setDimSize(d, firstInit->getType().getArraySizes()->getDimSize(d - 1));
8176 }
8177 }
8178 }
8179
8180 // lengthen list to be long enough
8181 lengthenList(loc, initList->getSequence(), arrayType.getOuterArraySize(), scalarInit);
8182
8183 // recursively process each element
8184 TType elementType(arrayType, 0); // dereferenced type
8185 for (int i = 0; i < arrayType.getOuterArraySize(); ++i) {
8186 initList->getSequence()[i] = convertInitializerList(loc, elementType,
8187 initList->getSequence()[i]->getAsTyped(), scalarInit);
8188 if (initList->getSequence()[i] == nullptr)
8189 return nullptr;
8190 }
8191
8192 return addConstructor(loc, initList, arrayType);
8193 } else if (type.isStruct()) {
8194 // do we have implicit assignments to opaques?
8195 for (size_t i = initList->getSequence().size(); i < type.getStruct()->size(); ++i) {
8196 if ((*type.getStruct())[i].type->containsOpaque()) {
8197 error(loc, "cannot implicitly initialize opaque members", "initializer list", "");
8198 return nullptr;
8199 }
8200 }
8201
8202 // lengthen list to be long enough
8203 lengthenList(loc, initList->getSequence(), static_cast<int>(type.getStruct()->size()), scalarInit);
8204
8205 if (type.getStruct()->size() != initList->getSequence().size()) {
8206 error(loc, "wrong number of structure members", "initializer list", "");
8207 return nullptr;
8208 }
8209 for (size_t i = 0; i < type.getStruct()->size(); ++i) {
8210 initList->getSequence()[i] = convertInitializerList(loc, *(*type.getStruct())[i].type,
8211 initList->getSequence()[i]->getAsTyped(), scalarInit);
8212 if (initList->getSequence()[i] == nullptr)
8213 return nullptr;
8214 }
8215 } else if (type.isMatrix()) {
8216 if (type.computeNumComponents() == (int)initList->getSequence().size()) {
8217 // This means the matrix is initialized component-wise, rather than as
8218 // a series of rows and columns. We can just use the list directly as
8219 // a constructor; no further processing needed.
8220 } else {
8221 // lengthen list to be long enough
8222 lengthenList(loc, initList->getSequence(), type.getMatrixCols(), scalarInit);
8223
8224 if (type.getMatrixCols() != (int)initList->getSequence().size()) {
8225 error(loc, "wrong number of matrix columns:", "initializer list", type.getCompleteString().c_str());
8226 return nullptr;
8227 }
8228 TType vectorType(type, 0); // dereferenced type
8229 for (int i = 0; i < type.getMatrixCols(); ++i) {
8230 initList->getSequence()[i] = convertInitializerList(loc, vectorType,
8231 initList->getSequence()[i]->getAsTyped(), scalarInit);
8232 if (initList->getSequence()[i] == nullptr)
8233 return nullptr;
8234 }
8235 }
8236 } else if (type.isVector()) {
8237 // lengthen list to be long enough
8238 lengthenList(loc, initList->getSequence(), type.getVectorSize(), scalarInit);
8239
8240 // error check; we're at bottom, so work is finished below
8241 if (type.getVectorSize() != (int)initList->getSequence().size()) {
8242 error(loc, "wrong vector size (or rows in a matrix column):", "initializer list",
8243 type.getCompleteString().c_str());
8244 return nullptr;
8245 }
8246 } else if (type.isScalar()) {
8247 // lengthen list to be long enough
8248 lengthenList(loc, initList->getSequence(), 1, scalarInit);
8249
8250 if ((int)initList->getSequence().size() != 1) {
8251 error(loc, "scalar expected one element:", "initializer list", type.getCompleteString().c_str());
8252 return nullptr;
8253 }
8254 } else {
8255 error(loc, "unexpected initializer-list type:", "initializer list", type.getCompleteString().c_str());
8256 return nullptr;
8257 }
8258
8259 // Now that the subtree is processed, process this node as if the
8260 // initializer list is a set of arguments to a constructor.
8261 TIntermTyped* emulatedConstructorArguments;
8262 if (initList->getSequence().size() == 1)
8263 emulatedConstructorArguments = initList->getSequence()[0]->getAsTyped();
8264 else
8265 emulatedConstructorArguments = initList;
8266
8267 return addConstructor(loc, emulatedConstructorArguments, type);
8268 }
8269
8270 // Lengthen list to be long enough to cover any gap from the current list size
8271 // to 'size'. If the list is longer, do nothing.
8272 // The value to lengthen with is the default for short lists.
8273 //
8274 // By default, lists that are too short due to lack of initializers initialize to zero.
8275 // Alternatively, it could be a scalar initializer for a structure. Both cases are handled,
8276 // based on whether something is passed in as 'scalarInit'.
8277 //
8278 // 'scalarInit' must be safe to use each time this is called (no side effects replication).
8279 //
lengthenList(const TSourceLoc & loc,TIntermSequence & list,int size,TIntermTyped * scalarInit)8280 void HlslParseContext::lengthenList(const TSourceLoc& loc, TIntermSequence& list, int size, TIntermTyped* scalarInit)
8281 {
8282 for (int c = (int)list.size(); c < size; ++c) {
8283 if (scalarInit == nullptr)
8284 list.push_back(intermediate.addConstantUnion(0, loc));
8285 else
8286 list.push_back(scalarInit);
8287 }
8288 }
8289
8290 //
8291 // Test for the correctness of the parameters passed to various constructor functions
8292 // and also convert them to the right data type, if allowed and required.
8293 //
8294 // Returns nullptr for an error or the constructed node (aggregate or typed) for no error.
8295 //
handleConstructor(const TSourceLoc & loc,TIntermTyped * node,const TType & type)8296 TIntermTyped* HlslParseContext::handleConstructor(const TSourceLoc& loc, TIntermTyped* node, const TType& type)
8297 {
8298 if (node == nullptr)
8299 return nullptr;
8300
8301 // Construct identical type
8302 if (type == node->getType())
8303 return node;
8304
8305 // Handle the idiom "(struct type)<scalar value>"
8306 if (type.isStruct() && isScalarConstructor(node)) {
8307 // 'node' will almost always get used multiple times, so should not be used directly,
8308 // it would create a DAG instead of a tree, which might be okay (would
8309 // like to formalize that for constants and symbols), but if it has
8310 // side effects, they would get executed multiple times, which is not okay.
8311 if (node->getAsConstantUnion() == nullptr && node->getAsSymbolNode() == nullptr) {
8312 TIntermAggregate* seq = intermediate.makeAggregate(loc);
8313 TIntermSymbol* copy = makeInternalVariableNode(loc, "scalarCopy", node->getType());
8314 seq = intermediate.growAggregate(seq, intermediate.addBinaryNode(EOpAssign, copy, node, loc));
8315 seq = intermediate.growAggregate(seq, convertInitializerList(loc, type, intermediate.makeAggregate(loc), copy));
8316 seq->setOp(EOpComma);
8317 seq->setType(type);
8318 return seq;
8319 } else
8320 return convertInitializerList(loc, type, intermediate.makeAggregate(loc), node);
8321 }
8322
8323 return addConstructor(loc, node, type);
8324 }
8325
8326 // Add a constructor, either from the grammar, or other programmatic reasons.
8327 //
8328 // 'node' is what to construct from.
8329 // 'type' is what type to construct.
8330 //
8331 // Returns the constructed object.
8332 // Return nullptr if it can't be done.
8333 //
addConstructor(const TSourceLoc & loc,TIntermTyped * node,const TType & type)8334 TIntermTyped* HlslParseContext::addConstructor(const TSourceLoc& loc, TIntermTyped* node, const TType& type)
8335 {
8336 TIntermAggregate* aggrNode = node->getAsAggregate();
8337 TOperator op = intermediate.mapTypeToConstructorOp(type);
8338
8339 if (op == EOpConstructTextureSampler)
8340 return intermediate.setAggregateOperator(aggrNode, op, type, loc);
8341
8342 TTypeList::const_iterator memberTypes;
8343 if (op == EOpConstructStruct)
8344 memberTypes = type.getStruct()->begin();
8345
8346 TType elementType;
8347 if (type.isArray()) {
8348 TType dereferenced(type, 0);
8349 elementType.shallowCopy(dereferenced);
8350 } else
8351 elementType.shallowCopy(type);
8352
8353 bool singleArg;
8354 if (aggrNode != nullptr) {
8355 if (aggrNode->getOp() != EOpNull)
8356 singleArg = true;
8357 else
8358 singleArg = false;
8359 } else
8360 singleArg = true;
8361
8362 TIntermTyped *newNode;
8363 if (singleArg) {
8364 // Handle array -> array conversion
8365 // Constructing an array of one type from an array of another type is allowed,
8366 // assuming there are enough components available (semantic-checked earlier).
8367 if (type.isArray() && node->isArray())
8368 newNode = convertArray(node, type);
8369
8370 // If structure constructor or array constructor is being called
8371 // for only one parameter inside the aggregate, we need to call constructAggregate function once.
8372 else if (type.isArray())
8373 newNode = constructAggregate(node, elementType, 1, node->getLoc());
8374 else if (op == EOpConstructStruct)
8375 newNode = constructAggregate(node, *(*memberTypes).type, 1, node->getLoc());
8376 else {
8377 // shape conversion for matrix constructor from scalar. HLSL semantics are: scalar
8378 // is replicated into every element of the matrix (not just the diagnonal), so
8379 // that is handled specially here.
8380 if (type.isMatrix() && node->getType().isScalarOrVec1())
8381 node = intermediate.addShapeConversion(type, node);
8382
8383 newNode = constructBuiltIn(type, op, node, node->getLoc(), false);
8384 }
8385
8386 if (newNode && (type.isArray() || op == EOpConstructStruct))
8387 newNode = intermediate.setAggregateOperator(newNode, EOpConstructStruct, type, loc);
8388
8389 return newNode;
8390 }
8391
8392 //
8393 // Handle list of arguments.
8394 //
8395 TIntermSequence& sequenceVector = aggrNode->getSequence(); // Stores the information about the parameter to the constructor
8396 // if the structure constructor contains more than one parameter, then construct
8397 // each parameter
8398
8399 int paramCount = 0; // keeps a track of the constructor parameter number being checked
8400
8401 // for each parameter to the constructor call, check to see if the right type is passed or convert them
8402 // to the right type if possible (and allowed).
8403 // for structure constructors, just check if the right type is passed, no conversion is allowed.
8404
8405 for (TIntermSequence::iterator p = sequenceVector.begin();
8406 p != sequenceVector.end(); p++, paramCount++) {
8407 if (type.isArray())
8408 newNode = constructAggregate(*p, elementType, paramCount + 1, node->getLoc());
8409 else if (op == EOpConstructStruct)
8410 newNode = constructAggregate(*p, *(memberTypes[paramCount]).type, paramCount + 1, node->getLoc());
8411 else
8412 newNode = constructBuiltIn(type, op, (*p)->getAsTyped(), node->getLoc(), true);
8413
8414 if (newNode)
8415 *p = newNode;
8416 else
8417 return nullptr;
8418 }
8419
8420 TIntermTyped* constructor = intermediate.setAggregateOperator(aggrNode, op, type, loc);
8421
8422 return constructor;
8423 }
8424
8425 // Function for constructor implementation. Calls addUnaryMath with appropriate EOp value
8426 // for the parameter to the constructor (passed to this function). Essentially, it converts
8427 // the parameter types correctly. If a constructor expects an int (like ivec2) and is passed a
8428 // float, then float is converted to int.
8429 //
8430 // Returns nullptr for an error or the constructed node.
8431 //
constructBuiltIn(const TType & type,TOperator op,TIntermTyped * node,const TSourceLoc & loc,bool subset)8432 TIntermTyped* HlslParseContext::constructBuiltIn(const TType& type, TOperator op, TIntermTyped* node,
8433 const TSourceLoc& loc, bool subset)
8434 {
8435 TIntermTyped* newNode;
8436 TOperator basicOp;
8437
8438 //
8439 // First, convert types as needed.
8440 //
8441 switch (op) {
8442 case EOpConstructF16Vec2:
8443 case EOpConstructF16Vec3:
8444 case EOpConstructF16Vec4:
8445 case EOpConstructF16Mat2x2:
8446 case EOpConstructF16Mat2x3:
8447 case EOpConstructF16Mat2x4:
8448 case EOpConstructF16Mat3x2:
8449 case EOpConstructF16Mat3x3:
8450 case EOpConstructF16Mat3x4:
8451 case EOpConstructF16Mat4x2:
8452 case EOpConstructF16Mat4x3:
8453 case EOpConstructF16Mat4x4:
8454 case EOpConstructFloat16:
8455 basicOp = EOpConstructFloat16;
8456 break;
8457
8458 case EOpConstructVec2:
8459 case EOpConstructVec3:
8460 case EOpConstructVec4:
8461 case EOpConstructMat2x2:
8462 case EOpConstructMat2x3:
8463 case EOpConstructMat2x4:
8464 case EOpConstructMat3x2:
8465 case EOpConstructMat3x3:
8466 case EOpConstructMat3x4:
8467 case EOpConstructMat4x2:
8468 case EOpConstructMat4x3:
8469 case EOpConstructMat4x4:
8470 case EOpConstructFloat:
8471 basicOp = EOpConstructFloat;
8472 break;
8473
8474 case EOpConstructDVec2:
8475 case EOpConstructDVec3:
8476 case EOpConstructDVec4:
8477 case EOpConstructDMat2x2:
8478 case EOpConstructDMat2x3:
8479 case EOpConstructDMat2x4:
8480 case EOpConstructDMat3x2:
8481 case EOpConstructDMat3x3:
8482 case EOpConstructDMat3x4:
8483 case EOpConstructDMat4x2:
8484 case EOpConstructDMat4x3:
8485 case EOpConstructDMat4x4:
8486 case EOpConstructDouble:
8487 basicOp = EOpConstructDouble;
8488 break;
8489
8490 case EOpConstructI16Vec2:
8491 case EOpConstructI16Vec3:
8492 case EOpConstructI16Vec4:
8493 case EOpConstructInt16:
8494 basicOp = EOpConstructInt16;
8495 break;
8496
8497 case EOpConstructIVec2:
8498 case EOpConstructIVec3:
8499 case EOpConstructIVec4:
8500 case EOpConstructIMat2x2:
8501 case EOpConstructIMat2x3:
8502 case EOpConstructIMat2x4:
8503 case EOpConstructIMat3x2:
8504 case EOpConstructIMat3x3:
8505 case EOpConstructIMat3x4:
8506 case EOpConstructIMat4x2:
8507 case EOpConstructIMat4x3:
8508 case EOpConstructIMat4x4:
8509 case EOpConstructInt:
8510 basicOp = EOpConstructInt;
8511 break;
8512
8513 case EOpConstructU16Vec2:
8514 case EOpConstructU16Vec3:
8515 case EOpConstructU16Vec4:
8516 case EOpConstructUint16:
8517 basicOp = EOpConstructUint16;
8518 break;
8519
8520 case EOpConstructUVec2:
8521 case EOpConstructUVec3:
8522 case EOpConstructUVec4:
8523 case EOpConstructUMat2x2:
8524 case EOpConstructUMat2x3:
8525 case EOpConstructUMat2x4:
8526 case EOpConstructUMat3x2:
8527 case EOpConstructUMat3x3:
8528 case EOpConstructUMat3x4:
8529 case EOpConstructUMat4x2:
8530 case EOpConstructUMat4x3:
8531 case EOpConstructUMat4x4:
8532 case EOpConstructUint:
8533 basicOp = EOpConstructUint;
8534 break;
8535
8536 case EOpConstructBVec2:
8537 case EOpConstructBVec3:
8538 case EOpConstructBVec4:
8539 case EOpConstructBMat2x2:
8540 case EOpConstructBMat2x3:
8541 case EOpConstructBMat2x4:
8542 case EOpConstructBMat3x2:
8543 case EOpConstructBMat3x3:
8544 case EOpConstructBMat3x4:
8545 case EOpConstructBMat4x2:
8546 case EOpConstructBMat4x3:
8547 case EOpConstructBMat4x4:
8548 case EOpConstructBool:
8549 basicOp = EOpConstructBool;
8550 break;
8551
8552 default:
8553 error(loc, "unsupported construction", "", "");
8554
8555 return nullptr;
8556 }
8557 newNode = intermediate.addUnaryMath(basicOp, node, node->getLoc());
8558 if (newNode == nullptr) {
8559 error(loc, "can't convert", "constructor", "");
8560 return nullptr;
8561 }
8562
8563 //
8564 // Now, if there still isn't an operation to do the construction, and we need one, add one.
8565 //
8566
8567 // Otherwise, skip out early.
8568 if (subset || (newNode != node && newNode->getType() == type))
8569 return newNode;
8570
8571 // setAggregateOperator will insert a new node for the constructor, as needed.
8572 return intermediate.setAggregateOperator(newNode, op, type, loc);
8573 }
8574
8575 // Convert the array in node to the requested type, which is also an array.
8576 // Returns nullptr on failure, otherwise returns aggregate holding the list of
8577 // elements needed to construct the array.
convertArray(TIntermTyped * node,const TType & type)8578 TIntermTyped* HlslParseContext::convertArray(TIntermTyped* node, const TType& type)
8579 {
8580 assert(node->isArray() && type.isArray());
8581 if (node->getType().computeNumComponents() < type.computeNumComponents())
8582 return nullptr;
8583
8584 // TODO: write an argument replicator, for the case the argument should not be
8585 // executed multiple times, yet multiple copies are needed.
8586
8587 TIntermTyped* constructee = node->getAsTyped();
8588 // track where we are in consuming the argument
8589 int constructeeElement = 0;
8590 int constructeeComponent = 0;
8591
8592 // bump up to the next component to consume
8593 const auto getNextComponent = [&]() {
8594 TIntermTyped* component;
8595 component = handleBracketDereference(node->getLoc(), constructee,
8596 intermediate.addConstantUnion(constructeeElement, node->getLoc()));
8597 if (component->isVector())
8598 component = handleBracketDereference(node->getLoc(), component,
8599 intermediate.addConstantUnion(constructeeComponent, node->getLoc()));
8600 // bump component pointer up
8601 ++constructeeComponent;
8602 if (constructeeComponent == constructee->getVectorSize()) {
8603 constructeeComponent = 0;
8604 ++constructeeElement;
8605 }
8606 return component;
8607 };
8608
8609 // make one subnode per constructed array element
8610 TIntermAggregate* constructor = nullptr;
8611 TType derefType(type, 0);
8612 TType speculativeComponentType(derefType, 0);
8613 TType* componentType = derefType.isVector() ? &speculativeComponentType : &derefType;
8614 TOperator componentOp = intermediate.mapTypeToConstructorOp(*componentType);
8615 TType crossType(node->getBasicType(), EvqTemporary, type.getVectorSize());
8616 for (int e = 0; e < type.getOuterArraySize(); ++e) {
8617 // construct an element
8618 TIntermTyped* elementArg;
8619 if (type.getVectorSize() == constructee->getVectorSize()) {
8620 // same element shape
8621 elementArg = handleBracketDereference(node->getLoc(), constructee,
8622 intermediate.addConstantUnion(e, node->getLoc()));
8623 } else {
8624 // mismatched element shapes
8625 if (type.getVectorSize() == 1)
8626 elementArg = getNextComponent();
8627 else {
8628 // make a vector
8629 TIntermAggregate* elementConstructee = nullptr;
8630 for (int c = 0; c < type.getVectorSize(); ++c)
8631 elementConstructee = intermediate.growAggregate(elementConstructee, getNextComponent());
8632 elementArg = addConstructor(node->getLoc(), elementConstructee, crossType);
8633 }
8634 }
8635 // convert basic types
8636 elementArg = intermediate.addConversion(componentOp, derefType, elementArg);
8637 if (elementArg == nullptr)
8638 return nullptr;
8639 // combine with top-level constructor
8640 constructor = intermediate.growAggregate(constructor, elementArg);
8641 }
8642
8643 return constructor;
8644 }
8645
8646 // This function tests for the type of the parameters to the structure or array constructor. Raises
8647 // an error message if the expected type does not match the parameter passed to the constructor.
8648 //
8649 // Returns nullptr for an error or the input node itself if the expected and the given parameter types match.
8650 //
constructAggregate(TIntermNode * node,const TType & type,int paramCount,const TSourceLoc & loc)8651 TIntermTyped* HlslParseContext::constructAggregate(TIntermNode* node, const TType& type, int paramCount,
8652 const TSourceLoc& loc)
8653 {
8654 // Handle cases that map more 1:1 between constructor arguments and constructed.
8655 TIntermTyped* converted = intermediate.addConversion(EOpConstructStruct, type, node->getAsTyped());
8656 if (converted == nullptr || converted->getType() != type) {
8657 error(loc, "", "constructor", "cannot convert parameter %d from '%s' to '%s'", paramCount,
8658 node->getAsTyped()->getType().getCompleteString().c_str(), type.getCompleteString().c_str());
8659
8660 return nullptr;
8661 }
8662
8663 return converted;
8664 }
8665
8666 //
8667 // Do everything needed to add an interface block.
8668 //
declareBlock(const TSourceLoc & loc,TType & type,const TString * instanceName)8669 void HlslParseContext::declareBlock(const TSourceLoc& loc, TType& type, const TString* instanceName)
8670 {
8671 assert(type.getWritableStruct() != nullptr);
8672
8673 // Clean up top-level decorations that don't belong.
8674 switch (type.getQualifier().storage) {
8675 case EvqUniform:
8676 case EvqBuffer:
8677 correctUniform(type.getQualifier());
8678 break;
8679 case EvqVaryingIn:
8680 correctInput(type.getQualifier());
8681 break;
8682 case EvqVaryingOut:
8683 correctOutput(type.getQualifier());
8684 break;
8685 default:
8686 break;
8687 }
8688
8689 TTypeList& typeList = *type.getWritableStruct();
8690 // fix and check for member storage qualifiers and types that don't belong within a block
8691 for (unsigned int member = 0; member < typeList.size(); ++member) {
8692 TType& memberType = *typeList[member].type;
8693 TQualifier& memberQualifier = memberType.getQualifier();
8694 const TSourceLoc& memberLoc = typeList[member].loc;
8695 globalQualifierFix(memberLoc, memberQualifier);
8696 memberQualifier.storage = type.getQualifier().storage;
8697
8698 if (memberType.isStruct()) {
8699 // clean up and pick up the right set of decorations
8700 auto it = ioTypeMap.find(memberType.getStruct());
8701 switch (type.getQualifier().storage) {
8702 case EvqUniform:
8703 case EvqBuffer:
8704 correctUniform(type.getQualifier());
8705 if (it != ioTypeMap.end() && it->second.uniform)
8706 memberType.setStruct(it->second.uniform);
8707 break;
8708 case EvqVaryingIn:
8709 correctInput(type.getQualifier());
8710 if (it != ioTypeMap.end() && it->second.input)
8711 memberType.setStruct(it->second.input);
8712 break;
8713 case EvqVaryingOut:
8714 correctOutput(type.getQualifier());
8715 if (it != ioTypeMap.end() && it->second.output)
8716 memberType.setStruct(it->second.output);
8717 break;
8718 default:
8719 break;
8720 }
8721 }
8722 }
8723
8724 // Make default block qualification, and adjust the member qualifications
8725
8726 TQualifier defaultQualification;
8727 switch (type.getQualifier().storage) {
8728 case EvqUniform: defaultQualification = globalUniformDefaults; break;
8729 case EvqBuffer: defaultQualification = globalBufferDefaults; break;
8730 case EvqVaryingIn: defaultQualification = globalInputDefaults; break;
8731 case EvqVaryingOut: defaultQualification = globalOutputDefaults; break;
8732 default: defaultQualification.clear(); break;
8733 }
8734
8735 // Special case for "push_constant uniform", which has a default of std430,
8736 // contrary to normal uniform defaults, and can't have a default tracked for it.
8737 if (type.getQualifier().layoutPushConstant && ! type.getQualifier().hasPacking())
8738 type.getQualifier().layoutPacking = ElpStd430;
8739
8740 // fix and check for member layout qualifiers
8741
8742 mergeObjectLayoutQualifiers(defaultQualification, type.getQualifier(), true);
8743
8744 bool memberWithLocation = false;
8745 bool memberWithoutLocation = false;
8746 for (unsigned int member = 0; member < typeList.size(); ++member) {
8747 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8748 const TSourceLoc& memberLoc = typeList[member].loc;
8749 if (memberQualifier.hasStream()) {
8750 if (defaultQualification.layoutStream != memberQualifier.layoutStream)
8751 error(memberLoc, "member cannot contradict block", "stream", "");
8752 }
8753
8754 // "This includes a block's inheritance of the
8755 // current global default buffer, a block member's inheritance of the block's
8756 // buffer, and the requirement that any *xfb_buffer* declared on a block
8757 // member must match the buffer inherited from the block."
8758 if (memberQualifier.hasXfbBuffer()) {
8759 if (defaultQualification.layoutXfbBuffer != memberQualifier.layoutXfbBuffer)
8760 error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_buffer", "");
8761 }
8762
8763 if (memberQualifier.hasLocation()) {
8764 switch (type.getQualifier().storage) {
8765 case EvqVaryingIn:
8766 case EvqVaryingOut:
8767 memberWithLocation = true;
8768 break;
8769 default:
8770 break;
8771 }
8772 } else
8773 memberWithoutLocation = true;
8774
8775 TQualifier newMemberQualification = defaultQualification;
8776 mergeQualifiers(newMemberQualification, memberQualifier);
8777 memberQualifier = newMemberQualification;
8778 }
8779
8780 // Process the members
8781 fixBlockLocations(loc, type.getQualifier(), typeList, memberWithLocation, memberWithoutLocation);
8782 fixXfbOffsets(type.getQualifier(), typeList);
8783 fixBlockUniformOffsets(type.getQualifier(), typeList);
8784
8785 // reverse merge, so that currentBlockQualifier now has all layout information
8786 // (can't use defaultQualification directly, it's missing other non-layout-default-class qualifiers)
8787 mergeObjectLayoutQualifiers(type.getQualifier(), defaultQualification, true);
8788
8789 //
8790 // Build and add the interface block as a new type named 'blockName'
8791 //
8792
8793 // Use the instance name as the interface name if one exists, else the block name.
8794 const TString& interfaceName = (instanceName && !instanceName->empty()) ? *instanceName : type.getTypeName();
8795
8796 TType blockType(&typeList, interfaceName, type.getQualifier());
8797 if (type.isArray())
8798 blockType.transferArraySizes(type.getArraySizes());
8799
8800 // Add the variable, as anonymous or named instanceName.
8801 // Make an anonymous variable if no name was provided.
8802 if (instanceName == nullptr)
8803 instanceName = NewPoolTString("");
8804
8805 TVariable& variable = *new TVariable(instanceName, blockType);
8806 if (! symbolTable.insert(variable)) {
8807 if (*instanceName == "")
8808 error(loc, "nameless block contains a member that already has a name at global scope",
8809 "" /* blockName->c_str() */, "");
8810 else
8811 error(loc, "block instance name redefinition", variable.getName().c_str(), "");
8812
8813 return;
8814 }
8815
8816 // Save it in the AST for linker use.
8817 if (symbolTable.atGlobalLevel())
8818 trackLinkage(variable);
8819 }
8820
8821 //
8822 // "For a block, this process applies to the entire block, or until the first member
8823 // is reached that has a location layout qualifier. When a block member is declared with a location
8824 // qualifier, its location comes from that qualifier: The member's location qualifier overrides the block-level
8825 // declaration. Subsequent members are again assigned consecutive locations, based on the newest location,
8826 // until the next member declared with a location qualifier. The values used for locations do not have to be
8827 // declared in increasing order."
fixBlockLocations(const TSourceLoc & loc,TQualifier & qualifier,TTypeList & typeList,bool memberWithLocation,bool memberWithoutLocation)8828 void HlslParseContext::fixBlockLocations(const TSourceLoc& loc, TQualifier& qualifier, TTypeList& typeList, bool memberWithLocation, bool memberWithoutLocation)
8829 {
8830 // "If a block has no block-level location layout qualifier, it is required that either all or none of its members
8831 // have a location layout qualifier, or a compile-time error results."
8832 if (! qualifier.hasLocation() && memberWithLocation && memberWithoutLocation)
8833 error(loc, "either the block needs a location, or all members need a location, or no members have a location", "location", "");
8834 else {
8835 if (memberWithLocation) {
8836 // remove any block-level location and make it per *every* member
8837 int nextLocation = 0; // by the rule above, initial value is not relevant
8838 if (qualifier.hasAnyLocation()) {
8839 nextLocation = qualifier.layoutLocation;
8840 qualifier.layoutLocation = TQualifier::layoutLocationEnd;
8841 if (qualifier.hasComponent()) {
8842 // "It is a compile-time error to apply the *component* qualifier to a ... block"
8843 error(loc, "cannot apply to a block", "component", "");
8844 }
8845 if (qualifier.hasIndex()) {
8846 error(loc, "cannot apply to a block", "index", "");
8847 }
8848 }
8849 for (unsigned int member = 0; member < typeList.size(); ++member) {
8850 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8851 const TSourceLoc& memberLoc = typeList[member].loc;
8852 if (! memberQualifier.hasLocation()) {
8853 if (nextLocation >= (int)TQualifier::layoutLocationEnd)
8854 error(memberLoc, "location is too large", "location", "");
8855 memberQualifier.layoutLocation = nextLocation;
8856 memberQualifier.layoutComponent = 0;
8857 }
8858 nextLocation = memberQualifier.layoutLocation +
8859 intermediate.computeTypeLocationSize(*typeList[member].type, language);
8860 }
8861 }
8862 }
8863 }
8864
fixXfbOffsets(TQualifier & qualifier,TTypeList & typeList)8865 void HlslParseContext::fixXfbOffsets(TQualifier& qualifier, TTypeList& typeList)
8866 {
8867 // "If a block is qualified with xfb_offset, all its
8868 // members are assigned transform feedback buffer offsets. If a block is not qualified with xfb_offset, any
8869 // members of that block not qualified with an xfb_offset will not be assigned transform feedback buffer
8870 // offsets."
8871
8872 if (! qualifier.hasXfbBuffer() || ! qualifier.hasXfbOffset())
8873 return;
8874
8875 int nextOffset = qualifier.layoutXfbOffset;
8876 for (unsigned int member = 0; member < typeList.size(); ++member) {
8877 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8878 bool contains64BitType = false;
8879 bool contains32BitType = false;
8880 bool contains16BitType = false;
8881 int memberSize = intermediate.computeTypeXfbSize(*typeList[member].type, contains64BitType, contains32BitType, contains16BitType);
8882 // see if we need to auto-assign an offset to this member
8883 if (! memberQualifier.hasXfbOffset()) {
8884 // "if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8"
8885 if (contains64BitType)
8886 RoundToPow2(nextOffset, 8);
8887 else if (contains32BitType)
8888 RoundToPow2(nextOffset, 4);
8889 // "if applied to an aggregate containing a half float or 16-bit integer, the offset must also be a multiple of 2"
8890 else if (contains16BitType)
8891 RoundToPow2(nextOffset, 2);
8892 memberQualifier.layoutXfbOffset = nextOffset;
8893 } else
8894 nextOffset = memberQualifier.layoutXfbOffset;
8895 nextOffset += memberSize;
8896 }
8897
8898 // The above gave all block members an offset, so we can take it off the block now,
8899 // which will avoid double counting the offset usage.
8900 qualifier.layoutXfbOffset = TQualifier::layoutXfbOffsetEnd;
8901 }
8902
8903 // Calculate and save the offset of each block member, using the recursively
8904 // defined block offset rules and the user-provided offset and align.
8905 //
8906 // Also, compute and save the total size of the block. For the block's size, arrayness
8907 // is not taken into account, as each element is backed by a separate buffer.
8908 //
fixBlockUniformOffsets(const TQualifier & qualifier,TTypeList & typeList)8909 void HlslParseContext::fixBlockUniformOffsets(const TQualifier& qualifier, TTypeList& typeList)
8910 {
8911 if (! qualifier.isUniformOrBuffer())
8912 return;
8913 if (qualifier.layoutPacking != ElpStd140 && qualifier.layoutPacking != ElpStd430 && qualifier.layoutPacking != ElpScalar)
8914 return;
8915
8916 int offset = 0;
8917 int memberSize;
8918 for (unsigned int member = 0; member < typeList.size(); ++member) {
8919 TQualifier& memberQualifier = typeList[member].type->getQualifier();
8920 const TSourceLoc& memberLoc = typeList[member].loc;
8921
8922 // "When align is applied to an array, it effects only the start of the array, not the array's internal stride."
8923
8924 // modify just the children's view of matrix layout, if there is one for this member
8925 TLayoutMatrix subMatrixLayout = typeList[member].type->getQualifier().layoutMatrix;
8926 int dummyStride;
8927 int memberAlignment = intermediate.getMemberAlignment(*typeList[member].type, memberSize, dummyStride,
8928 qualifier.layoutPacking,
8929 subMatrixLayout != ElmNone
8930 ? subMatrixLayout == ElmRowMajor
8931 : qualifier.layoutMatrix == ElmRowMajor);
8932 if (memberQualifier.hasOffset()) {
8933 // "The specified offset must be a multiple
8934 // of the base alignment of the type of the block member it qualifies, or a compile-time error results."
8935 if (! IsMultipleOfPow2(memberQualifier.layoutOffset, memberAlignment))
8936 error(memberLoc, "must be a multiple of the member's alignment", "offset", "");
8937
8938 // "The offset qualifier forces the qualified member to start at or after the specified
8939 // integral-constant expression, which will be its byte offset from the beginning of the buffer.
8940 // "The actual offset of a member is computed as
8941 // follows: If offset was declared, start with that offset, otherwise start with the next available offset."
8942 offset = std::max(offset, memberQualifier.layoutOffset);
8943 }
8944
8945 // "The actual alignment of a member will be the greater of the specified align alignment and the standard
8946 // (e.g., std140) base alignment for the member's type."
8947 if (memberQualifier.hasAlign())
8948 memberAlignment = std::max(memberAlignment, memberQualifier.layoutAlign);
8949
8950 // "If the resulting offset is not a multiple of the actual alignment,
8951 // increase it to the first offset that is a multiple of
8952 // the actual alignment."
8953 RoundToPow2(offset, memberAlignment);
8954 typeList[member].type->getQualifier().layoutOffset = offset;
8955 offset += memberSize;
8956 }
8957 }
8958
8959 // For an identifier that is already declared, add more qualification to it.
addQualifierToExisting(const TSourceLoc & loc,TQualifier qualifier,const TString & identifier)8960 void HlslParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, const TString& identifier)
8961 {
8962 TSymbol* symbol = symbolTable.find(identifier);
8963 if (symbol == nullptr) {
8964 error(loc, "identifier not previously declared", identifier.c_str(), "");
8965 return;
8966 }
8967 if (symbol->getAsFunction()) {
8968 error(loc, "cannot re-qualify a function name", identifier.c_str(), "");
8969 return;
8970 }
8971
8972 if (qualifier.isAuxiliary() ||
8973 qualifier.isMemory() ||
8974 qualifier.isInterpolation() ||
8975 qualifier.hasLayout() ||
8976 qualifier.storage != EvqTemporary ||
8977 qualifier.precision != EpqNone) {
8978 error(loc, "cannot add storage, auxiliary, memory, interpolation, layout, or precision qualifier to an existing variable", identifier.c_str(), "");
8979 return;
8980 }
8981
8982 // For read-only built-ins, add a new symbol for holding the modified qualifier.
8983 // This will bring up an entire block, if a block type has to be modified (e.g., gl_Position inside a block)
8984 if (symbol->isReadOnly())
8985 symbol = symbolTable.copyUp(symbol);
8986
8987 if (qualifier.invariant) {
8988 if (intermediate.inIoAccessed(identifier))
8989 error(loc, "cannot change qualification after use", "invariant", "");
8990 symbol->getWritableType().getQualifier().invariant = true;
8991 } else if (qualifier.noContraction) {
8992 if (intermediate.inIoAccessed(identifier))
8993 error(loc, "cannot change qualification after use", "precise", "");
8994 symbol->getWritableType().getQualifier().noContraction = true;
8995 } else if (qualifier.specConstant) {
8996 symbol->getWritableType().getQualifier().makeSpecConstant();
8997 if (qualifier.hasSpecConstantId())
8998 symbol->getWritableType().getQualifier().layoutSpecConstantId = qualifier.layoutSpecConstantId;
8999 } else
9000 warn(loc, "unknown requalification", "", "");
9001 }
9002
addQualifierToExisting(const TSourceLoc & loc,TQualifier qualifier,TIdentifierList & identifiers)9003 void HlslParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, TIdentifierList& identifiers)
9004 {
9005 for (unsigned int i = 0; i < identifiers.size(); ++i)
9006 addQualifierToExisting(loc, qualifier, *identifiers[i]);
9007 }
9008
9009 //
9010 // Update the intermediate for the given input geometry
9011 //
handleInputGeometry(const TSourceLoc & loc,const TLayoutGeometry & geometry)9012 bool HlslParseContext::handleInputGeometry(const TSourceLoc& loc, const TLayoutGeometry& geometry)
9013 {
9014 // these can be declared on non-entry-points, in which case they lose their meaning
9015 if (! parsingEntrypointParameters)
9016 return true;
9017
9018 switch (geometry) {
9019 case ElgPoints: // fall through
9020 case ElgLines: // ...
9021 case ElgTriangles: // ...
9022 case ElgLinesAdjacency: // ...
9023 case ElgTrianglesAdjacency: // ...
9024 if (! intermediate.setInputPrimitive(geometry)) {
9025 error(loc, "input primitive geometry redefinition", TQualifier::getGeometryString(geometry), "");
9026 return false;
9027 }
9028 break;
9029
9030 default:
9031 error(loc, "cannot apply to 'in'", TQualifier::getGeometryString(geometry), "");
9032 return false;
9033 }
9034
9035 return true;
9036 }
9037
9038 //
9039 // Update the intermediate for the given output geometry
9040 //
handleOutputGeometry(const TSourceLoc & loc,const TLayoutGeometry & geometry)9041 bool HlslParseContext::handleOutputGeometry(const TSourceLoc& loc, const TLayoutGeometry& geometry)
9042 {
9043 // If this is not a geometry shader, ignore. It might be a mixed shader including several stages.
9044 // Since that's an OK situation, return true for success.
9045 if (language != EShLangGeometry)
9046 return true;
9047
9048 // these can be declared on non-entry-points, in which case they lose their meaning
9049 if (! parsingEntrypointParameters)
9050 return true;
9051
9052 switch (geometry) {
9053 case ElgPoints:
9054 case ElgLineStrip:
9055 case ElgTriangleStrip:
9056 if (! intermediate.setOutputPrimitive(geometry)) {
9057 error(loc, "output primitive geometry redefinition", TQualifier::getGeometryString(geometry), "");
9058 return false;
9059 }
9060 break;
9061 default:
9062 error(loc, "cannot apply to 'out'", TQualifier::getGeometryString(geometry), "");
9063 return false;
9064 }
9065
9066 return true;
9067 }
9068
9069 //
9070 // Selection attributes
9071 //
handleSelectionAttributes(const TSourceLoc & loc,TIntermSelection * selection,const TAttributes & attributes)9072 void HlslParseContext::handleSelectionAttributes(const TSourceLoc& loc, TIntermSelection* selection,
9073 const TAttributes& attributes)
9074 {
9075 if (selection == nullptr)
9076 return;
9077
9078 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9079 switch (it->name) {
9080 case EatFlatten:
9081 selection->setFlatten();
9082 break;
9083 case EatBranch:
9084 selection->setDontFlatten();
9085 break;
9086 default:
9087 warn(loc, "attribute does not apply to a selection", "", "");
9088 break;
9089 }
9090 }
9091 }
9092
9093 //
9094 // Switch attributes
9095 //
handleSwitchAttributes(const TSourceLoc & loc,TIntermSwitch * selection,const TAttributes & attributes)9096 void HlslParseContext::handleSwitchAttributes(const TSourceLoc& loc, TIntermSwitch* selection,
9097 const TAttributes& attributes)
9098 {
9099 if (selection == nullptr)
9100 return;
9101
9102 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9103 switch (it->name) {
9104 case EatFlatten:
9105 selection->setFlatten();
9106 break;
9107 case EatBranch:
9108 selection->setDontFlatten();
9109 break;
9110 default:
9111 warn(loc, "attribute does not apply to a switch", "", "");
9112 break;
9113 }
9114 }
9115 }
9116
9117 //
9118 // Loop attributes
9119 //
handleLoopAttributes(const TSourceLoc & loc,TIntermLoop * loop,const TAttributes & attributes)9120 void HlslParseContext::handleLoopAttributes(const TSourceLoc& loc, TIntermLoop* loop,
9121 const TAttributes& attributes)
9122 {
9123 if (loop == nullptr)
9124 return;
9125
9126 for (auto it = attributes.begin(); it != attributes.end(); ++it) {
9127 switch (it->name) {
9128 case EatUnroll:
9129 loop->setUnroll();
9130 break;
9131 case EatLoop:
9132 loop->setDontUnroll();
9133 break;
9134 default:
9135 warn(loc, "attribute does not apply to a loop", "", "");
9136 break;
9137 }
9138 }
9139 }
9140
9141 //
9142 // Updating default qualifier for the case of a declaration with just a qualifier,
9143 // no type, block, or identifier.
9144 //
updateStandaloneQualifierDefaults(const TSourceLoc & loc,const TPublicType & publicType)9145 void HlslParseContext::updateStandaloneQualifierDefaults(const TSourceLoc& loc, const TPublicType& publicType)
9146 {
9147 if (publicType.shaderQualifiers.vertices != TQualifier::layoutNotSet) {
9148 assert(language == EShLangTessControl || language == EShLangGeometry);
9149 // const char* id = (language == EShLangTessControl) ? "vertices" : "max_vertices";
9150 }
9151 if (publicType.shaderQualifiers.invocations != TQualifier::layoutNotSet) {
9152 if (! intermediate.setInvocations(publicType.shaderQualifiers.invocations))
9153 error(loc, "cannot change previously set layout value", "invocations", "");
9154 }
9155 if (publicType.shaderQualifiers.geometry != ElgNone) {
9156 if (publicType.qualifier.storage == EvqVaryingIn) {
9157 switch (publicType.shaderQualifiers.geometry) {
9158 case ElgPoints:
9159 case ElgLines:
9160 case ElgLinesAdjacency:
9161 case ElgTriangles:
9162 case ElgTrianglesAdjacency:
9163 case ElgQuads:
9164 case ElgIsolines:
9165 break;
9166 default:
9167 error(loc, "cannot apply to input", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry),
9168 "");
9169 }
9170 } else if (publicType.qualifier.storage == EvqVaryingOut) {
9171 handleOutputGeometry(loc, publicType.shaderQualifiers.geometry);
9172 } else
9173 error(loc, "cannot apply to:", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry),
9174 GetStorageQualifierString(publicType.qualifier.storage));
9175 }
9176 if (publicType.shaderQualifiers.spacing != EvsNone)
9177 intermediate.setVertexSpacing(publicType.shaderQualifiers.spacing);
9178 if (publicType.shaderQualifiers.order != EvoNone)
9179 intermediate.setVertexOrder(publicType.shaderQualifiers.order);
9180 if (publicType.shaderQualifiers.pointMode)
9181 intermediate.setPointMode();
9182 for (int i = 0; i < 3; ++i) {
9183 if (publicType.shaderQualifiers.localSize[i] > 1) {
9184 int max = 0;
9185 switch (i) {
9186 case 0: max = resources.maxComputeWorkGroupSizeX; break;
9187 case 1: max = resources.maxComputeWorkGroupSizeY; break;
9188 case 2: max = resources.maxComputeWorkGroupSizeZ; break;
9189 default: break;
9190 }
9191 if (intermediate.getLocalSize(i) > (unsigned int)max)
9192 error(loc, "too large; see gl_MaxComputeWorkGroupSize", "local_size", "");
9193
9194 // Fix the existing constant gl_WorkGroupSize with this new information.
9195 TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
9196 workGroupSize->getWritableConstArray()[i].setUConst(intermediate.getLocalSize(i));
9197 }
9198 if (publicType.shaderQualifiers.localSizeSpecId[i] != TQualifier::layoutNotSet) {
9199 intermediate.setLocalSizeSpecId(i, publicType.shaderQualifiers.localSizeSpecId[i]);
9200 // Set the workgroup built-in variable as a specialization constant
9201 TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
9202 workGroupSize->getWritableType().getQualifier().specConstant = true;
9203 }
9204 }
9205 if (publicType.shaderQualifiers.earlyFragmentTests)
9206 intermediate.setEarlyFragmentTests();
9207
9208 const TQualifier& qualifier = publicType.qualifier;
9209
9210 switch (qualifier.storage) {
9211 case EvqUniform:
9212 if (qualifier.hasMatrix())
9213 globalUniformDefaults.layoutMatrix = qualifier.layoutMatrix;
9214 if (qualifier.hasPacking())
9215 globalUniformDefaults.layoutPacking = qualifier.layoutPacking;
9216 break;
9217 case EvqBuffer:
9218 if (qualifier.hasMatrix())
9219 globalBufferDefaults.layoutMatrix = qualifier.layoutMatrix;
9220 if (qualifier.hasPacking())
9221 globalBufferDefaults.layoutPacking = qualifier.layoutPacking;
9222 break;
9223 case EvqVaryingIn:
9224 break;
9225 case EvqVaryingOut:
9226 if (qualifier.hasStream())
9227 globalOutputDefaults.layoutStream = qualifier.layoutStream;
9228 if (qualifier.hasXfbBuffer())
9229 globalOutputDefaults.layoutXfbBuffer = qualifier.layoutXfbBuffer;
9230 if (globalOutputDefaults.hasXfbBuffer() && qualifier.hasXfbStride()) {
9231 if (! intermediate.setXfbBufferStride(globalOutputDefaults.layoutXfbBuffer, qualifier.layoutXfbStride))
9232 error(loc, "all stride settings must match for xfb buffer", "xfb_stride", "%d",
9233 qualifier.layoutXfbBuffer);
9234 }
9235 break;
9236 default:
9237 error(loc, "default qualifier requires 'uniform', 'buffer', 'in', or 'out' storage qualification", "", "");
9238 return;
9239 }
9240 }
9241
9242 //
9243 // Take the sequence of statements that has been built up since the last case/default,
9244 // put it on the list of top-level nodes for the current (inner-most) switch statement,
9245 // and follow that by the case/default we are on now. (See switch topology comment on
9246 // TIntermSwitch.)
9247 //
wrapupSwitchSubsequence(TIntermAggregate * statements,TIntermNode * branchNode)9248 void HlslParseContext::wrapupSwitchSubsequence(TIntermAggregate* statements, TIntermNode* branchNode)
9249 {
9250 TIntermSequence* switchSequence = switchSequenceStack.back();
9251
9252 if (statements) {
9253 statements->setOperator(EOpSequence);
9254 switchSequence->push_back(statements);
9255 }
9256 if (branchNode) {
9257 // check all previous cases for the same label (or both are 'default')
9258 for (unsigned int s = 0; s < switchSequence->size(); ++s) {
9259 TIntermBranch* prevBranch = (*switchSequence)[s]->getAsBranchNode();
9260 if (prevBranch) {
9261 TIntermTyped* prevExpression = prevBranch->getExpression();
9262 TIntermTyped* newExpression = branchNode->getAsBranchNode()->getExpression();
9263 if (prevExpression == nullptr && newExpression == nullptr)
9264 error(branchNode->getLoc(), "duplicate label", "default", "");
9265 else if (prevExpression != nullptr &&
9266 newExpression != nullptr &&
9267 prevExpression->getAsConstantUnion() &&
9268 newExpression->getAsConstantUnion() &&
9269 prevExpression->getAsConstantUnion()->getConstArray()[0].getIConst() ==
9270 newExpression->getAsConstantUnion()->getConstArray()[0].getIConst())
9271 error(branchNode->getLoc(), "duplicated value", "case", "");
9272 }
9273 }
9274 switchSequence->push_back(branchNode);
9275 }
9276 }
9277
9278 //
9279 // Turn the top-level node sequence built up of wrapupSwitchSubsequence
9280 // into a switch node.
9281 //
addSwitch(const TSourceLoc & loc,TIntermTyped * expression,TIntermAggregate * lastStatements,const TAttributes & attributes)9282 TIntermNode* HlslParseContext::addSwitch(const TSourceLoc& loc, TIntermTyped* expression,
9283 TIntermAggregate* lastStatements, const TAttributes& attributes)
9284 {
9285 wrapupSwitchSubsequence(lastStatements, nullptr);
9286
9287 if (expression == nullptr ||
9288 (expression->getBasicType() != EbtInt && expression->getBasicType() != EbtUint) ||
9289 expression->getType().isArray() || expression->getType().isMatrix() || expression->getType().isVector())
9290 error(loc, "condition must be a scalar integer expression", "switch", "");
9291
9292 // If there is nothing to do, drop the switch but still execute the expression
9293 TIntermSequence* switchSequence = switchSequenceStack.back();
9294 if (switchSequence->size() == 0)
9295 return expression;
9296
9297 if (lastStatements == nullptr) {
9298 // emulate a break for error recovery
9299 lastStatements = intermediate.makeAggregate(intermediate.addBranch(EOpBreak, loc));
9300 lastStatements->setOperator(EOpSequence);
9301 switchSequence->push_back(lastStatements);
9302 }
9303
9304 TIntermAggregate* body = new TIntermAggregate(EOpSequence);
9305 body->getSequence() = *switchSequenceStack.back();
9306 body->setLoc(loc);
9307
9308 TIntermSwitch* switchNode = new TIntermSwitch(expression, body);
9309 switchNode->setLoc(loc);
9310 handleSwitchAttributes(loc, switchNode, attributes);
9311
9312 return switchNode;
9313 }
9314
9315 // Make a new symbol-table level that is made out of the members of a structure.
9316 // This should be done as an anonymous struct (name is "") so that the symbol table
9317 // finds the members with no explicit reference to a 'this' variable.
pushThisScope(const TType & thisStruct,const TVector<TFunctionDeclarator> & functionDeclarators)9318 void HlslParseContext::pushThisScope(const TType& thisStruct, const TVector<TFunctionDeclarator>& functionDeclarators)
9319 {
9320 // member variables
9321 TVariable& thisVariable = *new TVariable(NewPoolTString(""), thisStruct);
9322 symbolTable.pushThis(thisVariable);
9323
9324 // member functions
9325 for (auto it = functionDeclarators.begin(); it != functionDeclarators.end(); ++it) {
9326 // member should have a prefix matching currentTypePrefix.back()
9327 // but, symbol lookup within the class scope will just use the
9328 // unprefixed name. Hence, there are two: one fully prefixed and
9329 // one with no prefix.
9330 TFunction& member = *it->function->clone();
9331 member.removePrefix(currentTypePrefix.back());
9332 symbolTable.insert(member);
9333 }
9334 }
9335
9336 // Track levels of class/struct/namespace nesting with a prefix string using
9337 // the type names separated by the scoping operator. E.g., two levels
9338 // would look like:
9339 //
9340 // outer::inner
9341 //
9342 // The string is empty when at normal global level.
9343 //
pushNamespace(const TString & typeName)9344 void HlslParseContext::pushNamespace(const TString& typeName)
9345 {
9346 // make new type prefix
9347 TString newPrefix;
9348 if (currentTypePrefix.size() > 0)
9349 newPrefix = currentTypePrefix.back();
9350 newPrefix.append(typeName);
9351 newPrefix.append(scopeMangler);
9352 currentTypePrefix.push_back(newPrefix);
9353 }
9354
9355 // Opposite of pushNamespace(), see above
popNamespace()9356 void HlslParseContext::popNamespace()
9357 {
9358 currentTypePrefix.pop_back();
9359 }
9360
9361 // Use the class/struct nesting string to create a global name for
9362 // a member of a class/struct.
getFullNamespaceName(TString * & name) const9363 void HlslParseContext::getFullNamespaceName(TString*& name) const
9364 {
9365 if (currentTypePrefix.size() == 0)
9366 return;
9367
9368 TString* fullName = NewPoolTString(currentTypePrefix.back().c_str());
9369 fullName->append(*name);
9370 name = fullName;
9371 }
9372
9373 // Helper function to add the namespace scope mangling syntax to a string.
addScopeMangler(TString & name)9374 void HlslParseContext::addScopeMangler(TString& name)
9375 {
9376 name.append(scopeMangler);
9377 }
9378
9379 // Return true if this has uniform-interface like decorations.
hasUniform(const TQualifier & qualifier) const9380 bool HlslParseContext::hasUniform(const TQualifier& qualifier) const
9381 {
9382 return qualifier.hasUniformLayout() ||
9383 qualifier.layoutPushConstant;
9384 }
9385
9386 // Potentially not the opposite of hasUniform(), as if some characteristic is
9387 // ever used for more than one thing (e.g., uniform or input), hasUniform() should
9388 // say it exists, but clearUniform() should leave it in place.
clearUniform(TQualifier & qualifier)9389 void HlslParseContext::clearUniform(TQualifier& qualifier)
9390 {
9391 qualifier.clearUniformLayout();
9392 qualifier.layoutPushConstant = false;
9393 }
9394
9395 // Return false if builtIn by itself doesn't force this qualifier to be an input qualifier.
isInputBuiltIn(const TQualifier & qualifier) const9396 bool HlslParseContext::isInputBuiltIn(const TQualifier& qualifier) const
9397 {
9398 switch (qualifier.builtIn) {
9399 case EbvPosition:
9400 case EbvPointSize:
9401 return language != EShLangVertex && language != EShLangCompute && language != EShLangFragment;
9402 case EbvClipDistance:
9403 case EbvCullDistance:
9404 return language != EShLangVertex && language != EShLangCompute;
9405 case EbvFragCoord:
9406 case EbvFace:
9407 case EbvHelperInvocation:
9408 case EbvLayer:
9409 case EbvPointCoord:
9410 case EbvSampleId:
9411 case EbvSampleMask:
9412 case EbvSamplePosition:
9413 case EbvViewportIndex:
9414 return language == EShLangFragment;
9415 case EbvGlobalInvocationId:
9416 case EbvLocalInvocationIndex:
9417 case EbvLocalInvocationId:
9418 case EbvNumWorkGroups:
9419 case EbvWorkGroupId:
9420 case EbvWorkGroupSize:
9421 return language == EShLangCompute;
9422 case EbvInvocationId:
9423 return language == EShLangTessControl || language == EShLangTessEvaluation || language == EShLangGeometry;
9424 case EbvPatchVertices:
9425 return language == EShLangTessControl || language == EShLangTessEvaluation;
9426 case EbvInstanceId:
9427 case EbvInstanceIndex:
9428 case EbvVertexId:
9429 case EbvVertexIndex:
9430 return language == EShLangVertex;
9431 case EbvPrimitiveId:
9432 return language == EShLangGeometry || language == EShLangFragment || language == EShLangTessControl;
9433 case EbvTessLevelInner:
9434 case EbvTessLevelOuter:
9435 return language == EShLangTessEvaluation;
9436 case EbvTessCoord:
9437 return language == EShLangTessEvaluation;
9438 default:
9439 return false;
9440 }
9441 }
9442
9443 // Return true if there are decorations to preserve for input-like storage.
hasInput(const TQualifier & qualifier) const9444 bool HlslParseContext::hasInput(const TQualifier& qualifier) const
9445 {
9446 if (qualifier.hasAnyLocation())
9447 return true;
9448
9449 if (language == EShLangFragment && (qualifier.isInterpolation() || qualifier.centroid || qualifier.sample))
9450 return true;
9451
9452 if (language == EShLangTessEvaluation && qualifier.patch)
9453 return true;
9454
9455 if (isInputBuiltIn(qualifier))
9456 return true;
9457
9458 return false;
9459 }
9460
9461 // Return false if builtIn by itself doesn't force this qualifier to be an output qualifier.
isOutputBuiltIn(const TQualifier & qualifier) const9462 bool HlslParseContext::isOutputBuiltIn(const TQualifier& qualifier) const
9463 {
9464 switch (qualifier.builtIn) {
9465 case EbvPosition:
9466 case EbvPointSize:
9467 case EbvClipVertex:
9468 case EbvClipDistance:
9469 case EbvCullDistance:
9470 return language != EShLangFragment && language != EShLangCompute;
9471 case EbvFragDepth:
9472 case EbvFragDepthGreater:
9473 case EbvFragDepthLesser:
9474 case EbvSampleMask:
9475 return language == EShLangFragment;
9476 case EbvLayer:
9477 case EbvViewportIndex:
9478 return language == EShLangGeometry || language == EShLangVertex;
9479 case EbvPrimitiveId:
9480 return language == EShLangGeometry;
9481 case EbvTessLevelInner:
9482 case EbvTessLevelOuter:
9483 return language == EShLangTessControl;
9484 default:
9485 return false;
9486 }
9487 }
9488
9489 // Return true if there are decorations to preserve for output-like storage.
hasOutput(const TQualifier & qualifier) const9490 bool HlslParseContext::hasOutput(const TQualifier& qualifier) const
9491 {
9492 if (qualifier.hasAnyLocation())
9493 return true;
9494
9495 if (language != EShLangFragment && language != EShLangCompute && qualifier.hasXfb())
9496 return true;
9497
9498 if (language == EShLangTessControl && qualifier.patch)
9499 return true;
9500
9501 if (language == EShLangGeometry && qualifier.hasStream())
9502 return true;
9503
9504 if (isOutputBuiltIn(qualifier))
9505 return true;
9506
9507 return false;
9508 }
9509
9510 // Make the IO decorations etc. be appropriate only for an input interface.
correctInput(TQualifier & qualifier)9511 void HlslParseContext::correctInput(TQualifier& qualifier)
9512 {
9513 clearUniform(qualifier);
9514 if (language == EShLangVertex)
9515 qualifier.clearInterstage();
9516 if (language != EShLangTessEvaluation)
9517 qualifier.patch = false;
9518 if (language != EShLangFragment) {
9519 qualifier.clearInterpolation();
9520 qualifier.sample = false;
9521 }
9522
9523 qualifier.clearStreamLayout();
9524 qualifier.clearXfbLayout();
9525
9526 if (! isInputBuiltIn(qualifier))
9527 qualifier.builtIn = EbvNone;
9528 }
9529
9530 // Make the IO decorations etc. be appropriate only for an output interface.
correctOutput(TQualifier & qualifier)9531 void HlslParseContext::correctOutput(TQualifier& qualifier)
9532 {
9533 clearUniform(qualifier);
9534 if (language == EShLangFragment)
9535 qualifier.clearInterstage();
9536 if (language != EShLangGeometry)
9537 qualifier.clearStreamLayout();
9538 if (language == EShLangFragment)
9539 qualifier.clearXfbLayout();
9540 if (language != EShLangTessControl)
9541 qualifier.patch = false;
9542
9543 switch (qualifier.builtIn) {
9544 case EbvFragDepth:
9545 intermediate.setDepthReplacing();
9546 intermediate.setDepth(EldAny);
9547 break;
9548 case EbvFragDepthGreater:
9549 intermediate.setDepthReplacing();
9550 intermediate.setDepth(EldGreater);
9551 qualifier.builtIn = EbvFragDepth;
9552 break;
9553 case EbvFragDepthLesser:
9554 intermediate.setDepthReplacing();
9555 intermediate.setDepth(EldLess);
9556 qualifier.builtIn = EbvFragDepth;
9557 break;
9558 default:
9559 break;
9560 }
9561
9562 if (! isOutputBuiltIn(qualifier))
9563 qualifier.builtIn = EbvNone;
9564 }
9565
9566 // Make the IO decorations etc. be appropriate only for uniform type interfaces.
correctUniform(TQualifier & qualifier)9567 void HlslParseContext::correctUniform(TQualifier& qualifier)
9568 {
9569 if (qualifier.declaredBuiltIn == EbvNone)
9570 qualifier.declaredBuiltIn = qualifier.builtIn;
9571
9572 qualifier.builtIn = EbvNone;
9573 qualifier.clearInterstage();
9574 qualifier.clearInterstageLayout();
9575 }
9576
9577 // Clear out all IO/Uniform stuff, so this has nothing to do with being an IO interface.
clearUniformInputOutput(TQualifier & qualifier)9578 void HlslParseContext::clearUniformInputOutput(TQualifier& qualifier)
9579 {
9580 clearUniform(qualifier);
9581 correctUniform(qualifier);
9582 }
9583
9584
9585 // Set texture return type. Returns success (not all types are valid).
setTextureReturnType(TSampler & sampler,const TType & retType,const TSourceLoc & loc)9586 bool HlslParseContext::setTextureReturnType(TSampler& sampler, const TType& retType, const TSourceLoc& loc)
9587 {
9588 // Seed the output with an invalid index. We will set it to a valid one if we can.
9589 sampler.structReturnIndex = TSampler::noReturnStruct;
9590
9591 // Arrays aren't supported.
9592 if (retType.isArray()) {
9593 error(loc, "Arrays not supported in texture template types", "", "");
9594 return false;
9595 }
9596
9597 // If return type is a vector, remember the vector size in the sampler, and return.
9598 if (retType.isVector() || retType.isScalar()) {
9599 sampler.vectorSize = retType.getVectorSize();
9600 return true;
9601 }
9602
9603 // If it wasn't a vector, it must be a struct meeting certain requirements. The requirements
9604 // are checked below: just check for struct-ness here.
9605 if (!retType.isStruct()) {
9606 error(loc, "Invalid texture template type", "", "");
9607 return false;
9608 }
9609
9610 // TODO: Subpass doesn't handle struct returns, due to some oddities with fn overloading.
9611 if (sampler.isSubpass()) {
9612 error(loc, "Unimplemented: structure template type in subpass input", "", "");
9613 return false;
9614 }
9615
9616 TTypeList* members = retType.getWritableStruct();
9617
9618 // Check for too many or not enough structure members.
9619 if (members->size() > 4 || members->size() == 0) {
9620 error(loc, "Invalid member count in texture template structure", "", "");
9621 return false;
9622 }
9623
9624 // Error checking: We must have <= 4 total components, all of the same basic type.
9625 unsigned totalComponents = 0;
9626 for (unsigned m = 0; m < members->size(); ++m) {
9627 // Check for bad member types
9628 if (!(*members)[m].type->isScalar() && !(*members)[m].type->isVector()) {
9629 error(loc, "Invalid texture template struct member type", "", "");
9630 return false;
9631 }
9632
9633 const unsigned memberVectorSize = (*members)[m].type->getVectorSize();
9634 totalComponents += memberVectorSize;
9635
9636 // too many total member components
9637 if (totalComponents > 4) {
9638 error(loc, "Too many components in texture template structure type", "", "");
9639 return false;
9640 }
9641
9642 // All members must be of a common basic type
9643 if ((*members)[m].type->getBasicType() != (*members)[0].type->getBasicType()) {
9644 error(loc, "Texture template structure members must same basic type", "", "");
9645 return false;
9646 }
9647 }
9648
9649 // If the structure in the return type already exists in the table, we'll use it. Otherwise, we'll make
9650 // a new entry. This is a linear search, but it hardly ever happens, and the list cannot be very large.
9651 for (unsigned int idx = 0; idx < textureReturnStruct.size(); ++idx) {
9652 if (textureReturnStruct[idx] == members) {
9653 sampler.structReturnIndex = idx;
9654 return true;
9655 }
9656 }
9657
9658 // It wasn't found as an existing entry. See if we have room for a new one.
9659 if (textureReturnStruct.size() >= TSampler::structReturnSlots) {
9660 error(loc, "Texture template struct return slots exceeded", "", "");
9661 return false;
9662 }
9663
9664 // Insert it in the vector that tracks struct return types.
9665 sampler.structReturnIndex = unsigned(textureReturnStruct.size());
9666 textureReturnStruct.push_back(members);
9667
9668 // Success!
9669 return true;
9670 }
9671
9672 // Return the sampler return type in retType.
getTextureReturnType(const TSampler & sampler,TType & retType) const9673 void HlslParseContext::getTextureReturnType(const TSampler& sampler, TType& retType) const
9674 {
9675 if (sampler.hasReturnStruct()) {
9676 assert(textureReturnStruct.size() >= sampler.structReturnIndex);
9677
9678 // We land here if the texture return is a structure.
9679 TTypeList* blockStruct = textureReturnStruct[sampler.structReturnIndex];
9680
9681 const TType resultType(blockStruct, "");
9682 retType.shallowCopy(resultType);
9683 } else {
9684 // We land here if the texture return is a vector or scalar.
9685 const TType resultType(sampler.type, EvqTemporary, sampler.getVectorSize());
9686 retType.shallowCopy(resultType);
9687 }
9688 }
9689
9690
9691 // Return a symbol for the tessellation linkage variable of the given TBuiltInVariable type
findTessLinkageSymbol(TBuiltInVariable biType) const9692 TIntermSymbol* HlslParseContext::findTessLinkageSymbol(TBuiltInVariable biType) const
9693 {
9694 const auto it = builtInTessLinkageSymbols.find(biType);
9695 if (it == builtInTessLinkageSymbols.end()) // if it wasn't declared by the user, return nullptr
9696 return nullptr;
9697
9698 return intermediate.addSymbol(*it->second->getAsVariable());
9699 }
9700
9701 // Find the patch constant function (issues error, returns nullptr if not found)
findPatchConstantFunction(const TSourceLoc & loc)9702 const TFunction* HlslParseContext::findPatchConstantFunction(const TSourceLoc& loc)
9703 {
9704 if (symbolTable.isFunctionNameVariable(patchConstantFunctionName)) {
9705 error(loc, "can't use variable in patch constant function", patchConstantFunctionName.c_str(), "");
9706 return nullptr;
9707 }
9708
9709 const TString mangledName = patchConstantFunctionName + "(";
9710
9711 // create list of PCF candidates
9712 TVector<const TFunction*> candidateList;
9713 bool builtIn;
9714 symbolTable.findFunctionNameList(mangledName, candidateList, builtIn);
9715
9716 // We have to have one and only one, or we don't know which to pick: the patchconstantfunc does not
9717 // allow any disambiguation of overloads.
9718 if (candidateList.empty()) {
9719 error(loc, "patch constant function not found", patchConstantFunctionName.c_str(), "");
9720 return nullptr;
9721 }
9722
9723 // Based on directed experiments, it appears that if there are overloaded patchconstantfunctions,
9724 // HLSL picks the last one in shader source order. Since that isn't yet implemented here, error
9725 // out if there is more than one candidate.
9726 if (candidateList.size() > 1) {
9727 error(loc, "ambiguous patch constant function", patchConstantFunctionName.c_str(), "");
9728 return nullptr;
9729 }
9730
9731 return candidateList[0];
9732 }
9733
9734 // Finalization step: Add patch constant function invocation
addPatchConstantInvocation()9735 void HlslParseContext::addPatchConstantInvocation()
9736 {
9737 TSourceLoc loc;
9738 loc.init();
9739
9740 // If there's no patch constant function, or we're not a HS, do nothing.
9741 if (patchConstantFunctionName.empty() || language != EShLangTessControl)
9742 return;
9743
9744 // Look for built-in variables in a function's parameter list.
9745 const auto findBuiltIns = [&](const TFunction& function, std::set<tInterstageIoData>& builtIns) {
9746 for (int p=0; p<function.getParamCount(); ++p) {
9747 TStorageQualifier storage = function[p].type->getQualifier().storage;
9748
9749 if (storage == EvqConstReadOnly) // treated identically to input
9750 storage = EvqIn;
9751
9752 if (function[p].getDeclaredBuiltIn() != EbvNone)
9753 builtIns.insert(HlslParseContext::tInterstageIoData(function[p].getDeclaredBuiltIn(), storage));
9754 else
9755 builtIns.insert(HlslParseContext::tInterstageIoData(function[p].type->getQualifier().builtIn, storage));
9756 }
9757 };
9758
9759 // If we synthesize a built-in interface variable, we must add it to the linkage.
9760 const auto addToLinkage = [&](const TType& type, const TString* name, TIntermSymbol** symbolNode) {
9761 if (name == nullptr) {
9762 error(loc, "unable to locate patch function parameter name", "", "");
9763 return;
9764 } else {
9765 TVariable& variable = *new TVariable(name, type);
9766 if (! symbolTable.insert(variable)) {
9767 error(loc, "unable to declare patch constant function interface variable", name->c_str(), "");
9768 return;
9769 }
9770
9771 globalQualifierFix(loc, variable.getWritableType().getQualifier());
9772
9773 if (symbolNode != nullptr)
9774 *symbolNode = intermediate.addSymbol(variable);
9775
9776 trackLinkage(variable);
9777 }
9778 };
9779
9780 const auto isOutputPatch = [](TFunction& patchConstantFunction, int param) {
9781 const TType& type = *patchConstantFunction[param].type;
9782 const TBuiltInVariable biType = patchConstantFunction[param].getDeclaredBuiltIn();
9783
9784 return type.isSizedArray() && biType == EbvOutputPatch;
9785 };
9786
9787 // We will perform these steps. Each is in a scoped block for separation: they could
9788 // become separate functions to make addPatchConstantInvocation shorter.
9789 //
9790 // 1. Union the interfaces, and create built-ins for anything present in the PCF and
9791 // declared as a built-in variable that isn't present in the entry point's signature.
9792 //
9793 // 2. Synthesizes a call to the patchconstfunction using built-in variables from either main,
9794 // or the ones we created. Matching is based on built-in type. We may use synthesized
9795 // variables from (1) above.
9796 //
9797 // 2B: Synthesize per control point invocations of wrapped entry point if the PCF requires them.
9798 //
9799 // 3. Create a return sequence: copy the return value (if any) from the PCF to a
9800 // (non-sanitized) output variable. In case this may involve multiple copies, such as for
9801 // an arrayed variable, a temporary copy of the PCF output is created to avoid multiple
9802 // indirections into a complex R-value coming from the call to the PCF.
9803 //
9804 // 4. Create a barrier.
9805 //
9806 // 5/5B. Call the PCF inside an if test for (invocation id == 0).
9807
9808 TFunction* patchConstantFunctionPtr = const_cast<TFunction*>(findPatchConstantFunction(loc));
9809
9810 if (patchConstantFunctionPtr == nullptr)
9811 return;
9812
9813 TFunction& patchConstantFunction = *patchConstantFunctionPtr;
9814
9815 const int pcfParamCount = patchConstantFunction.getParamCount();
9816 TIntermSymbol* invocationIdSym = findTessLinkageSymbol(EbvInvocationId);
9817 TIntermSequence& epBodySeq = entryPointFunctionBody->getAsAggregate()->getSequence();
9818
9819 int outPatchParam = -1; // -1 means there isn't one.
9820
9821 // ================ Step 1A: Union Interfaces ================
9822 // Our patch constant function.
9823 {
9824 std::set<tInterstageIoData> pcfBuiltIns; // patch constant function built-ins
9825 std::set<tInterstageIoData> epfBuiltIns; // entry point function built-ins
9826
9827 assert(entryPointFunction);
9828 assert(entryPointFunctionBody);
9829
9830 findBuiltIns(patchConstantFunction, pcfBuiltIns);
9831 findBuiltIns(*entryPointFunction, epfBuiltIns);
9832
9833 // Find the set of built-ins in the PCF that are not present in the entry point.
9834 std::set<tInterstageIoData> notInEntryPoint;
9835
9836 notInEntryPoint = pcfBuiltIns;
9837
9838 // std::set_difference not usable on unordered containers
9839 for (auto bi = epfBuiltIns.begin(); bi != epfBuiltIns.end(); ++bi)
9840 notInEntryPoint.erase(*bi);
9841
9842 // Now we'll add those to the entry and to the linkage.
9843 for (int p=0; p<pcfParamCount; ++p) {
9844 const TBuiltInVariable biType = patchConstantFunction[p].getDeclaredBuiltIn();
9845 TStorageQualifier storage = patchConstantFunction[p].type->getQualifier().storage;
9846
9847 // Track whether there is an output patch param
9848 if (isOutputPatch(patchConstantFunction, p)) {
9849 if (outPatchParam >= 0) {
9850 // Presently we only support one per ctrl pt input.
9851 error(loc, "unimplemented: multiple output patches in patch constant function", "", "");
9852 return;
9853 }
9854 outPatchParam = p;
9855 }
9856
9857 if (biType != EbvNone) {
9858 TType* paramType = patchConstantFunction[p].type->clone();
9859
9860 if (storage == EvqConstReadOnly) // treated identically to input
9861 storage = EvqIn;
9862
9863 // Presently, the only non-built-in we support is InputPatch, which is treated as
9864 // a pseudo-built-in.
9865 if (biType == EbvInputPatch) {
9866 builtInTessLinkageSymbols[biType] = inputPatch;
9867 } else if (biType == EbvOutputPatch) {
9868 // Nothing...
9869 } else {
9870 // Use the original declaration type for the linkage
9871 paramType->getQualifier().builtIn = biType;
9872 if (biType == EbvTessLevelInner || biType == EbvTessLevelOuter)
9873 paramType->getQualifier().patch = true;
9874
9875 if (notInEntryPoint.count(tInterstageIoData(biType, storage)) == 1)
9876 addToLinkage(*paramType, patchConstantFunction[p].name, nullptr);
9877 }
9878 }
9879 }
9880
9881 // If we didn't find it because the shader made one, add our own.
9882 if (invocationIdSym == nullptr) {
9883 TType invocationIdType(EbtUint, EvqIn, 1);
9884 TString* invocationIdName = NewPoolTString("InvocationId");
9885 invocationIdType.getQualifier().builtIn = EbvInvocationId;
9886 addToLinkage(invocationIdType, invocationIdName, &invocationIdSym);
9887 }
9888
9889 assert(invocationIdSym);
9890 }
9891
9892 TIntermTyped* pcfArguments = nullptr;
9893 TVariable* perCtrlPtVar = nullptr;
9894
9895 // ================ Step 1B: Argument synthesis ================
9896 // Create pcfArguments for synthesis of patchconstantfunction invocation
9897 {
9898 for (int p=0; p<pcfParamCount; ++p) {
9899 TIntermTyped* inputArg = nullptr;
9900
9901 if (p == outPatchParam) {
9902 if (perCtrlPtVar == nullptr) {
9903 perCtrlPtVar = makeInternalVariable(*patchConstantFunction[outPatchParam].name,
9904 *patchConstantFunction[outPatchParam].type);
9905
9906 perCtrlPtVar->getWritableType().getQualifier().makeTemporary();
9907 }
9908 inputArg = intermediate.addSymbol(*perCtrlPtVar, loc);
9909 } else {
9910 // find which built-in it is
9911 const TBuiltInVariable biType = patchConstantFunction[p].getDeclaredBuiltIn();
9912
9913 if (biType == EbvInputPatch && inputPatch == nullptr) {
9914 error(loc, "unimplemented: PCF input patch without entry point input patch parameter", "", "");
9915 return;
9916 }
9917
9918 inputArg = findTessLinkageSymbol(biType);
9919
9920 if (inputArg == nullptr) {
9921 error(loc, "unable to find patch constant function built-in variable", "", "");
9922 return;
9923 }
9924 }
9925
9926 if (pcfParamCount == 1)
9927 pcfArguments = inputArg;
9928 else
9929 pcfArguments = intermediate.growAggregate(pcfArguments, inputArg);
9930 }
9931 }
9932
9933 // ================ Step 2: Synthesize call to PCF ================
9934 TIntermAggregate* pcfCallSequence = nullptr;
9935 TIntermTyped* pcfCall = nullptr;
9936
9937 {
9938 // Create a function call to the patchconstantfunction
9939 if (pcfArguments)
9940 addInputArgumentConversions(patchConstantFunction, pcfArguments);
9941
9942 // Synthetic call.
9943 pcfCall = intermediate.setAggregateOperator(pcfArguments, EOpFunctionCall, patchConstantFunction.getType(), loc);
9944 pcfCall->getAsAggregate()->setUserDefined();
9945 pcfCall->getAsAggregate()->setName(patchConstantFunction.getMangledName());
9946 intermediate.addToCallGraph(infoSink, intermediate.getEntryPointMangledName().c_str(),
9947 patchConstantFunction.getMangledName());
9948
9949 if (pcfCall->getAsAggregate()) {
9950 TQualifierList& qualifierList = pcfCall->getAsAggregate()->getQualifierList();
9951 for (int i = 0; i < patchConstantFunction.getParamCount(); ++i) {
9952 TStorageQualifier qual = patchConstantFunction[i].type->getQualifier().storage;
9953 qualifierList.push_back(qual);
9954 }
9955 pcfCall = addOutputArgumentConversions(patchConstantFunction, *pcfCall->getAsOperator());
9956 }
9957 }
9958
9959 // ================ Step 2B: Per Control Point synthesis ================
9960 // If there is per control point data, we must either emulate that with multiple
9961 // invocations of the entry point to build up an array, or (TODO:) use a yet
9962 // unavailable extension to look across the SIMD lanes. This is the former
9963 // as a placeholder for the latter.
9964 if (outPatchParam >= 0) {
9965 // We must introduce a local temp variable of the type wanted by the PCF input.
9966 const int arraySize = patchConstantFunction[outPatchParam].type->getOuterArraySize();
9967
9968 if (entryPointFunction->getType().getBasicType() == EbtVoid) {
9969 error(loc, "entry point must return a value for use with patch constant function", "", "");
9970 return;
9971 }
9972
9973 // Create calls to wrapped main to fill in the array. We will substitute fixed values
9974 // of invocation ID when calling the wrapped main.
9975
9976 // This is the type of the each member of the per ctrl point array.
9977 const TType derefType(perCtrlPtVar->getType(), 0);
9978
9979 for (int cpt = 0; cpt < arraySize; ++cpt) {
9980 // TODO: improve. substr(1) here is to avoid the '@' that was grafted on but isn't in the symtab
9981 // for this function.
9982 const TString origName = entryPointFunction->getName().substr(1);
9983 TFunction callee(&origName, TType(EbtVoid));
9984 TIntermTyped* callingArgs = nullptr;
9985
9986 for (int i = 0; i < entryPointFunction->getParamCount(); i++) {
9987 TParameter& param = (*entryPointFunction)[i];
9988 TType& paramType = *param.type;
9989
9990 if (paramType.getQualifier().isParamOutput()) {
9991 error(loc, "unimplemented: entry point outputs in patch constant function invocation", "", "");
9992 return;
9993 }
9994
9995 if (paramType.getQualifier().isParamInput()) {
9996 TIntermTyped* arg = nullptr;
9997 if ((*entryPointFunction)[i].getDeclaredBuiltIn() == EbvInvocationId) {
9998 // substitute invocation ID with the array element ID
9999 arg = intermediate.addConstantUnion(cpt, loc);
10000 } else {
10001 TVariable* argVar = makeInternalVariable(*param.name, *param.type);
10002 argVar->getWritableType().getQualifier().makeTemporary();
10003 arg = intermediate.addSymbol(*argVar);
10004 }
10005
10006 handleFunctionArgument(&callee, callingArgs, arg);
10007 }
10008 }
10009
10010 // Call and assign to per ctrl point variable
10011 currentCaller = intermediate.getEntryPointMangledName().c_str();
10012 TIntermTyped* callReturn = handleFunctionCall(loc, &callee, callingArgs);
10013 TIntermTyped* index = intermediate.addConstantUnion(cpt, loc);
10014 TIntermSymbol* perCtrlPtSym = intermediate.addSymbol(*perCtrlPtVar, loc);
10015 TIntermTyped* element = intermediate.addIndex(EOpIndexDirect, perCtrlPtSym, index, loc);
10016 element->setType(derefType);
10017 element->setLoc(loc);
10018
10019 pcfCallSequence = intermediate.growAggregate(pcfCallSequence,
10020 handleAssign(loc, EOpAssign, element, callReturn));
10021 }
10022 }
10023
10024 // ================ Step 3: Create return Sequence ================
10025 // Return sequence: copy PCF result to a temporary, then to shader output variable.
10026 if (pcfCall->getBasicType() != EbtVoid) {
10027 const TType* retType = &patchConstantFunction.getType(); // return type from the PCF
10028 TType outType; // output type that goes with the return type.
10029 outType.shallowCopy(*retType);
10030
10031 // substitute the output type
10032 const auto newLists = ioTypeMap.find(retType->getStruct());
10033 if (newLists != ioTypeMap.end())
10034 outType.setStruct(newLists->second.output);
10035
10036 // Substitute the top level type's built-in type
10037 if (patchConstantFunction.getDeclaredBuiltInType() != EbvNone)
10038 outType.getQualifier().builtIn = patchConstantFunction.getDeclaredBuiltInType();
10039
10040 outType.getQualifier().patch = true; // make it a per-patch variable
10041
10042 TVariable* pcfOutput = makeInternalVariable("@patchConstantOutput", outType);
10043 pcfOutput->getWritableType().getQualifier().storage = EvqVaryingOut;
10044
10045 if (pcfOutput->getType().isStruct())
10046 flatten(*pcfOutput, false);
10047
10048 assignToInterface(*pcfOutput);
10049
10050 TIntermSymbol* pcfOutputSym = intermediate.addSymbol(*pcfOutput, loc);
10051
10052 // The call to the PCF is a complex R-value: we want to store it in a temp to avoid
10053 // repeated calls to the PCF:
10054 TVariable* pcfCallResult = makeInternalVariable("@patchConstantResult", *retType);
10055 pcfCallResult->getWritableType().getQualifier().makeTemporary();
10056
10057 TIntermSymbol* pcfResultVar = intermediate.addSymbol(*pcfCallResult, loc);
10058 TIntermNode* pcfResultAssign = handleAssign(loc, EOpAssign, pcfResultVar, pcfCall);
10059 TIntermNode* pcfResultToOut = handleAssign(loc, EOpAssign, pcfOutputSym,
10060 intermediate.addSymbol(*pcfCallResult, loc));
10061
10062 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfResultAssign);
10063 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfResultToOut);
10064 } else {
10065 pcfCallSequence = intermediate.growAggregate(pcfCallSequence, pcfCall);
10066 }
10067
10068 // ================ Step 4: Barrier ================
10069 TIntermTyped* barrier = new TIntermAggregate(EOpBarrier);
10070 barrier->setLoc(loc);
10071 barrier->setType(TType(EbtVoid));
10072 epBodySeq.insert(epBodySeq.end(), barrier);
10073
10074 // ================ Step 5: Test on invocation ID ================
10075 TIntermTyped* zero = intermediate.addConstantUnion(0, loc, true);
10076 TIntermTyped* cmp = intermediate.addBinaryNode(EOpEqual, invocationIdSym, zero, loc, TType(EbtBool));
10077
10078
10079 // ================ Step 5B: Create if statement on Invocation ID == 0 ================
10080 intermediate.setAggregateOperator(pcfCallSequence, EOpSequence, TType(EbtVoid), loc);
10081 TIntermTyped* invocationIdTest = new TIntermSelection(cmp, pcfCallSequence, nullptr);
10082 invocationIdTest->setLoc(loc);
10083
10084 // add our test sequence before the return.
10085 epBodySeq.insert(epBodySeq.end(), invocationIdTest);
10086 }
10087
10088 // Finalization step: remove unused buffer blocks from linkage (we don't know until the
10089 // shader is entirely compiled).
10090 // Preserve order of remaining symbols.
removeUnusedStructBufferCounters()10091 void HlslParseContext::removeUnusedStructBufferCounters()
10092 {
10093 const auto endIt = std::remove_if(linkageSymbols.begin(), linkageSymbols.end(),
10094 [this](const TSymbol* sym) {
10095 const auto sbcIt = structBufferCounter.find(sym->getName());
10096 return sbcIt != structBufferCounter.end() && !sbcIt->second;
10097 });
10098
10099 linkageSymbols.erase(endIt, linkageSymbols.end());
10100 }
10101
10102 // Finalization step: patch texture shadow modes to match samplers they were combined with
fixTextureShadowModes()10103 void HlslParseContext::fixTextureShadowModes()
10104 {
10105 for (auto symbol = linkageSymbols.begin(); symbol != linkageSymbols.end(); ++symbol) {
10106 TSampler& sampler = (*symbol)->getWritableType().getSampler();
10107
10108 if (sampler.isTexture()) {
10109 const auto shadowMode = textureShadowVariant.find((*symbol)->getUniqueId());
10110 if (shadowMode != textureShadowVariant.end()) {
10111
10112 if (shadowMode->second->overloaded())
10113 // Texture needs legalization if it's been seen with both shadow and non-shadow modes.
10114 intermediate.setNeedsLegalization();
10115
10116 sampler.shadow = shadowMode->second->isShadowId((*symbol)->getUniqueId());
10117 }
10118 }
10119 }
10120 }
10121
10122 // Finalization step: patch append methods to use proper stream output, which isn't known until
10123 // main is parsed, which could happen after the append method is parsed.
finalizeAppendMethods()10124 void HlslParseContext::finalizeAppendMethods()
10125 {
10126 TSourceLoc loc;
10127 loc.init();
10128
10129 // Nothing to do: bypass test for valid stream output.
10130 if (gsAppends.empty())
10131 return;
10132
10133 if (gsStreamOutput == nullptr) {
10134 error(loc, "unable to find output symbol for Append()", "", "");
10135 return;
10136 }
10137
10138 // Patch append sequences, now that we know the stream output symbol.
10139 for (auto append = gsAppends.begin(); append != gsAppends.end(); ++append) {
10140 append->node->getSequence()[0] =
10141 handleAssign(append->loc, EOpAssign,
10142 intermediate.addSymbol(*gsStreamOutput, append->loc),
10143 append->node->getSequence()[0]->getAsTyped());
10144 }
10145 }
10146
10147 // post-processing
finish()10148 void HlslParseContext::finish()
10149 {
10150 // Error check: There was a dangling .mips operator. These are not nested constructs in the grammar, so
10151 // cannot be detected there. This is not strictly needed in a non-validating parser; it's just helpful.
10152 if (! mipsOperatorMipArg.empty()) {
10153 error(mipsOperatorMipArg.back().loc, "unterminated mips operator:", "", "");
10154 }
10155
10156 removeUnusedStructBufferCounters();
10157 addPatchConstantInvocation();
10158 fixTextureShadowModes();
10159 finalizeAppendMethods();
10160
10161 // Communicate out (esp. for command line) that we formed AST that will make
10162 // illegal AST SPIR-V and it needs transforms to legalize it.
10163 if (intermediate.needsLegalization() && (messages & EShMsgHlslLegalization))
10164 infoSink.info << "WARNING: AST will form illegal SPIR-V; need to transform to legalize";
10165
10166 TParseContextBase::finish();
10167 }
10168
10169 } // end namespace glslang
10170