xref: /aosp_15_r20/external/angle/third_party/spirv-tools/src/source/spirv_target_env.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 // Copyright (c) 2015-2016 The Khronos Group Inc.
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 "source/spirv_target_env.h"
16 
17 #include <array>
18 #include <cassert>
19 #include <cctype>
20 #include <cstring>
21 #include <string>
22 
23 #include "source/latest_version_spirv_header.h"
24 #include "source/spirv_constant.h"
25 #include "spirv-tools/libspirv.h"
26 
spvTargetEnvDescription(spv_target_env env)27 const char* spvTargetEnvDescription(spv_target_env env) {
28   switch (env) {
29     case SPV_ENV_UNIVERSAL_1_0:
30       return "SPIR-V 1.0";
31     case SPV_ENV_VULKAN_1_0:
32       return "SPIR-V 1.0 (under Vulkan 1.0 semantics)";
33     case SPV_ENV_UNIVERSAL_1_1:
34       return "SPIR-V 1.1";
35     case SPV_ENV_OPENCL_1_2:
36       return "SPIR-V 1.0 (under OpenCL 1.2 Full Profile semantics)";
37     case SPV_ENV_OPENCL_EMBEDDED_1_2:
38       return "SPIR-V 1.0 (under OpenCL 1.2 Embedded Profile semantics)";
39     case SPV_ENV_OPENCL_2_0:
40       return "SPIR-V 1.0 (under OpenCL 2.0 Full Profile semantics)";
41     case SPV_ENV_OPENCL_EMBEDDED_2_0:
42       return "SPIR-V 1.0 (under OpenCL 2.0 Embedded Profile semantics)";
43     case SPV_ENV_OPENCL_2_1:
44       return "SPIR-V 1.0 (under OpenCL 2.1 Full Profile semantics)";
45     case SPV_ENV_OPENCL_EMBEDDED_2_1:
46       return "SPIR-V 1.0 (under OpenCL 2.1 Embedded Profile semantics)";
47     case SPV_ENV_OPENCL_2_2:
48       return "SPIR-V 1.2 (under OpenCL 2.2 Full Profile semantics)";
49     case SPV_ENV_OPENCL_EMBEDDED_2_2:
50       return "SPIR-V 1.2 (under OpenCL 2.2 Embedded Profile semantics)";
51     case SPV_ENV_OPENGL_4_0:
52       return "SPIR-V 1.0 (under OpenGL 4.0 semantics)";
53     case SPV_ENV_OPENGL_4_1:
54       return "SPIR-V 1.0 (under OpenGL 4.1 semantics)";
55     case SPV_ENV_OPENGL_4_2:
56       return "SPIR-V 1.0 (under OpenGL 4.2 semantics)";
57     case SPV_ENV_OPENGL_4_3:
58       return "SPIR-V 1.0 (under OpenGL 4.3 semantics)";
59     case SPV_ENV_OPENGL_4_5:
60       return "SPIR-V 1.0 (under OpenGL 4.5 semantics)";
61     case SPV_ENV_UNIVERSAL_1_2:
62       return "SPIR-V 1.2";
63     case SPV_ENV_UNIVERSAL_1_3:
64       return "SPIR-V 1.3";
65     case SPV_ENV_VULKAN_1_1:
66       return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
67     case SPV_ENV_WEBGPU_0:
68       assert(false && "Deprecated target environment value.");
69       break;
70     case SPV_ENV_UNIVERSAL_1_4:
71       return "SPIR-V 1.4";
72     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
73       return "SPIR-V 1.4 (under Vulkan 1.1 semantics)";
74     case SPV_ENV_UNIVERSAL_1_5:
75       return "SPIR-V 1.5";
76     case SPV_ENV_VULKAN_1_2:
77       return "SPIR-V 1.5 (under Vulkan 1.2 semantics)";
78     case SPV_ENV_UNIVERSAL_1_6:
79       return "SPIR-V 1.6";
80     case SPV_ENV_VULKAN_1_3:
81       return "SPIR-V 1.6 (under Vulkan 1.3 semantics)";
82     case SPV_ENV_VULKAN_1_4:
83       return "SPIR-V 1.6 (under Vulkan 1.4 semantics)";
84     case SPV_ENV_MAX:
85       assert(false && "Invalid target environment value.");
86       break;
87   }
88   return "";
89 }
90 
spvVersionForTargetEnv(spv_target_env env)91 uint32_t spvVersionForTargetEnv(spv_target_env env) {
92   switch (env) {
93     case SPV_ENV_UNIVERSAL_1_0:
94     case SPV_ENV_VULKAN_1_0:
95     case SPV_ENV_OPENCL_1_2:
96     case SPV_ENV_OPENCL_EMBEDDED_1_2:
97     case SPV_ENV_OPENCL_2_0:
98     case SPV_ENV_OPENCL_EMBEDDED_2_0:
99     case SPV_ENV_OPENCL_2_1:
100     case SPV_ENV_OPENCL_EMBEDDED_2_1:
101     case SPV_ENV_OPENGL_4_0:
102     case SPV_ENV_OPENGL_4_1:
103     case SPV_ENV_OPENGL_4_2:
104     case SPV_ENV_OPENGL_4_3:
105     case SPV_ENV_OPENGL_4_5:
106       return SPV_SPIRV_VERSION_WORD(1, 0);
107     case SPV_ENV_UNIVERSAL_1_1:
108       return SPV_SPIRV_VERSION_WORD(1, 1);
109     case SPV_ENV_UNIVERSAL_1_2:
110     case SPV_ENV_OPENCL_2_2:
111     case SPV_ENV_OPENCL_EMBEDDED_2_2:
112       return SPV_SPIRV_VERSION_WORD(1, 2);
113     case SPV_ENV_UNIVERSAL_1_3:
114     case SPV_ENV_VULKAN_1_1:
115       return SPV_SPIRV_VERSION_WORD(1, 3);
116     case SPV_ENV_WEBGPU_0:
117       assert(false && "Deprecated target environment value.");
118       break;
119     case SPV_ENV_UNIVERSAL_1_4:
120     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
121       return SPV_SPIRV_VERSION_WORD(1, 4);
122     case SPV_ENV_UNIVERSAL_1_5:
123     case SPV_ENV_VULKAN_1_2:
124       return SPV_SPIRV_VERSION_WORD(1, 5);
125     case SPV_ENV_UNIVERSAL_1_6:
126     case SPV_ENV_VULKAN_1_3:
127     case SPV_ENV_VULKAN_1_4:
128       return SPV_SPIRV_VERSION_WORD(1, 6);
129     case SPV_ENV_MAX:
130       assert(false && "Invalid target environment value.");
131       break;
132   }
133   return SPV_SPIRV_VERSION_WORD(0, 0);
134 }
135 
136 // When a new SPIR-V version is released, update this table.
137 static_assert(spv::Version == 0x10600);
138 constexpr auto ordered_universal_envs = std::array<spv_target_env, 7>{
139     SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2,
140     SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5,
141     SPV_ENV_UNIVERSAL_1_6,
142 };
143 
144 // When a new SPIR-V version is released, update this table.
145 static_assert(spv::Version == 0x10600);
146 inline constexpr std::pair<const char*, spv_target_env> spvTargetEnvNameMap[] =
147     {
148         {"vulkan1.1spv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4},
149         {"vulkan1.0", SPV_ENV_VULKAN_1_0},
150         {"vulkan1.1", SPV_ENV_VULKAN_1_1},
151         {"vulkan1.2", SPV_ENV_VULKAN_1_2},
152         {"vulkan1.3", SPV_ENV_VULKAN_1_3},
153         {"vulkan1.4", SPV_ENV_VULKAN_1_4},
154         {"spv1.0", SPV_ENV_UNIVERSAL_1_0},
155         {"spv1.1", SPV_ENV_UNIVERSAL_1_1},
156         {"spv1.2", SPV_ENV_UNIVERSAL_1_2},
157         {"spv1.3", SPV_ENV_UNIVERSAL_1_3},
158         {"spv1.4", SPV_ENV_UNIVERSAL_1_4},
159         {"spv1.5", SPV_ENV_UNIVERSAL_1_5},
160         {"spv1.6", SPV_ENV_UNIVERSAL_1_6},
161         {"opencl1.2embedded", SPV_ENV_OPENCL_EMBEDDED_1_2},
162         {"opencl1.2", SPV_ENV_OPENCL_1_2},
163         {"opencl2.0embedded", SPV_ENV_OPENCL_EMBEDDED_2_0},
164         {"opencl2.0", SPV_ENV_OPENCL_2_0},
165         {"opencl2.1embedded", SPV_ENV_OPENCL_EMBEDDED_2_1},
166         {"opencl2.1", SPV_ENV_OPENCL_2_1},
167         {"opencl2.2embedded", SPV_ENV_OPENCL_EMBEDDED_2_2},
168         {"opencl2.2", SPV_ENV_OPENCL_2_2},
169         {"opengl4.0", SPV_ENV_OPENGL_4_0},
170         {"opengl4.1", SPV_ENV_OPENGL_4_1},
171         {"opengl4.2", SPV_ENV_OPENGL_4_2},
172         {"opengl4.3", SPV_ENV_OPENGL_4_3},
173         {"opengl4.5", SPV_ENV_OPENGL_4_5},
174 };
175 
spvParseTargetEnv(const char * s,spv_target_env * env)176 bool spvParseTargetEnv(const char* s, spv_target_env* env) {
177   auto match = [s](const char* b) {
178     return s && (0 == strncmp(s, b, strlen(b)));
179   };
180   for (auto& name_env : spvTargetEnvNameMap) {
181     if (match(name_env.first)) {
182       if (env) {
183         *env = name_env.second;
184       }
185       return true;
186     }
187   }
188   if (env) *env = SPV_ENV_UNIVERSAL_1_0;
189   return false;
190 }
191 
spvReadEnvironmentFromText(const std::vector<char> & text,spv_target_env * env)192 bool spvReadEnvironmentFromText(const std::vector<char>& text,
193                                 spv_target_env* env) {
194   // Version is expected to match "; Version: 1.X"
195   // Version string must occur in header, that is, initial lines of comments
196   // Once a non-comment line occurs, the header has ended
197   for (std::size_t i = 0; i < text.size(); ++i) {
198     char c = text[i];
199 
200     if (c == ';') {
201       // Try to match against the expected version string
202       constexpr const char* kVersionPrefix = "; Version: 1.";
203       constexpr const auto kPrefixLength = 13;
204       // 'minor_digit_pos' is the expected position of the version digit.
205       const auto minor_digit_pos = i + kPrefixLength;
206       if (minor_digit_pos >= text.size()) return false;
207 
208       // Match the prefix.
209       auto j = 1;
210       for (; j < kPrefixLength; ++j) {
211         if (kVersionPrefix[j] != text[i + j]) break;
212       }
213       // j will match the prefix length if all characters before matched
214       if (j == kPrefixLength) {
215         // This expects only one digit in the minor number.
216         static_assert(((spv::Version >> 8) & 0xff) < 10);
217         char minor = text[minor_digit_pos];
218         char next_char =
219             minor_digit_pos + 1 < text.size() ? text[minor_digit_pos + 1] : 0;
220         if (std::isdigit(minor) && !std::isdigit(next_char)) {
221           const auto index = minor - '0';
222           assert(index >= 0);
223           if (static_cast<size_t>(index) < ordered_universal_envs.size()) {
224             *env = ordered_universal_envs[index];
225             return true;
226           }
227         }
228       }
229 
230       // If no match, determine whether the header has ended (in which case,
231       // assumption has failed.)
232       // Skip until the next line.
233       i = j;
234       for (; i < text.size(); ++i) {
235         if (text[i] == '\n') break;
236       }
237     } else if (!std::isspace(c)) {
238       // Allow blanks, but end the search if we find something else.
239       break;
240     }
241   }
242   return false;
243 }
244 
245 #define VULKAN_VER(MAJOR, MINOR) ((MAJOR << 22) | (MINOR << 12))
246 #define SPIRV_VER(MAJOR, MINOR) ((MAJOR << 16) | (MINOR << 8))
247 
248 struct VulkanEnv {
249   spv_target_env vulkan_env;
250   uint32_t vulkan_ver;
251   uint32_t spirv_ver;
252 };
253 // Maps each Vulkan target environment enum to the Vulkan version, and the
254 // maximum supported SPIR-V version for that Vulkan environment.
255 // Keep this ordered from least capable to most capable.
256 static const VulkanEnv ordered_vulkan_envs[] = {
257     {SPV_ENV_VULKAN_1_0, VULKAN_VER(1, 0), SPIRV_VER(1, 0)},
258     {SPV_ENV_VULKAN_1_1, VULKAN_VER(1, 1), SPIRV_VER(1, 3)},
259     {SPV_ENV_VULKAN_1_1_SPIRV_1_4, VULKAN_VER(1, 1), SPIRV_VER(1, 4)},
260     {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)},
261     {SPV_ENV_VULKAN_1_3, VULKAN_VER(1, 3), SPIRV_VER(1, 6)},
262     {SPV_ENV_VULKAN_1_4, VULKAN_VER(1, 4), SPIRV_VER(1, 6)}};
263 
spvParseVulkanEnv(uint32_t vulkan_ver,uint32_t spirv_ver,spv_target_env * env)264 bool spvParseVulkanEnv(uint32_t vulkan_ver, uint32_t spirv_ver,
265                        spv_target_env* env) {
266   for (auto triple : ordered_vulkan_envs) {
267     if (triple.vulkan_ver >= vulkan_ver && triple.spirv_ver >= spirv_ver) {
268       *env = triple.vulkan_env;
269       return true;
270     }
271   }
272   return false;
273 }
274 
spvIsVulkanEnv(spv_target_env env)275 bool spvIsVulkanEnv(spv_target_env env) {
276   switch (env) {
277     case SPV_ENV_UNIVERSAL_1_0:
278     case SPV_ENV_OPENCL_1_2:
279     case SPV_ENV_OPENCL_EMBEDDED_1_2:
280     case SPV_ENV_OPENCL_2_0:
281     case SPV_ENV_OPENCL_EMBEDDED_2_0:
282     case SPV_ENV_OPENCL_2_1:
283     case SPV_ENV_OPENCL_EMBEDDED_2_1:
284     case SPV_ENV_OPENGL_4_0:
285     case SPV_ENV_OPENGL_4_1:
286     case SPV_ENV_OPENGL_4_2:
287     case SPV_ENV_OPENGL_4_3:
288     case SPV_ENV_OPENGL_4_5:
289     case SPV_ENV_UNIVERSAL_1_1:
290     case SPV_ENV_UNIVERSAL_1_2:
291     case SPV_ENV_OPENCL_2_2:
292     case SPV_ENV_OPENCL_EMBEDDED_2_2:
293     case SPV_ENV_UNIVERSAL_1_3:
294     case SPV_ENV_UNIVERSAL_1_4:
295     case SPV_ENV_UNIVERSAL_1_5:
296     case SPV_ENV_UNIVERSAL_1_6:
297       return false;
298     case SPV_ENV_VULKAN_1_0:
299     case SPV_ENV_VULKAN_1_1:
300     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
301     case SPV_ENV_VULKAN_1_2:
302     case SPV_ENV_VULKAN_1_3:
303     case SPV_ENV_VULKAN_1_4:
304       return true;
305     case SPV_ENV_WEBGPU_0:
306       assert(false && "Deprecated target environment value.");
307       break;
308     case SPV_ENV_MAX:
309       assert(false && "Invalid target environment value.");
310       break;
311   }
312   return false;
313 }
314 
spvIsOpenCLEnv(spv_target_env env)315 bool spvIsOpenCLEnv(spv_target_env env) {
316   switch (env) {
317     case SPV_ENV_UNIVERSAL_1_0:
318     case SPV_ENV_VULKAN_1_0:
319     case SPV_ENV_UNIVERSAL_1_1:
320     case SPV_ENV_OPENGL_4_0:
321     case SPV_ENV_OPENGL_4_1:
322     case SPV_ENV_OPENGL_4_2:
323     case SPV_ENV_OPENGL_4_3:
324     case SPV_ENV_OPENGL_4_5:
325     case SPV_ENV_UNIVERSAL_1_2:
326     case SPV_ENV_UNIVERSAL_1_3:
327     case SPV_ENV_VULKAN_1_1:
328     case SPV_ENV_UNIVERSAL_1_4:
329     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
330     case SPV_ENV_UNIVERSAL_1_5:
331     case SPV_ENV_VULKAN_1_2:
332     case SPV_ENV_UNIVERSAL_1_6:
333     case SPV_ENV_VULKAN_1_3:
334     case SPV_ENV_VULKAN_1_4:
335       return false;
336     case SPV_ENV_OPENCL_1_2:
337     case SPV_ENV_OPENCL_EMBEDDED_1_2:
338     case SPV_ENV_OPENCL_2_0:
339     case SPV_ENV_OPENCL_EMBEDDED_2_0:
340     case SPV_ENV_OPENCL_EMBEDDED_2_1:
341     case SPV_ENV_OPENCL_EMBEDDED_2_2:
342     case SPV_ENV_OPENCL_2_1:
343     case SPV_ENV_OPENCL_2_2:
344       return true;
345     case SPV_ENV_WEBGPU_0:
346       assert(false && "Deprecated target environment value.");
347       break;
348     case SPV_ENV_MAX:
349       assert(false && "Invalid target environment value.");
350       break;
351   }
352   return false;
353 }
354 
spvIsOpenGLEnv(spv_target_env env)355 bool spvIsOpenGLEnv(spv_target_env env) {
356   switch (env) {
357     case SPV_ENV_UNIVERSAL_1_0:
358     case SPV_ENV_VULKAN_1_0:
359     case SPV_ENV_UNIVERSAL_1_1:
360     case SPV_ENV_UNIVERSAL_1_2:
361     case SPV_ENV_UNIVERSAL_1_3:
362     case SPV_ENV_VULKAN_1_1:
363     case SPV_ENV_OPENCL_1_2:
364     case SPV_ENV_OPENCL_EMBEDDED_1_2:
365     case SPV_ENV_OPENCL_2_0:
366     case SPV_ENV_OPENCL_EMBEDDED_2_0:
367     case SPV_ENV_OPENCL_EMBEDDED_2_1:
368     case SPV_ENV_OPENCL_EMBEDDED_2_2:
369     case SPV_ENV_OPENCL_2_1:
370     case SPV_ENV_OPENCL_2_2:
371     case SPV_ENV_UNIVERSAL_1_4:
372     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
373     case SPV_ENV_UNIVERSAL_1_5:
374     case SPV_ENV_VULKAN_1_2:
375     case SPV_ENV_UNIVERSAL_1_6:
376     case SPV_ENV_VULKAN_1_3:
377     case SPV_ENV_VULKAN_1_4:
378       return false;
379     case SPV_ENV_OPENGL_4_0:
380     case SPV_ENV_OPENGL_4_1:
381     case SPV_ENV_OPENGL_4_2:
382     case SPV_ENV_OPENGL_4_3:
383     case SPV_ENV_OPENGL_4_5:
384       return true;
385     case SPV_ENV_WEBGPU_0:
386       assert(false && "Deprecated target environment value.");
387       break;
388     case SPV_ENV_MAX:
389       assert(false && "Invalid target environment value.");
390       break;
391   }
392   return false;
393 }
394 
spvIsValidEnv(spv_target_env env)395 bool spvIsValidEnv(spv_target_env env) {
396   switch (env) {
397     case SPV_ENV_UNIVERSAL_1_0:
398     case SPV_ENV_VULKAN_1_0:
399     case SPV_ENV_UNIVERSAL_1_1:
400     case SPV_ENV_UNIVERSAL_1_2:
401     case SPV_ENV_UNIVERSAL_1_3:
402     case SPV_ENV_VULKAN_1_1:
403     case SPV_ENV_OPENCL_1_2:
404     case SPV_ENV_OPENCL_EMBEDDED_1_2:
405     case SPV_ENV_OPENCL_2_0:
406     case SPV_ENV_OPENCL_EMBEDDED_2_0:
407     case SPV_ENV_OPENCL_EMBEDDED_2_1:
408     case SPV_ENV_OPENCL_EMBEDDED_2_2:
409     case SPV_ENV_OPENCL_2_1:
410     case SPV_ENV_OPENCL_2_2:
411     case SPV_ENV_UNIVERSAL_1_4:
412     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
413     case SPV_ENV_UNIVERSAL_1_5:
414     case SPV_ENV_VULKAN_1_2:
415     case SPV_ENV_UNIVERSAL_1_6:
416     case SPV_ENV_VULKAN_1_3:
417     case SPV_ENV_VULKAN_1_4:
418     case SPV_ENV_OPENGL_4_0:
419     case SPV_ENV_OPENGL_4_1:
420     case SPV_ENV_OPENGL_4_2:
421     case SPV_ENV_OPENGL_4_3:
422     case SPV_ENV_OPENGL_4_5:
423       return true;
424     case SPV_ENV_WEBGPU_0:
425     case SPV_ENV_MAX:
426       break;
427   }
428   return false;
429 }
430 
spvLogStringForEnv(spv_target_env env)431 std::string spvLogStringForEnv(spv_target_env env) {
432   switch (env) {
433     case SPV_ENV_OPENCL_1_2:
434     case SPV_ENV_OPENCL_2_0:
435     case SPV_ENV_OPENCL_2_1:
436     case SPV_ENV_OPENCL_2_2:
437     case SPV_ENV_OPENCL_EMBEDDED_1_2:
438     case SPV_ENV_OPENCL_EMBEDDED_2_0:
439     case SPV_ENV_OPENCL_EMBEDDED_2_1:
440     case SPV_ENV_OPENCL_EMBEDDED_2_2: {
441       return "OpenCL";
442     }
443     case SPV_ENV_OPENGL_4_0:
444     case SPV_ENV_OPENGL_4_1:
445     case SPV_ENV_OPENGL_4_2:
446     case SPV_ENV_OPENGL_4_3:
447     case SPV_ENV_OPENGL_4_5: {
448       return "OpenGL";
449     }
450     case SPV_ENV_VULKAN_1_0:
451     case SPV_ENV_VULKAN_1_1:
452     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
453     case SPV_ENV_VULKAN_1_2:
454     case SPV_ENV_VULKAN_1_3:
455     case SPV_ENV_VULKAN_1_4: {
456       return "Vulkan";
457     }
458     case SPV_ENV_UNIVERSAL_1_0:
459     case SPV_ENV_UNIVERSAL_1_1:
460     case SPV_ENV_UNIVERSAL_1_2:
461     case SPV_ENV_UNIVERSAL_1_3:
462     case SPV_ENV_UNIVERSAL_1_4:
463     case SPV_ENV_UNIVERSAL_1_5:
464     case SPV_ENV_UNIVERSAL_1_6: {
465       return "Universal";
466     }
467     case SPV_ENV_WEBGPU_0:
468       assert(false && "Deprecated target environment value.");
469       break;
470     case SPV_ENV_MAX:
471       assert(false && "Invalid target environment value.");
472       break;
473   }
474   return "Unknown";
475 }
476 
spvTargetEnvList(const int pad,const int wrap)477 std::string spvTargetEnvList(const int pad, const int wrap) {
478   std::string ret;
479   size_t max_line_len = wrap - pad;  // The first line isn't padded
480   std::string line;
481   std::string sep = "";
482 
483   for (auto& name_env : spvTargetEnvNameMap) {
484     std::string word = sep + name_env.first;
485     if (line.length() + word.length() > max_line_len) {
486       // Adding one word wouldn't fit, commit the line in progress and
487       // start a new one.
488       ret += line + "\n";
489       line.assign(pad, ' ');
490       // The first line is done. The max length now comprises the
491       // padding.
492       max_line_len = wrap;
493     }
494     line += word;
495     sep = "|";
496   }
497 
498   ret += line;
499 
500   return ret;
501 }
502