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