AzslcMain.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <CLI/CLI.hpp>
  9. #include "AzslcReflection.h"
  10. #include "AzslcEmitter.h"
  11. #include "AzslcHomonymVisitor.h"
  12. #include "Texture2DMSto2DCodeMutator.h"
  13. #include <cstddef>
  14. #include <filesystem>
  15. namespace StdFs = std::filesystem;
  16. // versioning
  17. // Correspond to the supported version of the AZSL language.
  18. #define AZSLC_MAJOR "1"
  19. // For large features or milestones. Minor version allows for breaking changes. Existing tests can change.
  20. #define AZSLC_MINOR "8" // last change: introduction of class inheritance
  21. // For small features or bug fixes. They cannot introduce breaking changes. Existing tests shouldn't change.
  22. #define AZSLC_REVISION "18" // last change: automatic option ranks
  23. namespace AZ::ShaderCompiler
  24. {
  25. DiagnosticStream verboseCout;
  26. DiagnosticStream warningCout(std::cerr);
  27. Endl azEndl;
  28. using MapOfStringViewToSetOfString = map<string_view, set<string>>;
  29. template <typename TypeClassFilterPredicate = std::nullptr_t>
  30. void VisitTokens(const antlr4::Recognizer* recognizer,
  31. MapOfStringViewToSetOfString& acceptedToken, set<string>& notTypes1, // out
  32. TypeClassFilterPredicate tcFilter = nullptr)
  33. {
  34. // loop over all keywords
  35. const auto& vocabulary = recognizer->getVocabulary();
  36. size_t maxToken = vocabulary.getMaxTokenType();
  37. for (size_t ii = 0; ii < maxToken; ++ii)
  38. {
  39. string token = vocabulary.getLiteralName(ii);
  40. token = Trim(token, "\"'"); // because AntlR gives us e.g "'float'"
  41. if (!token.empty()) // empty when there is a complex rule (not a straight unconditional keyword)
  42. {
  43. TypeClass tc = AnalyzeTypeClass(TentativeName{token});
  44. bool accept = true;
  45. if constexpr (!is_same_v<std::nullptr_t, std::remove_reference_t<decltype(tcFilter)>>)
  46. {
  47. accept = tcFilter(tc);
  48. }
  49. if (tc == TypeClass::IsNotType)
  50. {
  51. notTypes1.insert(token);
  52. }
  53. if (accept)
  54. {
  55. acceptedToken[TypeClass::ToStr(tc)].emplace(std::move(token));
  56. }
  57. }
  58. }
  59. }
  60. // out argument: classifiedTokens
  61. // filter: is a predicate for condition to check to pass registration
  62. template <typename FilterFunction>
  63. void ClassifyAllTokens(const azslLexer* lexer, MapOfStringViewToSetOfString& classifiedTokens, FilterFunction filter)
  64. {
  65. set<string> notTypes1;
  66. VisitTokens(lexer, classifiedTokens, notTypes1, filter);
  67. // now. because of names such as StructuredBuffer or matrix, need to have a generic appendix to mean something,
  68. // they will be classified as IsNotType. So we need to re-attempt analysis by appending something parseable.
  69. // get a scalar typename from the scalar class:
  70. string someScalar = *classifiedTokens[TypeClass::ToStr(TypeClass::Scalar)].begin();
  71. // now we'll use it to construct parseable generic type expressions
  72. enum class RetryStateMachine
  73. {
  74. OneTypeGenericParameter,
  75. OneTypeAndDimensionGenericParameters,
  76. OneTypeAndTwoDimensionsGenericParameters,
  77. End,
  78. };
  79. constexpr auto isNotTypeKey = TypeClass::ToStr(TypeClass::IsNotType);
  80. // completely delete all IsNotType from the classification, and redo it all over.
  81. set<string> notTypes = std::move(classifiedTokens[isNotTypeKey]);
  82. classifiedTokens.erase(isNotTypeKey);
  83. SetMerge(notTypes, notTypes1);
  84. for (const string& token : notTypes)
  85. {
  86. RetryStateMachine state = RetryStateMachine::OneTypeGenericParameter;
  87. TypeClass tc;
  88. do
  89. {
  90. string attemptedTypeName = token;
  91. switch (state)
  92. {
  93. case RetryStateMachine::OneTypeGenericParameter:
  94. attemptedTypeName += "<" + someScalar + ">";
  95. state = RetryStateMachine::OneTypeAndDimensionGenericParameters;
  96. break;
  97. case RetryStateMachine::OneTypeAndDimensionGenericParameters:
  98. attemptedTypeName += "<" + someScalar + ",1>";
  99. state = RetryStateMachine::OneTypeAndTwoDimensionsGenericParameters;
  100. break;
  101. case RetryStateMachine::OneTypeAndTwoDimensionsGenericParameters:
  102. attemptedTypeName += "<" + someScalar + "1,1>";
  103. state = RetryStateMachine::End;
  104. break;
  105. case RetryStateMachine::End:
  106. default:
  107. // save as is
  108. break;
  109. }
  110. tc = AnalyzeTypeClass(TentativeName{attemptedTypeName});
  111. } while (tc == TypeClass::IsNotType && state != RetryStateMachine::End);
  112. // re-register at the correct place (if passes filter):
  113. bool accept = true;
  114. if constexpr (!is_same_v<std::nullptr_t, std::remove_reference_t<decltype(filter)>>)
  115. {
  116. accept = filter(tc);
  117. }
  118. if (accept)
  119. {
  120. classifiedTokens[TypeClass::ToStr(tc)].insert(token);
  121. }
  122. } // end for
  123. }
  124. bool IsKeyword(const antlr4::Recognizer* r, antlr4::Token* token)
  125. {
  126. MapOfStringViewToSetOfString byTypeClass;
  127. set<string> notTypes;
  128. VisitTokens(r, byTypeClass, notTypes);
  129. bool notType = notTypes.find(token->getText()) != notTypes.end();
  130. bool notIdentifier = r->getVocabulary().getSymbolicName(token->getType()) != "Identifier";
  131. bool firstIsalpha(isalpha(token->getText()[0]));
  132. return notType && notIdentifier && firstIsalpha;
  133. }
  134. void DumpClassifiedTokensToYaml(const MapOfStringViewToSetOfString& classifiedTokens)
  135. {
  136. for (const auto& [category, tokens] : classifiedTokens)
  137. {
  138. std::cout << category << ":\n";
  139. for (const string& tokenName : tokens)
  140. {
  141. std::cout << " - \"" << tokenName << "\"\n";
  142. }
  143. }
  144. }
  145. // outputs information about language keywords related to predefined types
  146. void DumpPredefinedVocabulary(const azslLexer* lexer)
  147. {
  148. MapOfStringViewToSetOfString classifiedTokens;
  149. ClassifyAllTokens(lexer, classifiedTokens /*out*/, [](TypeClass tc) { return IsPredefinedType(tc); });
  150. DumpClassifiedTokensToYaml(classifiedTokens);
  151. }
  152. //! iterates on tokens and build the line number mapping (from preprocessor line directives)
  153. void ConstructLineMap(vector<std::unique_ptr<Token>>* allTokens, PreprocessorLineDirectiveFinder* lineFinder)
  154. {
  155. string lastNonEmptyFileName = lineFinder->m_physicalSourceFileName;
  156. for (auto& token : *allTokens) // auto& because each element is a unique_ptr we can't copy
  157. {
  158. if (token->getType() == azslLexer::LineDirective)
  159. {
  160. LineDirectiveInfo directiveInfo{ 0, 0 };
  161. const auto lineText = token->getText();
  162. // the sharp
  163. // | any whitespaces
  164. // | / optional line token
  165. // | | | decimal
  166. // custom raw string | | | | optional filename between quotes
  167. // delimiter ──┐ | | | | |
  168. std::regex lineRegex(R"__(#\s*(line\s+)?\s*(\d+)\s*("(.*)")?)__");
  169. auto matchBegin = std::sregex_iterator(lineText.begin(), lineText.end(), lineRegex);
  170. // there can be only 1 match, and it HAS to match since AntlR lexer already matched.
  171. auto& groups = *matchBegin; // 4 groups: [0] is the whole line. [1] is the first parenthesized group, [2] the 2nd etc
  172. directiveInfo.m_physicalTokenLine = token->getLine();
  173. directiveInfo.m_forcedLineNumber = std::atoi(groups[2].str().c_str());
  174. directiveInfo.m_containingFilename = groups[4];
  175. if (directiveInfo.m_containingFilename.empty())
  176. {
  177. // if we don't have a filename specified on the line, it means the last seen filename is still active.
  178. // storing it this way simplifies the lookup algorithm using this data.
  179. directiveInfo.m_containingFilename = lastNonEmptyFileName;
  180. }
  181. else
  182. {
  183. lastNonEmptyFileName = directiveInfo.m_containingFilename;
  184. }
  185. lineFinder->PushLineDirective(directiveInfo);
  186. }
  187. }
  188. if (lineFinder->m_lineMap.find(1) == lineFinder->m_lineMap.end())
  189. {
  190. // if we have no line directives on line 1, add one before the file's first line (at 0), that can always be found by Infimum
  191. lineFinder->PushLineDirective({0, 1, lineFinder->m_physicalSourceFileName});
  192. }
  193. }
  194. }
  195. namespace AZ::ShaderCompiler::Main
  196. {
  197. using namespace AZ::ShaderCompiler;
  198. /// This function will support the --ast option. It uses an AntlR facility and prettifies it.
  199. void PrintAst(tree::ParseTree* tree, azslParser& parser)
  200. {
  201. // not sure why wstring, but I'm going along the AntlR's doc example.
  202. std::wstring s = antlrcpp::s2ws(tree->toStringTree(&parser));
  203. // hopefully easy to read indentator
  204. std::wstring curindent = L"";
  205. for (wchar_t c : s)
  206. {
  207. if (c == L'(')
  208. {
  209. std::wcout << "\n";
  210. curindent += L" ";
  211. std::wcout << curindent << c;
  212. }
  213. else if (c == L')')
  214. {
  215. std::wcout << c << "\n";
  216. curindent = curindent.substr(0, std::max(2_sz, curindent.size()) - 2);
  217. std::wcout << curindent;
  218. }
  219. else
  220. {
  221. std::wcout << c;
  222. }
  223. }
  224. std::wcout << std::endl; // flush
  225. }
  226. /// this function supports the --visitsym option
  227. // @symbolMqn starting point of symbol homonyms graph discovery. Mqn: mangled qualified name
  228. void PrintVisitSymbol(IntermediateRepresentation& ir, string_view symbolMqn, RelationshipExtentFlag visitOptions)
  229. {
  230. IdAndKind* symbol = ir.GetIdAndKindInfo(QualifiedNameView{symbolMqn});
  231. if (!symbol)
  232. {
  233. std::cerr << "Error: symbol " << symbolMqn.data() << " not found. To list all symbols use --dumpsym option.\n";
  234. return;
  235. }
  236. std::cout << "Symbol found. kind: " << Kind::ToStr(ir.GetKind(symbol->first)) << ". Homonyms list:\n";
  237. HomonymVisitor hv{[&ir](QualifiedNameView qnv) { return ir.GetKindInfo({{qnv}}); }};
  238. hv(symbol->first,
  239. [](const Seenat &at, RelationshipExtent category)
  240. {
  241. std::cout << "- {categ: " << RelationshipExtent::ToStr(category)
  242. << ", id: " << Decorate("'", at.m_referredDefinition.GetName())
  243. << ", at: ':" << at.m_where.m_line << ":" << at.m_where.m_charPos + 1 << "'"
  244. << ", token#: " << at.m_where.m_focusedTokenId << "}\n";
  245. },
  246. visitOptions);
  247. }
  248. void ParseWarningLevel(const std::array<bool, Warn::EndEnumeratorSentinel_>& args, DiagnosticStream& warningConfig)
  249. {
  250. for (auto level : Warn::Enumerate{})
  251. {
  252. bool active = args[level];
  253. if (active)
  254. {
  255. if (level >= Warn::Wx)
  256. {
  257. warningConfig.SetAsErrorLevel(level);
  258. }
  259. else
  260. {
  261. warningConfig.SetRevealedWarningLevel(level);
  262. }
  263. }
  264. }
  265. }
  266. }
  267. namespace AZ
  268. {
  269. string_view GetFileLeafName(string_view path)
  270. {
  271. return Slice(path, path.find_last_of("/\\") + 1, -1);
  272. }
  273. inline void DoAsserts()
  274. {
  275. #ifndef NDEBUG
  276. using namespace AZ::Tests;
  277. DoAsserts2();
  278. DoAsserts4();
  279. DoAsserts5();
  280. DoAsserts6();
  281. #endif
  282. }
  283. }
  284. int main(int argc, const char* argv[])
  285. {
  286. using namespace AZ;
  287. using namespace AZ::ShaderCompiler::Main;
  288. CLI::App cli{ "Amazon Shader Language Compiler" };
  289. bool printVersion = false;
  290. cli.add_flag("--version", printVersion, "Prints version information");
  291. std::string inputFile;
  292. cli.add_option("(- | FILE)", inputFile, "Input file (pass - to read from stdin).");
  293. std::string output;
  294. cli.add_option("-o", output, "Output file (writes to stdout if omitted).");
  295. bool uniqueIdx = false;
  296. cli.add_flag("--unique-idx", uniqueIdx, "Use unique indices for all registers. e.g. b0, t0, u0, s0 becomes b0, t1, u2, s3. Use on platforms that don't differentiate registers by resource type.");
  297. bool cbBody = false;
  298. cli.add_flag("--cb-body", cbBody, "Emit ConstantBuffer body rather than using <T>.");
  299. bool rootSig = false;
  300. cli.add_flag("--root-sig", rootSig, "Emit RootSignature for parameter binding in the shader. --namespace must also be used to select a specific API.");
  301. int rootConst = 0;
  302. auto rootConstOpt = cli.add_option("--root-const", rootConst, "Maximum size in bytes of the root constants buffer.");
  303. bool padRootConst = false;
  304. cli.add_flag("--pad-root-const", padRootConst, "Automatically append padding data to the root constant CB to keep it aligned to a 16-byte boundary.");
  305. bool Zpc = false;
  306. cli.add_flag("--Zpc", Zpc, "Pack matrices in column-major order (default). Cannot be specified together with -Zpr.");
  307. bool Zpr = false;
  308. cli.add_flag("--Zpr", Zpr, "Pack matrices in row-major order. Cannot be specified together with -Zpc.");
  309. bool packDx12 = false;
  310. cli.add_flag("--pack-dx12", packDx12, "Pack buffers using strict DX12 packing rules. If not specified AZSLc will use relaxed packing rules.");
  311. bool packVulkan = false;
  312. cli.add_flag("--pack-vulkan", packVulkan, "Pack buffers using strict Vulkan packing rules (Vector-relaxed std140 for uniforms and std430 for storage buffers).");
  313. bool packOpenGL = false;
  314. cli.add_flag("--pack-opengl", packOpenGL, "Pack buffers using strict OpenGL packing rules (Vector-strict std140 for uniforms and std430 for storage buffers).");
  315. std::vector<std::string> namespaces;
  316. cli.add_option("--namespace", namespaces,
  317. "Activate an attribute namespace. May be used multiple times to activate multiple namespaces. "
  318. "Activating a namespace may also activate corresponding API-specific features, like dx for DirectX 12, vk for Vulkan, and mt for Metal.");
  319. bool ia = false;
  320. cli.add_flag("--ia", ia, "Output a list of vs entries with their Input Assembler layouts *and* a list of CS entries and their numthreads.");
  321. bool om = false;
  322. cli.add_flag("--om", om, "Output the Output Merger layout instead of the shader code.");
  323. bool srg = false;
  324. cli.add_flag("--srg", srg, "Output the Shader Resource Group layout instead of the shader code.");
  325. bool options = false;
  326. cli.add_flag("--options", options, "Output the list of available shader options for this shader.");
  327. bool dumpsym = false;
  328. cli.add_flag("--dumpsym", dumpsym, "Dump symbols.");
  329. bool syntax = false;
  330. cli.add_flag("--syntax", syntax, "Check syntax (no output means no complaints).");
  331. bool semantic = false;
  332. cli.add_flag("--semantic", semantic, "Check semantics (no output means no complaints).");
  333. bool ast = false;
  334. cli.add_flag("--ast", ast, "Output the abstract syntax tree.");
  335. bool bindingdep = false;
  336. cli.add_flag("--bindingdep", bindingdep, "Output binding dependencies (what entry points access what external resources).");
  337. std::string visitName;
  338. cli.add_option("--visitsym", visitName, "Output the locations of all relationships of the supplied symbol name.");
  339. bool full = false;
  340. cli.add_flag("--full", full, "Output the shader code, IA layout, OM layout, SRG layout, the list of available shader options, and the binding dependencies.");
  341. bool stripUnusedSrgs = false;
  342. cli.add_flag("--strip-unused-srgs", stripUnusedSrgs, "Strips unused SRGs.");
  343. bool noMS = false;
  344. cli.add_flag("--no-ms", noMS, "Transforms usage of Texture2DMS/Texture2DMSArray and related functions and semantics into plain Texture2D/Texture2DArray "
  345. "equivalents. This is useful for allowing shader authors to easily write AZSL code that can be compiled into alternatives to work with both a "
  346. "multisample render pipeline and a non-MS render pipeline.");
  347. bool noAlignmentValidation = false;
  348. cli.add_flag("--no-alignment-validation", noAlignmentValidation, "Skips checking for potential alignment issues related to differences between dxil and spirv."
  349. "By default, potential alignment discrepancies will fail compilation.");
  350. bool visitDirectReferences = false;
  351. cli.add_flag("-d", visitDirectReferences, "(Option of --visitsym) Visit direct references.");
  352. bool visitOverloadSet = false;
  353. cli.add_flag("-v", visitOverloadSet, "(Option of --visitsym) Visit overload-set.");
  354. bool visitFamily = false;
  355. cli.add_flag("-f", visitFamily, "(Option of --visitsym) Visit family.");
  356. bool visitRecursively = false;
  357. cli.add_flag("-r", visitRecursively, "(Option of --visitsym) Visit recursively.");
  358. bool listPredefined = false;
  359. cli.add_flag("--listpredefined", listPredefined, "Output a list of all predefined types in AZSLang.");
  360. int maxSpaces = std::numeric_limits<int>::max();
  361. auto maxSpacesOpt = cli.add_option("--max-spaces", maxSpaces, "Will choose register spaces that do not extend past this limit.");
  362. std::array<bool, Warn::EndEnumeratorSentinel_> warningOpts;
  363. for (const auto e : Warn::Enumerate{})
  364. {
  365. warningOpts[e] = false;
  366. }
  367. cli.add_flag("--W0", warningOpts[Warn::EnumType::W0], "Suppresses all warnings.");
  368. cli.add_flag("--W1", warningOpts[Warn::EnumType::W1], "Activate severe warnings (default).");
  369. cli.add_flag("--W2", warningOpts[Warn::EnumType::W2], "Activate warnings that may be significant.");
  370. cli.add_flag("--W3", warningOpts[Warn::EnumType::W3], "Activate low-confidence diagnostic warnings.");
  371. cli.add_flag("--Wx", warningOpts[Warn::EnumType::Wx], "Treat activated warnings as errors.");
  372. cli.add_flag("--Wx1", warningOpts[Warn::EnumType::Wx1], "Treat level-1 warnings as errors.");
  373. cli.add_flag("--Wx2", warningOpts[Warn::EnumType::Wx2], "Treat level-2 and below warnings as errors.");
  374. cli.add_flag("--Wx3", warningOpts[Warn::EnumType::Wx3], "Treat level-3 and below warnings as errors.");
  375. std::string minDescriptors;
  376. cli.add_option("--min-descriptors", minDescriptors, "Comma-separated list of limits corresponding to "
  377. "<set,space,sampler,texture,buffer> descriptors. Emits a warning if a count overshoots a limit. Use -1 to specify \"no limit\".");
  378. bool verbose = false;
  379. cli.add_flag("--verbose", verbose);
  380. DoAsserts();
  381. int processReturnCode = 0;
  382. try
  383. {
  384. CLI11_PARSE(cli, argc, argv);
  385. // Major.Minor.Revision
  386. auto versionString = string{"AZSL Compiler " AZSLC_MAJOR "." AZSLC_MINOR "." AZSLC_REVISION " "} + GetCurrentOsName().data();
  387. if (printVersion)
  388. {
  389. std::cout << versionString << std::endl;
  390. return 0;
  391. }
  392. if (listPredefined)
  393. {
  394. verboseCout.m_on = false;
  395. ANTLRInputStream is;
  396. azslLexer lexer{&is};
  397. DumpPredefinedVocabulary(&lexer);
  398. return 0;
  399. }
  400. verboseCout.m_on = verbose;
  401. bool useStdin = inputFile == '-';
  402. // we need to scope a stream object here, to be able to bind a polymorphic reference to it
  403. std::ifstream ifs;
  404. if (!useStdin)
  405. {
  406. ifs = std::ifstream{ inputFile }; // try to open as file
  407. }
  408. std::istream& in{useStdin ? std::cin : ifs};
  409. if (!in.good())
  410. {
  411. throw std::runtime_error("input file could not be opened");
  412. }
  413. if (rootSig && namespaces.empty())
  414. {
  415. throw std::runtime_error("--root-sig requested but no API was selected. Use a --namespace option as well.");
  416. }
  417. const string inputFileName = useStdin ? "" : inputFile;
  418. PreprocessorLineDirectiveFinder lineFinder;
  419. lineFinder.m_physicalSourceFileName = useStdin ? "stdin" : inputFile;
  420. // setup the line finder address on the exception system so that errors are canonically mutated to "virtual line space"
  421. AzslcException::s_lineFinder = &lineFinder;
  422. bool useOutputFile = !output.empty();
  423. const string outputFileName = output;
  424. ANTLRInputStream input(in);
  425. azslLexer lexer(&input);
  426. CommonTokenStream tokens(&lexer);
  427. IntermediateRepresentation ir(&lexer);
  428. auto allTokens = lexer.getAllTokens();
  429. if (lexer.getNumberOfSyntaxErrors() > 0)
  430. {
  431. throw std::runtime_error("syntax errors present");
  432. }
  433. ConstructLineMap(&allTokens, &lineFinder);
  434. lexer.reset();
  435. AzslParserEventListener azslParserEventListener;
  436. azslParser parser(&tokens);
  437. parser.removeErrorListeners();
  438. azslParserEventListener.m_isKeywordPredicate = IsKeyword;
  439. parser.addErrorListener(&azslParserEventListener);
  440. tree::ParseTree *tree = parser.compilationUnit();
  441. if (ast)
  442. {
  443. PrintAst(tree, parser);
  444. syntax = true; // ast print is a syntax only build.
  445. }
  446. if (parser.getNumberOfSyntaxErrors() > 0)
  447. {
  448. throw std::runtime_error("grammatic errors present");
  449. }
  450. if (syntax)
  451. { // if we are here with no exception then the syntax pass is valid.
  452. }
  453. else // continue with semantic, and later emission
  454. {
  455. if (!useStdin)
  456. {
  457. StdFs::path inSource{ inputFile };
  458. ir.m_metaData.m_insource = StdFs::absolute(inSource).lexically_normal().generic_string();
  459. }
  460. tree::ParseTreeWalker walker;
  461. Texture2DMSto2DCodeMutator texture2DMSto2DCodeMutator(&ir, &tokens);
  462. SemaCheckListener semanticListener{&ir};
  463. warningCout.m_onErrorCallback = [](string_view message) {
  464. throw AzslcException{WX_WARNINGS_AS_ERRORS, "as-error", string{message}};
  465. };
  466. ParseWarningLevel(warningOpts, warningCout);
  467. bool nonValidativeOptions[] = {full, ia, om, srg, options, dumpsym, ast, bindingdep, !visitName.empty(), stripUnusedSrgs};
  468. bool anyNonValidativeOption = std::any_of(std::begin(nonValidativeOptions), std::end(nonValidativeOptions), [](bool opt) { return opt; });
  469. semanticListener.m_silentPrintExtensions = !semantic || verbose; // print-extensions are useful for interested parties; but not normal operation.
  470. if (noMS)
  471. {
  472. semanticListener.m_functionCallMutator = &texture2DMSto2DCodeMutator;
  473. }
  474. warningCout.m_on = !anyNonValidativeOption; // warnings are interesting for emission, and explicit semantic check modes.
  475. // Enable attribute namespaces
  476. std::for_each(namespaces.begin(), namespaces.end(),
  477. [&](const string& space) { ir.AddAttributeNamespaceFilter(space); });
  478. UnboundedArraysValidator::Options unboundedArraysValidationOptions;
  479. unboundedArraysValidationOptions.m_useUniqueIndicesEnabled = uniqueIdx;
  480. if (*maxSpacesOpt)
  481. {
  482. unboundedArraysValidationOptions.m_maxSpaces = maxSpaces;
  483. }
  484. ir.m_sema.m_unboundedArraysValidator.SetOptions(unboundedArraysValidationOptions);
  485. // semantic logic and validation
  486. walker.walk(&semanticListener, tree);
  487. Options emitOptions;
  488. emitOptions.m_useUniqueIndices = uniqueIdx;
  489. emitOptions.m_emitConstantBufferBody = cbBody;
  490. emitOptions.m_emitRootSig = rootSig;
  491. emitOptions.m_padRootConstantCB = padRootConst;
  492. emitOptions.m_skipAlignmentValidation = noAlignmentValidation;
  493. if (*rootConstOpt)
  494. {
  495. emitOptions.m_rootConstantsMaxSize = rootConst;
  496. }
  497. if (!minDescriptors.empty())
  498. {
  499. sscanf(minDescriptors.c_str(), "%d,%d,%d,%d,%d",
  500. &emitOptions.m_minAvailableDescriptors.m_descriptorsTotal,
  501. &emitOptions.m_minAvailableDescriptors.m_spaces,
  502. &emitOptions.m_minAvailableDescriptors.m_samplers,
  503. &emitOptions.m_minAvailableDescriptors.m_textures,
  504. &emitOptions.m_minAvailableDescriptors.m_buffers);
  505. }
  506. if (*maxSpacesOpt)
  507. {
  508. emitOptions.m_maxSpaces = maxSpaces;
  509. }
  510. if (Zpc && Zpr)
  511. {
  512. throw std::runtime_error("Cannot specify --Zpr and --Zpc together, use --help to get usage information");
  513. }
  514. else if (Zpr)
  515. {
  516. emitOptions.m_forceMatrixRowMajor = true;
  517. emitOptions.m_forceEmitMajor = true;
  518. }
  519. else if (Zpc)
  520. {
  521. emitOptions.m_forceMatrixRowMajor = false; // Default
  522. emitOptions.m_forceEmitMajor = true;
  523. }
  524. if (packDx12)
  525. {
  526. emitOptions.m_packConstantBuffers = AZ::ShaderCompiler::Packing::Layout::DirectXPacking;
  527. emitOptions.m_packDataBuffers = AZ::ShaderCompiler::Packing::Layout::DirectXStoragePacking;
  528. }
  529. if (packVulkan)
  530. {
  531. emitOptions.m_packConstantBuffers = AZ::ShaderCompiler::Packing::Layout::RelaxedStd140Packing;
  532. emitOptions.m_packDataBuffers = AZ::ShaderCompiler::Packing::Layout::RelaxedStd430Packing;
  533. }
  534. if (packOpenGL)
  535. {
  536. emitOptions.m_packConstantBuffers = AZ::ShaderCompiler::Packing::Layout::StrictStd140Packing;
  537. emitOptions.m_packDataBuffers = AZ::ShaderCompiler::Packing::Layout::StrictStd430Packing;
  538. }
  539. // middle end logic
  540. MiddleEndConfiguration middleEndConfigration{emitOptions.m_rootConstantsMaxSize,
  541. emitOptions.m_packConstantBuffers,
  542. emitOptions.m_packDataBuffers,
  543. emitOptions.m_forceMatrixRowMajor,
  544. emitOptions.m_padRootConstantCB,
  545. emitOptions.m_skipAlignmentValidation};
  546. ir.MiddleEnd(middleEndConfigration, &lineFinder);
  547. if (noMS)
  548. {
  549. texture2DMSto2DCodeMutator.RunMiddleEndMutations();
  550. }
  551. // If ir fails to find any root members in the source, overwrite the m_numOfRootConstants to 0
  552. if (ir.m_rootConstantStructUID.m_name == "")
  553. {
  554. emitOptions.m_rootConstantsMaxSize = 0;
  555. }
  556. // intermediate state validation
  557. ir.Validate();
  558. if (stripUnusedSrgs)
  559. {
  560. ir.RemoveUnusedSrgs();
  561. }
  562. if (dumpsym)
  563. {
  564. DumpSymbols(ir);
  565. }
  566. else if (!visitName.empty())
  567. {
  568. using RE = RelationshipExtent;
  569. RelationshipExtentFlag visitOptions{RE::Self}; // at least self. + optional things as listed here-under
  570. array<pair<bool, RE::EnumType>, 4> optToRelation = {{{visitDirectReferences, RE::Reference},
  571. {visitFamily, RE::Family},
  572. {visitOverloadSet, RE::OverloadSet},
  573. {visitRecursively, RE::Recursive}}};
  574. for (auto &&possibleOption : optToRelation)
  575. {
  576. visitOptions |= possibleOption.first ? possibleOption.second : RE::EnumType(0);
  577. }
  578. PrintVisitSymbol(ir, visitName, visitOptions);
  579. }
  580. else if (!semantic) // do emission
  581. {
  582. verboseCout << "--Emission/Reflection--\n";
  583. std::ofstream mainOutFile;
  584. if (useOutputFile)
  585. {
  586. mainOutFile = std::ofstream(outputFileName);
  587. if (!mainOutFile.good())
  588. {
  589. throw std::runtime_error("output file could not be opened");
  590. }
  591. }
  592. std::ostream& out{useOutputFile ? mainOutFile : std::cout};
  593. CodeReflection reflecter{&ir, &tokens, out};
  594. // Lambda to create an output stream and perform an output action
  595. auto prepareOutputAndCall = [&](const string &suffix, std::function<void(CodeReflection&)> action)
  596. {
  597. string outputName;
  598. if (useOutputFile)
  599. {
  600. outputName = string(GetFileNameWithoutExtension(outputFileName));
  601. }
  602. else
  603. {
  604. outputName = string(GetFileNameWithoutExtension(inputFileName));
  605. }
  606. outputName = outputName + "." + suffix + ".json";
  607. std::ofstream outFile = std::ofstream(outputName);
  608. if (!outFile.good())
  609. {
  610. throw std::runtime_error("output file '" + outputName + "' could not be opened");
  611. }
  612. std::ostream& out{outFile};
  613. CodeReflection reflecter{&ir, &tokens, out};
  614. action(reflecter);
  615. };
  616. if (full)
  617. { // Combine the default emission and the ia, om, srg, options, bindingdep commands
  618. CodeEmitter emitter{&ir, &tokens, out, &lineFinder};
  619. if (noMS)
  620. {
  621. emitter.SetCodeMutator(&texture2DMSto2DCodeMutator);
  622. }
  623. emitter << "// HLSL emission by " << versionString << "\n";
  624. emitter.Run(emitOptions);
  625. prepareOutputAndCall("ia", [&](CodeReflection& r) { r.DumpShaderEntries(); });
  626. prepareOutputAndCall("om", [&](CodeReflection& r) { r.DumpOutputMergerLayout(); });
  627. prepareOutputAndCall("srg", [&](CodeReflection& r) { r.DumpSRGLayout(emitOptions, &lineFinder); });
  628. prepareOutputAndCall("options", [&](CodeReflection& r) { r.DumpVariantList(emitOptions); });
  629. prepareOutputAndCall("bindingdep", [&](CodeReflection& r) { r.DumpResourceBindingDependencies(emitOptions); });
  630. }
  631. else if (ia)
  632. { // Reflect the Input Assembler layout and the Compute shader entries
  633. reflecter.DumpShaderEntries();
  634. }
  635. else if (om)
  636. { // Reflect the Input Assembler layout
  637. reflecter.DumpOutputMergerLayout();
  638. }
  639. else if (srg)
  640. { // Reflect the Shader Resource Groups layout
  641. reflecter.DumpSRGLayout(emitOptions, &lineFinder);
  642. }
  643. else if (options)
  644. { // Reflect the list of available variant options for this shader
  645. reflecter.DumpVariantList(emitOptions);
  646. }
  647. else if (bindingdep)
  648. {
  649. reflecter.DumpResourceBindingDependencies(emitOptions);
  650. }
  651. else
  652. { // Emit the shader source code
  653. CodeEmitter emitter{&ir, &tokens, out, &lineFinder};
  654. if (noMS)
  655. {
  656. emitter.SetCodeMutator(&texture2DMSto2DCodeMutator);
  657. }
  658. emitter << "// HLSL emission by " << versionString << "\n";
  659. emitter.Run(emitOptions);
  660. }
  661. }
  662. }
  663. }
  664. catch (const exception& e)
  665. {
  666. OutputNestedAndException(e);
  667. processReturnCode = 1;
  668. }
  669. catch (...)
  670. {
  671. std::cerr << "Unknown exception" << std::endl;
  672. processReturnCode = 1;
  673. }
  674. return processReturnCode;
  675. }