4040#include " nabla.h"
4141#include < boost/exception/diagnostic_information.hpp>
4242#include < boost/wave/util/insert_whitespace_detection.hpp>
43+ #include < algorithm>
4344#include < optional>
4445
4546using namespace nbl ;
@@ -49,6 +50,22 @@ using namespace nbl::asset;
4950
5051namespace
5152{
53+ constexpr size_t kWaveFailureLogOutputTailMaxChars = 4096ull ;
54+ constexpr size_t kWaveFailureLogOutputTailMaxLines = 16ull ;
55+ constexpr size_t kWaveFailureLogTokenPreviewMaxChars = 160ull ;
56+
57+ struct WaveRenderProgress
58+ {
59+ core::string output;
60+ std::optional<nbl::wave::context::position_type> previousPosition = std::nullopt ;
61+ bool previousWasExplicitWhitespace = false ;
62+ size_t emittedTokenCount = 0ull ;
63+ std::string lastTokenFile;
64+ int lastTokenLine = 0 ;
65+ int lastTokenColumn = 0 ;
66+ std::string lastTokenValue;
67+ };
68+
5269std::string getLineSnippet (std::string_view text, const int lineNo)
5370{
5471 if (lineNo <= 0 )
@@ -85,8 +102,94 @@ std::string makeCaretLine(const int columnNo)
85102 return std::string (static_cast <size_t >(columnNo - 1 ), ' ' ) + ' ^' ;
86103}
87104
105+ size_t countLogicalLines (const std::string_view text)
106+ {
107+ if (text.empty ())
108+ return 0ull ;
109+
110+ size_t lines = static_cast <size_t >(std::count (text.begin (), text.end (), ' \n ' ));
111+ if (text.back () != ' \n ' )
112+ ++lines;
113+ return lines;
114+ }
115+
116+ std::string truncateEscapedPreview (std::string value, const size_t maxChars)
117+ {
118+ if (value.size () <= maxChars)
119+ return value;
120+
121+ if (maxChars <= 3ull )
122+ return value.substr (0ull , maxChars);
123+
124+ value.resize (maxChars - 3ull );
125+ value += " ..." ;
126+ return value;
127+ }
128+
129+ std::string indentMultiline (std::string_view text, std::string_view indent)
130+ {
131+ if (text.empty ())
132+ return {};
133+
134+ std::string out;
135+ out.reserve (text.size () + indent.size () * 4ull );
136+
137+ size_t lineStart = 0ull ;
138+ while (lineStart < text.size ())
139+ {
140+ out.append (indent.data (), indent.size ());
141+
142+ const auto lineEnd = text.find (' \n ' , lineStart);
143+ if (lineEnd == std::string_view::npos)
144+ {
145+ out.append (text.data () + lineStart, text.size () - lineStart);
146+ break ;
147+ }
148+
149+ out.append (text.data () + lineStart, lineEnd - lineStart + 1ull );
150+ lineStart = lineEnd + 1ull ;
151+ }
152+
153+ return out;
154+ }
155+
156+ std::string makeFailureLogOutputTail (std::string_view text)
157+ {
158+ if (text.empty ())
159+ return {};
160+
161+ size_t start = text.size ();
162+ size_t chars = 0ull ;
163+ size_t newlines = 0ull ;
164+ while (start > 0ull )
165+ {
166+ --start;
167+ ++chars;
168+ if (text[start] == ' \n ' )
169+ {
170+ ++newlines;
171+ if (newlines > kWaveFailureLogOutputTailMaxLines )
172+ {
173+ ++start;
174+ break ;
175+ }
176+ }
177+
178+ if (chars >= kWaveFailureLogOutputTailMaxChars )
179+ break ;
180+ }
181+
182+ std::string tail;
183+ if (start > 0ull )
184+ tail = " [truncated]\n " ;
185+ tail.append (text.data () + start, text.size () - start);
186+ return tail;
187+ }
188+
88189std::string makeWaveFailureContext (
89190 const nbl::asset::IShaderCompiler::SPreprocessorOptions& preprocessOptions,
191+ const nbl::wave::context& context,
192+ const WaveRenderProgress& renderProgress,
90193 const std::string_view code,
91194 const char * const phase,
92195 const std::string_view activeMacroDefinition,
@@ -97,14 +200,28 @@ std::string makeWaveFailureContext(
97200 std::ostringstream stream;
98201 stream << " Wave preprocessing context:" ;
99202 if (!preprocessOptions.sourceIdentifier .empty ())
100- stream << " \n source: " << preprocessOptions.sourceIdentifier ;
203+ stream << " \n source: " << nbl::wave::detail::escape_control_chars ( preprocessOptions.sourceIdentifier ) ;
101204 stream << " \n phase: " << phase;
102205 stream << " \n extra_define_count: " << preprocessOptions.extraDefines .size ();
206+ stream << " \n source_length_bytes: " << code.size ();
103207 stream << " \n source_has_trailing_newline: " << ((!code.empty () && code.back () == ' \n ' ) ? " yes" : " no" );
208+ stream << " \n include_depth: " << context.get_iteration_depth ();
209+ stream << " \n current_include_spelling: " << nbl::wave::detail::escape_control_chars (context.get_current_relative_filename ());
210+ stream << " \n current_directory: " << nbl::wave::detail::escape_control_chars (context.get_current_directory ().string ());
211+ #if BOOST_WAVE_SUPPORT_PRAGMA_ONCE != 0
212+ stream << " \n current_include_absolute_path: " << nbl::wave::detail::escape_control_chars (context.get_current_filename ());
213+ #endif
104214 if (!activeMacroDefinition.empty ())
105- stream << " \n active_macro_definition: " << activeMacroDefinition;
215+ stream << " \n active_macro_definition: " << nbl::wave::detail::escape_control_chars ( activeMacroDefinition) ;
106216 if (fileName && fileName[0 ] != ' \0 ' )
107- stream << " \n location: " << fileName << ' :' << lineNo << ' :' << columnNo;
217+ stream << " \n location: " << nbl::wave::detail::escape_control_chars (fileName) << ' :' << lineNo << ' :' << columnNo;
218+ stream << " \n emitted_output_bytes: " << renderProgress.output .size ();
219+ stream << " \n emitted_output_lines: " << countLogicalLines (renderProgress.output );
220+ stream << " \n emitted_token_count: " << renderProgress.emittedTokenCount ;
221+ if (!renderProgress.lastTokenFile .empty ())
222+ stream << " \n last_emitted_token_location: " << nbl::wave::detail::escape_control_chars (renderProgress.lastTokenFile ) << ' :' << renderProgress.lastTokenLine << ' :' << renderProgress.lastTokenColumn ;
223+ if (!renderProgress.lastTokenValue .empty ())
224+ stream << " \n last_emitted_token_value: " << truncateEscapedPreview (nbl::wave::detail::escape_control_chars (renderProgress.lastTokenValue ), kWaveFailureLogTokenPreviewMaxChars );
108225
109226 const auto snippet = getLineSnippet (code, lineNo);
110227 if (!snippet.empty () && fileName && preprocessOptions.sourceIdentifier == fileName)
@@ -115,6 +232,10 @@ std::string makeWaveFailureContext(
115232 stream << " \n " << caret;
116233 }
117234
235+ const auto outputTail = makeFailureLogOutputTail (renderProgress.output );
236+ if (!outputTail.empty ())
237+ stream << " \n partial_output_tail:\n " << indentMultiline (outputTail, " " );
238+
118239 return stream.str ();
119240}
120241
@@ -134,14 +255,11 @@ std::string tokenValueToString(const TokenT& token)
134255 return std::string (value.data (), value.size ());
135256}
136257
137- core::string renderPreprocessedOutput (nbl::wave::context& context)
258+ void renderPreprocessedOutput (nbl::wave::context& context, WaveRenderProgress& renderProgress )
138259{
139260 using namespace boost ::wave;
140261
141- core::string output;
142262 util::insert_whitespace_detection whitespace (true );
143- std::optional<nbl::wave::context::position_type> previousPosition = std::nullopt ;
144- bool previousWasExplicitWhitespace = false ;
145263
146264 for (auto it = context.begin (); it != context.end (); ++it)
147265 {
@@ -154,37 +272,41 @@ core::string renderPreprocessedOutput(nbl::wave::context& context)
154272 const auto & position = token.get_position ();
155273 const auto value = tokenValueToString (token);
156274
157- if (previousPosition.has_value () && !explicitWhitespace)
275+ if (renderProgress. previousPosition .has_value () && !explicitWhitespace)
158276 {
159277 const auto movedToNewLogicalLine =
160- position.get_file () != previousPosition->get_file () ||
161- position.get_line () > previousPosition->get_line ();
278+ position.get_file () != renderProgress. previousPosition ->get_file () ||
279+ position.get_line () > renderProgress. previousPosition ->get_line ();
162280
163281 if (movedToNewLogicalLine)
164282 {
165- if (output.empty () || output.back () != ' \n ' )
283+ if (renderProgress. output .empty () || renderProgress. output .back () != ' \n ' )
166284 {
167- output.push_back (' \n ' );
285+ renderProgress. output .push_back (' \n ' );
168286 whitespace.shift_tokens (T_NEWLINE);
169287 }
170288 }
171- else if (!previousWasExplicitWhitespace && whitespace.must_insert (id, value))
289+ else if (!renderProgress. previousWasExplicitWhitespace && whitespace.must_insert (id, value))
172290 {
173- if (output.empty () || (output.back () != ' ' && output.back () != ' \n ' && output.back () != ' \r ' && output.back () != ' \t ' ))
291+ if (renderProgress. output .empty () || (renderProgress. output .back () != ' ' && renderProgress. output .back () != ' \n ' && renderProgress. output .back () != ' \r ' && renderProgress. output .back () != ' \t ' ))
174292 {
175- output.push_back (' ' );
293+ renderProgress. output .push_back (' ' );
176294 whitespace.shift_tokens (T_SPACE);
177295 }
178296 }
179297 }
180298
181- output += value;
299+ renderProgress. output += value;
182300 whitespace.shift_tokens (id);
183- previousPosition = position;
184- previousWasExplicitWhitespace = explicitWhitespace;
301+ renderProgress.previousPosition = position;
302+ renderProgress.previousWasExplicitWhitespace = explicitWhitespace;
303+ const auto & file = position.get_file ();
304+ renderProgress.lastTokenFile .assign (file.c_str (), file.size ());
305+ renderProgress.lastTokenLine = position.get_line ();
306+ renderProgress.lastTokenColumn = position.get_column ();
307+ renderProgress.lastTokenValue = value;
308+ ++renderProgress.emittedTokenCount ;
185309 }
186-
187- return output;
188310}
189311
190312std::string preprocessImpl (
@@ -194,16 +316,26 @@ std::string preprocessImpl(
194316 std::function<void (nbl::wave::context&)> post)
195317{
196318 nbl::wave::context context (code.begin (), code.end (), preprocessOptions.sourceIdentifier .data (), { preprocessOptions });
197- context.set_caching (withCaching);
198- context.add_macro_definition (" __HLSL_VERSION" );
199- context.add_macro_definition (" __SPIRV_MAJOR_VERSION__=" + std::to_string (IShaderCompiler::getSpirvMajor (preprocessOptions.targetSpirvVersion )));
200- context.add_macro_definition (" __SPIRV_MINOR_VERSION__=" + std::to_string (IShaderCompiler::getSpirvMinor (preprocessOptions.targetSpirvVersion )));
201319
202- core::string resolvedString ;
320+ WaveRenderProgress renderProgress ;
203321 const char * phase = " registering built-in macros" ;
204322 std::string activeMacroDefinition;
323+ const auto reportPartialOutputOnFailure = [&]()
324+ {
325+ if (preprocessOptions.onPartialOutputOnFailure )
326+ preprocessOptions.onPartialOutputOnFailure (renderProgress.output );
327+ };
328+ const auto makeFailureContext = [&](const char * const fileName, const int lineNo, const int columnNo)
329+ {
330+ return makeWaveFailureContext (preprocessOptions, context, renderProgress, code, phase, activeMacroDefinition, fileName, lineNo, columnNo);
331+ };
205332 try
206333 {
334+ context.set_caching (withCaching);
335+ context.add_macro_definition (" __HLSL_VERSION" );
336+ context.add_macro_definition (" __SPIRV_MAJOR_VERSION__=" + std::to_string (IShaderCompiler::getSpirvMajor (preprocessOptions.targetSpirvVersion )));
337+ context.add_macro_definition (" __SPIRV_MINOR_VERSION__=" + std::to_string (IShaderCompiler::getSpirvMinor (preprocessOptions.targetSpirvVersion )));
338+
207339 phase = " registering extra macro definitions" ;
208340 for (const auto & define : preprocessOptions.extraDefines )
209341 {
@@ -215,39 +347,45 @@ std::string preprocessImpl(
215347 activeMacroDefinition.clear ();
216348
217349 phase = " expanding translation unit" ;
218- resolvedString = renderPreprocessedOutput (context);
350+ renderPreprocessedOutput (context, renderProgress );
219351 }
220352 catch (boost::wave::preprocess_exception& e)
221353 {
222- const auto failureContext = makeWaveFailureContext (preprocessOptions, code, phase, activeMacroDefinition, e.file_name (), e.line_no (), e.column_no ());
223- preprocessOptions.logger .log (" %s exception caught. %s [%s:%d:%d]\n %s" , system::ILogger::ELL_ERROR, e.what (), e.description (), e.file_name (), e.line_no (), e.column_no (), failureContext.c_str ());
354+ reportPartialOutputOnFailure ();
355+ const auto escapedDescription = nbl::wave::detail::escape_control_chars (e.description ());
356+ const auto escapedFileName = nbl::wave::detail::escape_control_chars (e.file_name ());
357+ const auto failureContext = makeFailureContext (e.file_name (), e.line_no (), e.column_no ());
358+ preprocessOptions.logger .log (" %s exception caught. %s [%s:%d:%d]\n %s" , system::ILogger::ELL_ERROR, e.what (), escapedDescription.c_str (), escapedFileName.c_str (), e.line_no (), e.column_no (), failureContext.c_str ());
224359 preprocessOptions.logger .log (" Boost diagnostic information:\n %s" , system::ILogger::ELL_ERROR, boost::diagnostic_information (e).c_str ());
225360 return {};
226361 }
227362 catch (const boost::exception& e)
228363 {
229- const auto failureContext = makeWaveFailureContext (preprocessOptions, code, phase, activeMacroDefinition, preprocessOptions.sourceIdentifier .data (), 0 , 0 );
364+ reportPartialOutputOnFailure ();
365+ const auto failureContext = makeFailureContext (preprocessOptions.sourceIdentifier .data (), 0 , 0 );
230366 preprocessOptions.logger .log (" Boost exception caught during Wave preprocessing.\n %s" , system::ILogger::ELL_ERROR, failureContext.c_str ());
231367 preprocessOptions.logger .log (" Boost diagnostic information:\n %s" , system::ILogger::ELL_ERROR, boost::diagnostic_information (e).c_str ());
232368 return {};
233369 }
234370 catch (const std::exception& e)
235371 {
236- const auto failureContext = makeWaveFailureContext (preprocessOptions, code, phase, activeMacroDefinition, preprocessOptions.sourceIdentifier .data (), 0 , 0 );
372+ reportPartialOutputOnFailure ();
373+ const auto failureContext = makeFailureContext (preprocessOptions.sourceIdentifier .data (), 0 , 0 );
237374 preprocessOptions.logger .log (" std::exception caught during Wave preprocessing. %s\n %s" , system::ILogger::ELL_ERROR, e.what (), failureContext.c_str ());
238375 return {};
239376 }
240377 catch (...)
241378 {
242- const auto failureContext = makeWaveFailureContext (preprocessOptions, code, phase, activeMacroDefinition, preprocessOptions.sourceIdentifier .data (), 0 , 0 );
379+ reportPartialOutputOnFailure ();
380+ const auto failureContext = makeFailureContext (preprocessOptions.sourceIdentifier .data (), 0 , 0 );
243381 preprocessOptions.logger .log (" Unknown exception caught during Wave preprocessing.\n %s" , system::ILogger::ELL_ERROR, failureContext.c_str ());
244382 preprocessOptions.logger .log (" Current exception diagnostic information:\n %s" , system::ILogger::ELL_ERROR, boost::current_exception_diagnostic_information ().c_str ());
245383 return {};
246384 }
247385
248386 post (context);
249387
250- return resolvedString ;
388+ return std::move (renderProgress. output ) ;
251389}
252390}
253391
0 commit comments