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