Home | History | Annotate | Download | only in src
      1 // Copyright 2015 The Shaderc Authors. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 #include "libshaderc_util/compiler.h"
     16 
     17 #include <cstdint>
     18 #include <tuple>
     19 
     20 #include "libshaderc_util/format.h"
     21 #include "libshaderc_util/io.h"
     22 #include "libshaderc_util/message.h"
     23 #include "libshaderc_util/resources.h"
     24 #include "libshaderc_util/shader_stage.h"
     25 #include "libshaderc_util/spirv_tools_wrapper.h"
     26 #include "libshaderc_util/string_piece.h"
     27 #include "libshaderc_util/version_profile.h"
     28 
     29 #include "SPIRV/GlslangToSpv.h"
     30 
     31 namespace {
     32 using shaderc_util::string_piece;
     33 
     34 // For use with glslang parsing calls.
     35 const bool kNotForwardCompatible = false;
     36 
     37 // Returns true if #line directive sets the line number for the next line in the
     38 // given version and profile.
     39 inline bool LineDirectiveIsForNextLine(int version, EProfile profile) {
     40   return profile == EEsProfile || version >= 330;
     41 }
     42 
     43 // Returns a #line directive whose arguments are line and filename.
     44 inline std::string GetLineDirective(int line, const string_piece& filename) {
     45   return "#line " + std::to_string(line) + " \"" + filename.str() + "\"\n";
     46 }
     47 
     48 // Given a canonicalized #line directive (starting exactly with "#line", using
     49 // single spaces to separate different components, and having an optional
     50 // newline at the end), returns the line number and string name/number. If no
     51 // string name/number is provided, the second element in the returned pair is an
     52 // empty string_piece. Behavior is undefined if the directive parameter is not a
     53 // canonicalized #line directive.
     54 std::pair<int, string_piece> DecodeLineDirective(string_piece directive) {
     55   const string_piece kLineDirective = "#line ";
     56   assert(directive.starts_with(kLineDirective));
     57   directive = directive.substr(kLineDirective.size());
     58 
     59   const int line = std::atoi(directive.data());
     60   const size_t space_loc = directive.find_first_of(' ');
     61   if (space_loc == string_piece::npos) return std::make_pair(line, "");
     62 
     63   directive = directive.substr(space_loc);
     64   directive = directive.strip("\" \n");
     65   return std::make_pair(line, directive);
     66 }
     67 }  // anonymous namespace
     68 
     69 namespace shaderc_util {
     70 std::tuple<bool, std::vector<uint32_t>, size_t> Compiler::Compile(
     71     const string_piece& input_source_string, EShLanguage forced_shader_stage,
     72     const std::string& error_tag,
     73     const std::function<EShLanguage(std::ostream* error_stream,
     74                                     const string_piece& error_tag)>&
     75         stage_callback,
     76     CountingIncluder& includer, OutputType output_type,
     77     std::ostream* error_stream, size_t* total_warnings, size_t* total_errors,
     78     GlslInitializer* initializer) const {
     79   // Compilation results to be returned:
     80   // Initialize the result tuple as a failed compilation. In error cases, we
     81   // should return result_tuple directly without setting its members.
     82   auto result_tuple =
     83       std::make_tuple(false, std::vector<uint32_t>(), (size_t)0u);
     84   // Get the reference of the members of the result tuple. We should set their
     85   // values for succeeded compilation before returning the result tuple.
     86   bool& succeeded = std::get<0>(result_tuple);
     87   std::vector<uint32_t>& compilation_output_data = std::get<1>(result_tuple);
     88   size_t& compilation_output_data_size_in_bytes = std::get<2>(result_tuple);
     89 
     90   auto token = initializer->Acquire();
     91   EShLanguage used_shader_stage = forced_shader_stage;
     92   const std::string macro_definitions =
     93       shaderc_util::format(predefined_macros_, "#define ", " ", "\n");
     94   const std::string pound_extension =
     95       "#extension GL_GOOGLE_include_directive : enable\n";
     96   const std::string preamble = macro_definitions + pound_extension;
     97 
     98   std::string preprocessed_shader;
     99 
    100   // If only preprocessing, we definitely need to preprocess. Otherwise, if
    101   // we don't know the stage until now, we need the preprocessed shader to
    102   // deduce the shader stage.
    103   if (output_type == OutputType::PreprocessedText ||
    104       used_shader_stage == EShLangCount) {
    105     bool success;
    106     std::string glslang_errors;
    107     std::tie(success, preprocessed_shader, glslang_errors) =
    108         PreprocessShader(error_tag, input_source_string, preamble, includer);
    109 
    110     success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
    111                                    /* suppress_warnings = */ true,
    112                                    glslang_errors.c_str(), total_warnings,
    113                                    total_errors);
    114     if (!success) return result_tuple;
    115     // Because of the behavior change of the #line directive, the #line
    116     // directive introducing each file's content must use the syntax for the
    117     // specified version. So we need to probe this shader's version and
    118     // profile.
    119     int version;
    120     EProfile profile;
    121     std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
    122     const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
    123 
    124     preprocessed_shader =
    125         CleanupPreamble(preprocessed_shader, error_tag, pound_extension,
    126                         includer.num_include_directives(), is_for_next_line);
    127 
    128     if (output_type == OutputType::PreprocessedText) {
    129       // Set the values of the result tuple.
    130       succeeded = true;
    131       compilation_output_data = ConvertStringToVector(preprocessed_shader);
    132       compilation_output_data_size_in_bytes = preprocessed_shader.size();
    133       return result_tuple;
    134     } else if (used_shader_stage == EShLangCount) {
    135       std::string errors;
    136       std::tie(used_shader_stage, errors) =
    137           GetShaderStageFromSourceCode(error_tag, preprocessed_shader);
    138       if (!errors.empty()) {
    139         *error_stream << errors;
    140         return result_tuple;
    141       }
    142       if (used_shader_stage == EShLangCount) {
    143         if ((used_shader_stage = stage_callback(error_stream, error_tag)) ==
    144             EShLangCount) {
    145           return result_tuple;
    146         }
    147       }
    148     }
    149   }
    150 
    151   // Parsing requires its own Glslang symbol tables.
    152   glslang::TShader shader(used_shader_stage);
    153   const char* shader_strings = input_source_string.data();
    154   const int shader_lengths = static_cast<int>(input_source_string.size());
    155   const char* string_names = error_tag.c_str();
    156   shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
    157                                        &string_names, 1);
    158   shader.setPreamble(preamble.c_str());
    159 
    160   // TODO(dneto): Generate source-level debug info if requested.
    161   bool success =
    162       shader.parse(&shaderc_util::kDefaultTBuiltInResource, default_version_,
    163                    default_profile_, force_version_profile_,
    164                    kNotForwardCompatible, message_rules_, includer);
    165 
    166   success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
    167                                  suppress_warnings_, shader.getInfoLog(),
    168                                  total_warnings, total_errors);
    169   if (!success) return result_tuple;
    170 
    171   glslang::TProgram program;
    172   program.addShader(&shader);
    173   success = program.link(EShMsgDefault);
    174   success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
    175                                  suppress_warnings_, program.getInfoLog(),
    176                                  total_warnings, total_errors);
    177   if (!success) return result_tuple;
    178 
    179   // 'spirv' is an alias for the compilation_output_data. This alias is added
    180   // to
    181   // serve as an input for the call to DissassemblyBinary.
    182   std::vector<uint32_t>& spirv = compilation_output_data;
    183   // Note the call to GlslangToSpv also populates compilation_output_data.
    184   glslang::GlslangToSpv(*program.getIntermediate(used_shader_stage), spirv);
    185   if (output_type == OutputType::SpirvAssemblyText) {
    186     std::string text_or_error;
    187     if (!SpirvToolsDisassemble(spirv, &text_or_error)) {
    188       *error_stream << "shaderc: internal error: compilation succeeded but "
    189                        "failed to disassemble: "
    190                     << text_or_error << "\n";
    191       return result_tuple;
    192     }
    193     succeeded = true;
    194     compilation_output_data = ConvertStringToVector(text_or_error);
    195     compilation_output_data_size_in_bytes = text_or_error.size();
    196     return result_tuple;
    197   } else {
    198     succeeded = true;
    199     // Note compilation_output_data is already populated in GlslangToSpv().
    200     compilation_output_data_size_in_bytes = spirv.size() * sizeof(spirv[0]);
    201     return result_tuple;
    202   }
    203 }
    204 
    205 void Compiler::AddMacroDefinition(const char* macro, size_t macro_length,
    206                                   const char* definition,
    207                                   size_t definition_length) {
    208   predefined_macros_[std::string(macro, macro_length)] =
    209       definition ? std::string(definition, definition_length) : "";
    210 }
    211 
    212 void Compiler::SetMessageRules(EShMessages rules) { message_rules_ = rules; }
    213 
    214 EShMessages Compiler::GetMessageRules() const { return message_rules_; }
    215 
    216 void Compiler::SetForcedVersionProfile(int version, EProfile profile) {
    217   default_version_ = version;
    218   default_profile_ = profile;
    219   force_version_profile_ = true;
    220 }
    221 
    222 void Compiler::SetWarningsAsErrors() { warnings_as_errors_ = true; }
    223 
    224 void Compiler::SetGenerateDebugInfo() { generate_debug_info_ = true; }
    225 
    226 void Compiler::SetSuppressWarnings() { suppress_warnings_ = true; }
    227 
    228 std::tuple<bool, std::string, std::string> Compiler::PreprocessShader(
    229     const std::string& error_tag, const string_piece& shader_source,
    230     const string_piece& shader_preamble, CountingIncluder& includer) const {
    231   // The stage does not matter for preprocessing.
    232   glslang::TShader shader(EShLangVertex);
    233   const char* shader_strings = shader_source.data();
    234   const int shader_lengths = static_cast<int>(shader_source.size());
    235   const char* string_names = error_tag.c_str();
    236   shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
    237                                        &string_names, 1);
    238   shader.setPreamble(shader_preamble.data());
    239 
    240   // The preprocessor might be sensitive to the target environment.
    241   // So combine the existing rules with the just-give-me-preprocessor-output
    242   // flag.
    243   const auto rules =
    244       static_cast<EShMessages>(EShMsgOnlyPreprocessor | message_rules_);
    245 
    246   std::string preprocessed_shader;
    247   const bool success = shader.preprocess(
    248       &shaderc_util::kDefaultTBuiltInResource, default_version_,
    249       default_profile_, force_version_profile_, kNotForwardCompatible, rules,
    250       &preprocessed_shader, includer);
    251 
    252   if (success) {
    253     return std::make_tuple(true, preprocessed_shader, shader.getInfoLog());
    254   }
    255   return std::make_tuple(false, "", shader.getInfoLog());
    256 }
    257 
    258 std::string Compiler::CleanupPreamble(const string_piece& preprocessed_shader,
    259                                       const string_piece& error_tag,
    260                                       const string_piece& pound_extension,
    261                                       int num_include_directives,
    262                                       bool is_for_next_line) const {
    263   // Those #define directives in preamble will become empty lines after
    264   // preprocessing. We also injected an #extension directive to turn on #include
    265   // directive support. In the original preprocessing output from glslang, it
    266   // appears before the user source string. We need to do proper adjustment:
    267   // * Remove empty lines generated from #define directives in preamble.
    268   // * If there is no #include directive in the source code, we do not need to
    269   //   output the injected #extension directive. Otherwise,
    270   // * If there exists a #version directive in the source code, it should be
    271   //   placed at the first line. Its original line will be filled with an empty
    272   //   line as placeholder to maintain the code structure.
    273 
    274   const std::vector<string_piece> lines =
    275       preprocessed_shader.get_fields('\n', /* keep_delimiter = */ true);
    276 
    277   std::ostringstream output_stream;
    278 
    279   size_t pound_extension_index = lines.size();
    280   size_t pound_version_index = lines.size();
    281   for (size_t i = 0; i < lines.size(); ++i) {
    282     if (lines[i] == pound_extension) {
    283       pound_extension_index = std::min(i, pound_extension_index);
    284     } else if (lines[i].starts_with("#version")) {
    285       // In a preprocessed shader, directives are in a canonical format, so we
    286       // can confidently compare to '#version' verbatim, without worrying about
    287       // whitespace.
    288       pound_version_index = i;
    289       if (num_include_directives > 0) output_stream << lines[i];
    290       break;
    291     }
    292   }
    293   // We know that #extension directive exists and appears before #version
    294   // directive (if any).
    295   assert(pound_extension_index < lines.size());
    296 
    297   for (size_t i = 0; i < pound_extension_index; ++i) {
    298     // All empty lines before the #line directive we injected are generated by
    299     // preprocessing preamble. Do not output them.
    300     if (lines[i].strip_whitespace().empty()) continue;
    301     output_stream << lines[i];
    302   }
    303 
    304   if (num_include_directives > 0) {
    305     output_stream << pound_extension;
    306     // Also output a #line directive for the main file.
    307     output_stream << GetLineDirective(is_for_next_line, error_tag);
    308   }
    309 
    310   for (size_t i = pound_extension_index + 1; i < lines.size(); ++i) {
    311     if (i == pound_version_index) {
    312       if (num_include_directives > 0) {
    313         output_stream << "\n";
    314       } else {
    315         output_stream << lines[i];
    316       }
    317     } else {
    318       output_stream << lines[i];
    319     }
    320   }
    321 
    322   return output_stream.str();
    323 }
    324 
    325 std::pair<EShLanguage, std::string> Compiler::GetShaderStageFromSourceCode(
    326     string_piece filename, const std::string& preprocessed_shader) const {
    327   const string_piece kPragmaShaderStageDirective = "#pragma shader_stage";
    328   const string_piece kLineDirective = "#line";
    329 
    330   int version;
    331   EProfile profile;
    332   std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
    333   const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
    334 
    335   std::vector<string_piece> lines =
    336       string_piece(preprocessed_shader).get_fields('\n');
    337   // The filename, logical line number (which starts from 1 and is sensitive to
    338   // #line directives), and stage value for #pragma shader_stage() directives.
    339   std::vector<std::tuple<string_piece, size_t, string_piece>> stages;
    340   // The physical line numbers of the first #pragma shader_stage() line and
    341   // first non-preprocessing line in the preprocessed shader text.
    342   size_t first_pragma_physical_line = lines.size() + 1;
    343   size_t first_non_pp_line = lines.size() + 1;
    344 
    345   for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) {
    346     const string_piece current_line = lines[i].strip_whitespace();
    347     if (current_line.starts_with(kPragmaShaderStageDirective)) {
    348       const string_piece stage_value =
    349           current_line.substr(kPragmaShaderStageDirective.size()).strip("()");
    350       stages.emplace_back(filename, logical_line_no, stage_value);
    351       first_pragma_physical_line = std::min(first_pragma_physical_line, i + 1);
    352     } else if (!current_line.empty() && !current_line.starts_with("#")) {
    353       first_non_pp_line = std::min(first_non_pp_line, i + 1);
    354     }
    355 
    356     // Update logical line number for the next line.
    357     if (current_line.starts_with(kLineDirective)) {
    358       string_piece name;
    359       std::tie(logical_line_no, name) = DecodeLineDirective(current_line);
    360       if (!name.empty()) filename = name;
    361       // Note that for core profile, the meaning of #line changed since version
    362       // 330. The line number given by #line used to mean the logical line
    363       // number of the #line line. Now it means the logical line number of the
    364       // next line after the #line line.
    365       if (!is_for_next_line) ++logical_line_no;
    366     } else {
    367       ++logical_line_no;
    368     }
    369   }
    370   if (stages.empty()) return std::make_pair(EShLangCount, "");
    371 
    372   std::string error_message;
    373 
    374   const string_piece& first_pragma_filename = std::get<0>(stages[0]);
    375   const std::string first_pragma_line = std::to_string(std::get<1>(stages[0]));
    376   const string_piece& first_pragma_stage = std::get<2>(stages[0]);
    377 
    378   if (first_pragma_physical_line > first_non_pp_line) {
    379     error_message += first_pragma_filename.str() + ":" + first_pragma_line +
    380                      ": error: '#pragma': the first 'shader_stage' #pragma "
    381                      "must appear before any non-preprocessing code\n";
    382   }
    383 
    384   EShLanguage stage = MapStageNameToLanguage(first_pragma_stage);
    385   if (stage == EShLangCount) {
    386     error_message +=
    387         first_pragma_filename.str() + ":" + first_pragma_line +
    388         ": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" +
    389         first_pragma_stage.str() + "'\n";
    390   }
    391 
    392   for (size_t i = 1; i < stages.size(); ++i) {
    393     const string_piece& current_stage = std::get<2>(stages[i]);
    394     if (current_stage != first_pragma_stage) {
    395       const string_piece& current_filename = std::get<0>(stages[i]);
    396       const std::string current_line = std::to_string(std::get<1>(stages[i]));
    397       error_message += current_filename.str() + ":" + current_line +
    398                        ": error: '#pragma': conflicting stages for "
    399                        "'shader_stage' #pragma: '" +
    400                        current_stage.str() + "' (was '" +
    401                        first_pragma_stage.str() + "' at " +
    402                        first_pragma_filename.str() + ":" + first_pragma_line +
    403                        ")\n";
    404     }
    405   }
    406 
    407   return std::make_pair(error_message.empty() ? stage : EShLangCount,
    408                         error_message);
    409 }
    410 
    411 std::pair<int, EProfile> Compiler::DeduceVersionProfile(
    412     const std::string& preprocessed_shader) const {
    413   int version = default_version_;
    414   EProfile profile = default_profile_;
    415   if (!force_version_profile_) {
    416     std::tie(version, profile) =
    417         GetVersionProfileFromSourceCode(preprocessed_shader);
    418     if (version == 0 && profile == ENoProfile) {
    419       version = default_version_;
    420       profile = default_profile_;
    421     }
    422   }
    423   return std::make_pair(version, profile);
    424 }
    425 
    426 std::pair<int, EProfile> Compiler::GetVersionProfileFromSourceCode(
    427     const std::string& preprocessed_shader) const {
    428   string_piece pound_version = preprocessed_shader;
    429   const size_t pound_version_loc = pound_version.find("#version");
    430   if (pound_version_loc == string_piece::npos) {
    431     return std::make_pair(0, ENoProfile);
    432   }
    433   pound_version =
    434       pound_version.substr(pound_version_loc + std::strlen("#version"));
    435   pound_version = pound_version.substr(0, pound_version.find_first_of("\n"));
    436 
    437   std::string version_profile;
    438   for (const auto character : pound_version) {
    439     if (character != ' ') version_profile += character;
    440   }
    441 
    442   int version;
    443   EProfile profile;
    444   if (!ParseVersionProfile(version_profile, &version, &profile)) {
    445     return std::make_pair(0, ENoProfile);
    446   }
    447   return std::make_pair(version, profile);
    448 }
    449 
    450 // Converts a string to a vector of uint32_t by copying the content of a given
    451 // string to a vector<uint32_t> and returns it. Appends '\0' at the end if extra
    452 // bytes are required to complete the last element.
    453 std::vector<uint32_t> ConvertStringToVector(const std::string& str) {
    454   size_t num_bytes_str = str.size() + 1u;
    455   size_t vector_length =
    456       (num_bytes_str + sizeof(uint32_t) - 1) / sizeof(uint32_t);
    457   std::vector<uint32_t> result_vec(vector_length, 0);
    458   std::strncpy(reinterpret_cast<char*>(result_vec.data()), str.c_str(),
    459                str.size());
    460   return result_vec;
    461 }
    462 
    463 }  // namesapce shaderc_util
    464