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/text.h" |
16 | |
17 | #include <algorithm> |
18 | #include <cassert> |
19 | #include <cctype> |
20 | #include <cstdio> |
21 | #include <cstdlib> |
22 | #include <cstring> |
23 | #include <memory> |
24 | #include <set> |
25 | #include <sstream> |
26 | #include <string> |
27 | #include <unordered_map> |
28 | #include <utility> |
29 | #include <vector> |
30 | |
31 | #include "source/assembly_grammar.h" |
32 | #include "source/binary.h" |
33 | #include "source/diagnostic.h" |
34 | #include "source/ext_inst.h" |
35 | #include "source/instruction.h" |
36 | #include "source/opcode.h" |
37 | #include "source/operand.h" |
38 | #include "source/spirv_constant.h" |
39 | #include "source/spirv_target_env.h" |
40 | #include "source/table.h" |
41 | #include "source/text_handler.h" |
42 | #include "source/util/bitutils.h" |
43 | #include "source/util/parse_number.h" |
44 | #include "spirv-tools/libspirv.h" |
45 | |
46 | bool spvIsValidIDCharacter(const char value) { |
47 | return value == '_' || 0 != ::isalnum(value); |
48 | } |
49 | |
50 | // Returns true if the given string represents a valid ID name. |
51 | bool spvIsValidID(const char* textValue) { |
52 | const char* c = textValue; |
53 | for (; *c != '\0'; ++c) { |
54 | if (!spvIsValidIDCharacter(*c)) { |
55 | return false; |
56 | } |
57 | } |
58 | // If the string was empty, then the ID also is not valid. |
59 | return c != textValue; |
60 | } |
61 | |
62 | // Text API |
63 | |
64 | spv_result_t spvTextToLiteral(const char* textValue, spv_literal_t* pLiteral) { |
65 | bool isSigned = false; |
66 | int numPeriods = 0; |
67 | bool isString = false; |
68 | |
69 | const size_t len = strlen(textValue); |
70 | if (len == 0) return SPV_FAILED_MATCH; |
71 | |
72 | for (uint64_t index = 0; index < len; ++index) { |
73 | switch (textValue[index]) { |
74 | case '0': |
75 | case '1': |
76 | case '2': |
77 | case '3': |
78 | case '4': |
79 | case '5': |
80 | case '6': |
81 | case '7': |
82 | case '8': |
83 | case '9': |
84 | break; |
85 | case '.': |
86 | numPeriods++; |
87 | break; |
88 | case '-': |
89 | if (index == 0) { |
90 | isSigned = true; |
91 | } else { |
92 | isString = true; |
93 | } |
94 | break; |
95 | default: |
96 | isString = true; |
97 | index = len; // break out of the loop too. |
98 | break; |
99 | } |
100 | } |
101 | |
102 | pLiteral->type = spv_literal_type_t(99); |
103 | |
104 | if (isString || numPeriods > 1 || (isSigned && len == 1)) { |
105 | if (len < 2 || textValue[0] != '"' || textValue[len - 1] != '"') |
106 | return SPV_FAILED_MATCH; |
107 | bool escaping = false; |
108 | for (const char* val = textValue + 1; val != textValue + len - 1; ++val) { |
109 | if ((*val == '\\') && (!escaping)) { |
110 | escaping = true; |
111 | } else { |
112 | // Have to save space for the null-terminator |
113 | if (pLiteral->str.size() >= SPV_LIMIT_LITERAL_STRING_BYTES_MAX) |
114 | return SPV_ERROR_OUT_OF_MEMORY; |
115 | pLiteral->str.push_back(*val); |
116 | escaping = false; |
117 | } |
118 | } |
119 | |
120 | pLiteral->type = SPV_LITERAL_TYPE_STRING; |
121 | } else if (numPeriods == 1) { |
122 | double d = std::strtod(textValue, nullptr); |
123 | float f = (float)d; |
124 | if (d == (double)f) { |
125 | pLiteral->type = SPV_LITERAL_TYPE_FLOAT_32; |
126 | pLiteral->value.f = f; |
127 | } else { |
128 | pLiteral->type = SPV_LITERAL_TYPE_FLOAT_64; |
129 | pLiteral->value.d = d; |
130 | } |
131 | } else if (isSigned) { |
132 | int64_t i64 = strtoll(textValue, nullptr, 10); |
133 | int32_t i32 = (int32_t)i64; |
134 | if (i64 == (int64_t)i32) { |
135 | pLiteral->type = SPV_LITERAL_TYPE_INT_32; |
136 | pLiteral->value.i32 = i32; |
137 | } else { |
138 | pLiteral->type = SPV_LITERAL_TYPE_INT_64; |
139 | pLiteral->value.i64 = i64; |
140 | } |
141 | } else { |
142 | uint64_t u64 = strtoull(textValue, nullptr, 10); |
143 | uint32_t u32 = (uint32_t)u64; |
144 | if (u64 == (uint64_t)u32) { |
145 | pLiteral->type = SPV_LITERAL_TYPE_UINT_32; |
146 | pLiteral->value.u32 = u32; |
147 | } else { |
148 | pLiteral->type = SPV_LITERAL_TYPE_UINT_64; |
149 | pLiteral->value.u64 = u64; |
150 | } |
151 | } |
152 | |
153 | return SPV_SUCCESS; |
154 | } |
155 | |
156 | namespace { |
157 | |
158 | /// Parses an immediate integer from text, guarding against overflow. If |
159 | /// successful, adds the parsed value to pInst, advances the context past it, |
160 | /// and returns SPV_SUCCESS. Otherwise, leaves pInst alone, emits diagnostics, |
161 | /// and returns SPV_ERROR_INVALID_TEXT. |
162 | spv_result_t encodeImmediate(spvtools::AssemblyContext* context, |
163 | const char* text, spv_instruction_t* pInst) { |
164 | assert(*text == '!'); |
165 | uint32_t parse_result; |
166 | if (!spvtools::utils::ParseNumber(text + 1, &parse_result)) { |
167 | return context->diagnostic(SPV_ERROR_INVALID_TEXT) |
168 | << "Invalid immediate integer: !" << text + 1; |
169 | } |
170 | context->binaryEncodeU32(parse_result, pInst); |
171 | context->seekForward(static_cast<uint32_t>(strlen(text))); |
172 | return SPV_SUCCESS; |
173 | } |
174 | |
175 | } // anonymous namespace |
176 | |
177 | /// @brief Translate an Opcode operand to binary form |
178 | /// |
179 | /// @param[in] grammar the grammar to use for compilation |
180 | /// @param[in, out] context the dynamic compilation info |
181 | /// @param[in] type of the operand |
182 | /// @param[in] textValue word of text to be parsed |
183 | /// @param[out] pInst return binary Opcode |
184 | /// @param[in,out] pExpectedOperands the operand types expected |
185 | /// |
186 | /// @return result code |
187 | spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, |
188 | spvtools::AssemblyContext* context, |
189 | const spv_operand_type_t type, |
190 | const char* textValue, |
191 | spv_instruction_t* pInst, |
192 | spv_operand_pattern_t* pExpectedOperands) { |
193 | // NOTE: Handle immediate int in the stream |
194 | if ('!' == textValue[0]) { |
195 | if (auto error = encodeImmediate(context, textValue, pInst)) { |
196 | return error; |
197 | } |
198 | *pExpectedOperands = |
199 | spvAlternatePatternFollowingImmediate(*pExpectedOperands); |
200 | return SPV_SUCCESS; |
201 | } |
202 | |
203 | // Optional literal operands can fail to parse. In that case use |
204 | // SPV_FAILED_MATCH to avoid emitting a diagostic. Use the following |
205 | // for those situations. |
206 | spv_result_t error_code_for_literals = |
207 | spvOperandIsOptional(type) ? SPV_FAILED_MATCH : SPV_ERROR_INVALID_TEXT; |
208 | |
209 | switch (type) { |
210 | case SPV_OPERAND_TYPE_ID: |
211 | case SPV_OPERAND_TYPE_TYPE_ID: |
212 | case SPV_OPERAND_TYPE_RESULT_ID: |
213 | case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: |
214 | case SPV_OPERAND_TYPE_SCOPE_ID: |
215 | case SPV_OPERAND_TYPE_OPTIONAL_ID: { |
216 | if ('%' == textValue[0]) { |
217 | textValue++; |
218 | } else { |
219 | return context->diagnostic() << "Expected id to start with %." ; |
220 | } |
221 | if (!spvIsValidID(textValue)) { |
222 | return context->diagnostic() << "Invalid ID " << textValue; |
223 | } |
224 | const uint32_t id = context->spvNamedIdAssignOrGet(textValue); |
225 | if (type == SPV_OPERAND_TYPE_TYPE_ID) pInst->resultTypeId = id; |
226 | spvInstructionAddWord(pInst, id); |
227 | |
228 | // Set the extended instruction type. |
229 | // The import set id is the 3rd operand of OpExtInst. |
230 | if (pInst->opcode == SpvOpExtInst && pInst->words.size() == 4) { |
231 | auto ext_inst_type = context->getExtInstTypeForId(pInst->words[3]); |
232 | if (ext_inst_type == SPV_EXT_INST_TYPE_NONE) { |
233 | return context->diagnostic() |
234 | << "Invalid extended instruction import Id " |
235 | << pInst->words[2]; |
236 | } |
237 | pInst->extInstType = ext_inst_type; |
238 | } |
239 | } break; |
240 | |
241 | case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { |
242 | // The assembler accepts the symbolic name for an extended instruction, |
243 | // and emits its corresponding number. |
244 | spv_ext_inst_desc extInst; |
245 | if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst) == |
246 | SPV_SUCCESS) { |
247 | // if we know about this extended instruction, push the numeric value |
248 | spvInstructionAddWord(pInst, extInst->ext_inst); |
249 | |
250 | // Prepare to parse the operands for the extended instructions. |
251 | spvPushOperandTypes(extInst->operandTypes, pExpectedOperands); |
252 | } else { |
253 | // if we don't know this extended instruction and the set isn't |
254 | // non-semantic, we cannot process further |
255 | if (!spvExtInstIsNonSemantic(pInst->extInstType)) { |
256 | return context->diagnostic() |
257 | << "Invalid extended instruction name '" << textValue << "'." ; |
258 | } else { |
259 | // for non-semantic instruction sets, as long as the text name is an |
260 | // integer value we can encode it since we know the form of all such |
261 | // extended instructions |
262 | spv_literal_t extInstValue; |
263 | if (spvTextToLiteral(textValue, &extInstValue) || |
264 | extInstValue.type != SPV_LITERAL_TYPE_UINT_32) { |
265 | return context->diagnostic() |
266 | << "Couldn't translate unknown extended instruction name '" |
267 | << textValue << "' to unsigned integer." ; |
268 | } |
269 | |
270 | spvInstructionAddWord(pInst, extInstValue.value.u32); |
271 | |
272 | // opcode contains an unknown number of IDs. |
273 | pExpectedOperands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID); |
274 | } |
275 | } |
276 | } break; |
277 | |
278 | case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { |
279 | // The assembler accepts the symbolic name for the opcode, but without |
280 | // the "Op" prefix. For example, "IAdd" is accepted. The number |
281 | // of the opcode is emitted. |
282 | SpvOp opcode; |
283 | if (grammar.lookupSpecConstantOpcode(textValue, &opcode)) { |
284 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
285 | << " '" << textValue << "'." ; |
286 | } |
287 | spv_opcode_desc opcodeEntry = nullptr; |
288 | if (grammar.lookupOpcode(opcode, &opcodeEntry)) { |
289 | return context->diagnostic(SPV_ERROR_INTERNAL) |
290 | << "OpSpecConstant opcode table out of sync" ; |
291 | } |
292 | spvInstructionAddWord(pInst, uint32_t(opcodeEntry->opcode)); |
293 | |
294 | // Prepare to parse the operands for the opcode. Except skip the |
295 | // type Id and result Id, since they've already been processed. |
296 | assert(opcodeEntry->hasType); |
297 | assert(opcodeEntry->hasResult); |
298 | assert(opcodeEntry->numTypes >= 2); |
299 | spvPushOperandTypes(opcodeEntry->operandTypes + 2, pExpectedOperands); |
300 | } break; |
301 | |
302 | case SPV_OPERAND_TYPE_LITERAL_INTEGER: |
303 | case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: { |
304 | // The current operand is an *unsigned* 32-bit integer. |
305 | // That's just how the grammar works. |
306 | spvtools::IdType expected_type = { |
307 | 32, false, spvtools::IdTypeClass::kScalarIntegerType}; |
308 | if (auto error = context->binaryEncodeNumericLiteral( |
309 | textValue, error_code_for_literals, expected_type, pInst)) { |
310 | return error; |
311 | } |
312 | } break; |
313 | |
314 | case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER: |
315 | // This is a context-independent literal number which can be a 32-bit |
316 | // number of floating point value. |
317 | if (auto error = context->binaryEncodeNumericLiteral( |
318 | textValue, error_code_for_literals, spvtools::kUnknownType, |
319 | pInst)) { |
320 | return error; |
321 | } |
322 | break; |
323 | |
324 | case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: |
325 | case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: { |
326 | spvtools::IdType expected_type = spvtools::kUnknownType; |
327 | // The encoding for OpConstant, OpSpecConstant and OpSwitch all |
328 | // depend on either their own result-id or the result-id of |
329 | // one of their parameters. |
330 | if (SpvOpConstant == pInst->opcode || |
331 | SpvOpSpecConstant == pInst->opcode) { |
332 | // The type of the literal is determined by the type Id of the |
333 | // instruction. |
334 | expected_type = |
335 | context->getTypeOfTypeGeneratingValue(pInst->resultTypeId); |
336 | if (!spvtools::isScalarFloating(expected_type) && |
337 | !spvtools::isScalarIntegral(expected_type)) { |
338 | spv_opcode_desc d; |
339 | const char* opcode_name = "opcode" ; |
340 | if (SPV_SUCCESS == grammar.lookupOpcode(pInst->opcode, &d)) { |
341 | opcode_name = d->name; |
342 | } |
343 | return context->diagnostic() |
344 | << "Type for " << opcode_name |
345 | << " must be a scalar floating point or integer type" ; |
346 | } |
347 | } else if (pInst->opcode == SpvOpSwitch) { |
348 | // The type of the literal is the same as the type of the selector. |
349 | expected_type = context->getTypeOfValueInstruction(pInst->words[1]); |
350 | if (!spvtools::isScalarIntegral(expected_type)) { |
351 | return context->diagnostic() |
352 | << "The selector operand for OpSwitch must be the result" |
353 | " of an instruction that generates an integer scalar" ; |
354 | } |
355 | } |
356 | if (auto error = context->binaryEncodeNumericLiteral( |
357 | textValue, error_code_for_literals, expected_type, pInst)) { |
358 | return error; |
359 | } |
360 | } break; |
361 | |
362 | case SPV_OPERAND_TYPE_LITERAL_STRING: |
363 | case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: { |
364 | spv_literal_t literal = {}; |
365 | spv_result_t error = spvTextToLiteral(textValue, &literal); |
366 | if (error != SPV_SUCCESS) { |
367 | if (error == SPV_ERROR_OUT_OF_MEMORY) return error; |
368 | return context->diagnostic(error_code_for_literals) |
369 | << "Invalid literal string '" << textValue << "'." ; |
370 | } |
371 | if (literal.type != SPV_LITERAL_TYPE_STRING) { |
372 | return context->diagnostic() |
373 | << "Expected literal string, found literal number '" << textValue |
374 | << "'." ; |
375 | } |
376 | |
377 | // NOTE: Special case for extended instruction library import |
378 | if (SpvOpExtInstImport == pInst->opcode) { |
379 | const spv_ext_inst_type_t ext_inst_type = |
380 | spvExtInstImportTypeGet(literal.str.c_str()); |
381 | if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) { |
382 | return context->diagnostic() |
383 | << "Invalid extended instruction import '" << literal.str |
384 | << "'" ; |
385 | } |
386 | if ((error = context->recordIdAsExtInstImport(pInst->words[1], |
387 | ext_inst_type))) |
388 | return error; |
389 | } |
390 | |
391 | if (context->binaryEncodeString(literal.str.c_str(), pInst)) |
392 | return SPV_ERROR_INVALID_TEXT; |
393 | } break; |
394 | |
395 | // Masks. |
396 | case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: |
397 | case SPV_OPERAND_TYPE_FUNCTION_CONTROL: |
398 | case SPV_OPERAND_TYPE_LOOP_CONTROL: |
399 | case SPV_OPERAND_TYPE_IMAGE: |
400 | case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: |
401 | case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: |
402 | case SPV_OPERAND_TYPE_SELECTION_CONTROL: |
403 | case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: |
404 | case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: { |
405 | uint32_t value; |
406 | if (grammar.parseMaskOperand(type, textValue, &value)) { |
407 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
408 | << " operand '" << textValue << "'." ; |
409 | } |
410 | if (auto error = context->binaryEncodeU32(value, pInst)) return error; |
411 | // Prepare to parse the operands for this logical operand. |
412 | grammar.pushOperandTypesForMask(type, value, pExpectedOperands); |
413 | } break; |
414 | case SPV_OPERAND_TYPE_OPTIONAL_CIV: { |
415 | auto error = spvTextEncodeOperand( |
416 | grammar, context, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER, textValue, |
417 | pInst, pExpectedOperands); |
418 | if (error == SPV_FAILED_MATCH) { |
419 | // It's not a literal number -- is it a literal string? |
420 | error = spvTextEncodeOperand(grammar, context, |
421 | SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING, |
422 | textValue, pInst, pExpectedOperands); |
423 | } |
424 | if (error == SPV_FAILED_MATCH) { |
425 | // It's not a literal -- is it an ID? |
426 | error = |
427 | spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_OPTIONAL_ID, |
428 | textValue, pInst, pExpectedOperands); |
429 | } |
430 | if (error) { |
431 | return context->diagnostic(error) |
432 | << "Invalid word following !<integer>: " << textValue; |
433 | } |
434 | if (pExpectedOperands->empty()) { |
435 | pExpectedOperands->push_back(SPV_OPERAND_TYPE_OPTIONAL_CIV); |
436 | } |
437 | } break; |
438 | default: { |
439 | // NOTE: All non literal operands are handled here using the operand |
440 | // table. |
441 | spv_operand_desc entry; |
442 | if (grammar.lookupOperand(type, textValue, strlen(textValue), &entry)) { |
443 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
444 | << " '" << textValue << "'." ; |
445 | } |
446 | if (context->binaryEncodeU32(entry->value, pInst)) { |
447 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
448 | << " '" << textValue << "'." ; |
449 | } |
450 | |
451 | // Prepare to parse the operands for this logical operand. |
452 | spvPushOperandTypes(entry->operandTypes, pExpectedOperands); |
453 | } break; |
454 | } |
455 | return SPV_SUCCESS; |
456 | } |
457 | |
458 | namespace { |
459 | |
460 | /// Encodes an instruction started by !<integer> at the given position in text. |
461 | /// |
462 | /// Puts the encoded words into *pInst. If successful, moves position past the |
463 | /// instruction and returns SPV_SUCCESS. Otherwise, returns an error code and |
464 | /// leaves position pointing to the error in text. |
465 | spv_result_t encodeInstructionStartingWithImmediate( |
466 | const spvtools::AssemblyGrammar& grammar, |
467 | spvtools::AssemblyContext* context, spv_instruction_t* pInst) { |
468 | std::string firstWord; |
469 | spv_position_t nextPosition = {}; |
470 | auto error = context->getWord(&firstWord, &nextPosition); |
471 | if (error) return context->diagnostic(error) << "Internal Error" ; |
472 | |
473 | if ((error = encodeImmediate(context, firstWord.c_str(), pInst))) { |
474 | return error; |
475 | } |
476 | while (context->advance() != SPV_END_OF_STREAM) { |
477 | // A beginning of a new instruction means we're done. |
478 | if (context->isStartOfNewInst()) return SPV_SUCCESS; |
479 | |
480 | // Otherwise, there must be an operand that's either a literal, an ID, or |
481 | // an immediate. |
482 | std::string operandValue; |
483 | if ((error = context->getWord(&operandValue, &nextPosition))) |
484 | return context->diagnostic(error) << "Internal Error" ; |
485 | |
486 | if (operandValue == "=" ) |
487 | return context->diagnostic() << firstWord << " not allowed before =." ; |
488 | |
489 | // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be |
490 | // expanded. |
491 | spv_operand_pattern_t dummyExpectedOperands; |
492 | error = spvTextEncodeOperand( |
493 | grammar, context, SPV_OPERAND_TYPE_OPTIONAL_CIV, operandValue.c_str(), |
494 | pInst, &dummyExpectedOperands); |
495 | if (error) return error; |
496 | context->setPosition(nextPosition); |
497 | } |
498 | return SPV_SUCCESS; |
499 | } |
500 | |
501 | /// @brief Translate single Opcode and operands to binary form |
502 | /// |
503 | /// @param[in] grammar the grammar to use for compilation |
504 | /// @param[in, out] context the dynamic compilation info |
505 | /// @param[in] text stream to translate |
506 | /// @param[out] pInst returned binary Opcode |
507 | /// @param[in,out] pPosition in the text stream |
508 | /// |
509 | /// @return result code |
510 | spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar, |
511 | spvtools::AssemblyContext* context, |
512 | spv_instruction_t* pInst) { |
513 | // Check for !<integer> first. |
514 | if ('!' == context->peek()) { |
515 | return encodeInstructionStartingWithImmediate(grammar, context, pInst); |
516 | } |
517 | |
518 | std::string firstWord; |
519 | spv_position_t nextPosition = {}; |
520 | spv_result_t error = context->getWord(&firstWord, &nextPosition); |
521 | if (error) return context->diagnostic() << "Internal Error" ; |
522 | |
523 | std::string opcodeName; |
524 | std::string result_id; |
525 | spv_position_t result_id_position = {}; |
526 | if (context->startsWithOp()) { |
527 | opcodeName = firstWord; |
528 | } else { |
529 | result_id = firstWord; |
530 | if ('%' != result_id.front()) { |
531 | return context->diagnostic() |
532 | << "Expected <opcode> or <result-id> at the beginning " |
533 | "of an instruction, found '" |
534 | << result_id << "'." ; |
535 | } |
536 | result_id_position = context->position(); |
537 | |
538 | // The '=' sign. |
539 | context->setPosition(nextPosition); |
540 | if (context->advance()) |
541 | return context->diagnostic() << "Expected '=', found end of stream." ; |
542 | std::string equal_sign; |
543 | error = context->getWord(&equal_sign, &nextPosition); |
544 | if ("=" != equal_sign) |
545 | return context->diagnostic() << "'=' expected after result id." ; |
546 | |
547 | // The <opcode> after the '=' sign. |
548 | context->setPosition(nextPosition); |
549 | if (context->advance()) |
550 | return context->diagnostic() << "Expected opcode, found end of stream." ; |
551 | error = context->getWord(&opcodeName, &nextPosition); |
552 | if (error) return context->diagnostic(error) << "Internal Error" ; |
553 | if (!context->startsWithOp()) { |
554 | return context->diagnostic() |
555 | << "Invalid Opcode prefix '" << opcodeName << "'." ; |
556 | } |
557 | } |
558 | |
559 | // NOTE: The table contains Opcode names without the "Op" prefix. |
560 | const char* pInstName = opcodeName.data() + 2; |
561 | |
562 | spv_opcode_desc opcodeEntry; |
563 | error = grammar.lookupOpcode(pInstName, &opcodeEntry); |
564 | if (error) { |
565 | return context->diagnostic(error) |
566 | << "Invalid Opcode name '" << opcodeName << "'" ; |
567 | } |
568 | if (opcodeEntry->hasResult && result_id.empty()) { |
569 | return context->diagnostic() |
570 | << "Expected <result-id> at the beginning of an instruction, found '" |
571 | << firstWord << "'." ; |
572 | } |
573 | if (!opcodeEntry->hasResult && !result_id.empty()) { |
574 | return context->diagnostic() |
575 | << "Cannot set ID " << result_id << " because " << opcodeName |
576 | << " does not produce a result ID." ; |
577 | } |
578 | pInst->opcode = opcodeEntry->opcode; |
579 | context->setPosition(nextPosition); |
580 | // Reserve the first word for the instruction. |
581 | spvInstructionAddWord(pInst, 0); |
582 | |
583 | // Maintains the ordered list of expected operand types. |
584 | // For many instructions we only need the {numTypes, operandTypes} |
585 | // entries in opcodeEntry. However, sometimes we need to modify |
586 | // the list as we parse the operands. This occurs when an operand |
587 | // has its own logical operands (such as the LocalSize operand for |
588 | // ExecutionMode), or for extended instructions that may have their |
589 | // own operands depending on the selected extended instruction. |
590 | spv_operand_pattern_t expectedOperands; |
591 | expectedOperands.reserve(opcodeEntry->numTypes); |
592 | for (auto i = 0; i < opcodeEntry->numTypes; i++) |
593 | expectedOperands.push_back( |
594 | opcodeEntry->operandTypes[opcodeEntry->numTypes - i - 1]); |
595 | |
596 | while (!expectedOperands.empty()) { |
597 | const spv_operand_type_t type = expectedOperands.back(); |
598 | expectedOperands.pop_back(); |
599 | |
600 | // Expand optional tuples lazily. |
601 | if (spvExpandOperandSequenceOnce(type, &expectedOperands)) continue; |
602 | |
603 | if (type == SPV_OPERAND_TYPE_RESULT_ID && !result_id.empty()) { |
604 | // Handle the <result-id> for value generating instructions. |
605 | // We've already consumed it from the text stream. Here |
606 | // we inject its words into the instruction. |
607 | spv_position_t temp_pos = context->position(); |
608 | error = spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_RESULT_ID, |
609 | result_id.c_str(), pInst, nullptr); |
610 | result_id_position = context->position(); |
611 | // Because we are injecting we have to reset the position afterwards. |
612 | context->setPosition(temp_pos); |
613 | if (error) return error; |
614 | } else { |
615 | // Find the next word. |
616 | error = context->advance(); |
617 | if (error == SPV_END_OF_STREAM) { |
618 | if (spvOperandIsOptional(type)) { |
619 | // This would have been the last potential operand for the |
620 | // instruction, |
621 | // and we didn't find one. We're finished parsing this instruction. |
622 | break; |
623 | } else { |
624 | return context->diagnostic() |
625 | << "Expected operand, found end of stream." ; |
626 | } |
627 | } |
628 | assert(error == SPV_SUCCESS && "Somebody added another way to fail" ); |
629 | |
630 | if (context->isStartOfNewInst()) { |
631 | if (spvOperandIsOptional(type)) { |
632 | break; |
633 | } else { |
634 | return context->diagnostic() |
635 | << "Expected operand, found next instruction instead." ; |
636 | } |
637 | } |
638 | |
639 | std::string operandValue; |
640 | error = context->getWord(&operandValue, &nextPosition); |
641 | if (error) return context->diagnostic(error) << "Internal Error" ; |
642 | |
643 | error = spvTextEncodeOperand(grammar, context, type, operandValue.c_str(), |
644 | pInst, &expectedOperands); |
645 | |
646 | if (error == SPV_FAILED_MATCH && spvOperandIsOptional(type)) |
647 | return SPV_SUCCESS; |
648 | |
649 | if (error) return error; |
650 | |
651 | context->setPosition(nextPosition); |
652 | } |
653 | } |
654 | |
655 | if (spvOpcodeGeneratesType(pInst->opcode)) { |
656 | if (context->recordTypeDefinition(pInst) != SPV_SUCCESS) { |
657 | return SPV_ERROR_INVALID_TEXT; |
658 | } |
659 | } else if (opcodeEntry->hasType) { |
660 | // SPIR-V dictates that if an instruction has both a return value and a |
661 | // type ID then the type id is first, and the return value is second. |
662 | assert(opcodeEntry->hasResult && |
663 | "Unknown opcode: has a type but no result." ); |
664 | context->recordTypeIdForValue(pInst->words[2], pInst->words[1]); |
665 | } |
666 | |
667 | if (pInst->words.size() > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) { |
668 | return context->diagnostic() |
669 | << "Instruction too long: " << pInst->words.size() |
670 | << " words, but the limit is " |
671 | << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX; |
672 | } |
673 | |
674 | pInst->words[0] = |
675 | spvOpcodeMake(uint16_t(pInst->words.size()), opcodeEntry->opcode); |
676 | |
677 | return SPV_SUCCESS; |
678 | } |
679 | |
680 | enum { kAssemblerVersion = 0 }; |
681 | |
682 | // Populates a binary stream's |header|. The target environment is specified via |
683 | // |env| and Id bound is via |bound|. |
684 | spv_result_t (spv_target_env env, const uint32_t bound, |
685 | uint32_t* ) { |
686 | if (!header) return SPV_ERROR_INVALID_BINARY; |
687 | |
688 | header[SPV_INDEX_MAGIC_NUMBER] = SpvMagicNumber; |
689 | header[SPV_INDEX_VERSION_NUMBER] = spvVersionForTargetEnv(env); |
690 | header[SPV_INDEX_GENERATOR_NUMBER] = |
691 | SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, kAssemblerVersion); |
692 | header[SPV_INDEX_BOUND] = bound; |
693 | header[SPV_INDEX_SCHEMA] = 0; // NOTE: Reserved |
694 | |
695 | return SPV_SUCCESS; |
696 | } |
697 | |
698 | // Collects all numeric ids in the module source into |numeric_ids|. |
699 | // This function is essentially a dry-run of spvTextToBinary. |
700 | spv_result_t GetNumericIds(const spvtools::AssemblyGrammar& grammar, |
701 | const spvtools::MessageConsumer& consumer, |
702 | const spv_text text, |
703 | std::set<uint32_t>* numeric_ids) { |
704 | spvtools::AssemblyContext context(text, consumer); |
705 | |
706 | if (!text->str) return context.diagnostic() << "Missing assembly text." ; |
707 | |
708 | if (!grammar.isValid()) { |
709 | return SPV_ERROR_INVALID_TABLE; |
710 | } |
711 | |
712 | // Skip past whitespace and comments. |
713 | context.advance(); |
714 | |
715 | while (context.hasText()) { |
716 | spv_instruction_t inst; |
717 | |
718 | if (spvTextEncodeOpcode(grammar, &context, &inst)) { |
719 | return SPV_ERROR_INVALID_TEXT; |
720 | } |
721 | |
722 | if (context.advance()) break; |
723 | } |
724 | |
725 | *numeric_ids = context.GetNumericIds(); |
726 | return SPV_SUCCESS; |
727 | } |
728 | |
729 | // Translates a given assembly language module into binary form. |
730 | // If a diagnostic is generated, it is not yet marked as being |
731 | // for a text-based input. |
732 | spv_result_t spvTextToBinaryInternal(const spvtools::AssemblyGrammar& grammar, |
733 | const spvtools::MessageConsumer& consumer, |
734 | const spv_text text, |
735 | const uint32_t options, |
736 | spv_binary* pBinary) { |
737 | // The ids in this set will have the same values both in source and binary. |
738 | // All other ids will be generated by filling in the gaps. |
739 | std::set<uint32_t> ids_to_preserve; |
740 | |
741 | if (options & SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS) { |
742 | // Collect all numeric ids from the source into ids_to_preserve. |
743 | const spv_result_t result = |
744 | GetNumericIds(grammar, consumer, text, &ids_to_preserve); |
745 | if (result != SPV_SUCCESS) return result; |
746 | } |
747 | |
748 | spvtools::AssemblyContext context(text, consumer, std::move(ids_to_preserve)); |
749 | |
750 | if (!text->str) return context.diagnostic() << "Missing assembly text." ; |
751 | |
752 | if (!grammar.isValid()) { |
753 | return SPV_ERROR_INVALID_TABLE; |
754 | } |
755 | if (!pBinary) return SPV_ERROR_INVALID_POINTER; |
756 | |
757 | std::vector<spv_instruction_t> instructions; |
758 | |
759 | // Skip past whitespace and comments. |
760 | context.advance(); |
761 | |
762 | while (context.hasText()) { |
763 | instructions.push_back({}); |
764 | spv_instruction_t& inst = instructions.back(); |
765 | |
766 | if (spvTextEncodeOpcode(grammar, &context, &inst)) { |
767 | return SPV_ERROR_INVALID_TEXT; |
768 | } |
769 | |
770 | if (context.advance()) break; |
771 | } |
772 | |
773 | size_t totalSize = SPV_INDEX_INSTRUCTION; |
774 | for (auto& inst : instructions) { |
775 | totalSize += inst.words.size(); |
776 | } |
777 | |
778 | uint32_t* data = new uint32_t[totalSize]; |
779 | if (!data) return SPV_ERROR_OUT_OF_MEMORY; |
780 | uint64_t currentIndex = SPV_INDEX_INSTRUCTION; |
781 | for (auto& inst : instructions) { |
782 | memcpy(data + currentIndex, inst.words.data(), |
783 | sizeof(uint32_t) * inst.words.size()); |
784 | currentIndex += inst.words.size(); |
785 | } |
786 | |
787 | if (auto error = SetHeader(grammar.target_env(), context.getBound(), data)) |
788 | return error; |
789 | |
790 | spv_binary binary = new spv_binary_t(); |
791 | if (!binary) { |
792 | delete[] data; |
793 | return SPV_ERROR_OUT_OF_MEMORY; |
794 | } |
795 | binary->code = data; |
796 | binary->wordCount = totalSize; |
797 | |
798 | *pBinary = binary; |
799 | |
800 | return SPV_SUCCESS; |
801 | } |
802 | |
803 | } // anonymous namespace |
804 | |
805 | spv_result_t spvTextToBinary(const spv_const_context context, |
806 | const char* input_text, |
807 | const size_t input_text_size, spv_binary* pBinary, |
808 | spv_diagnostic* pDiagnostic) { |
809 | return spvTextToBinaryWithOptions(context, input_text, input_text_size, |
810 | SPV_TEXT_TO_BINARY_OPTION_NONE, pBinary, |
811 | pDiagnostic); |
812 | } |
813 | |
814 | spv_result_t spvTextToBinaryWithOptions(const spv_const_context context, |
815 | const char* input_text, |
816 | const size_t input_text_size, |
817 | const uint32_t options, |
818 | spv_binary* pBinary, |
819 | spv_diagnostic* pDiagnostic) { |
820 | spv_context_t hijack_context = *context; |
821 | if (pDiagnostic) { |
822 | *pDiagnostic = nullptr; |
823 | spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); |
824 | } |
825 | |
826 | spv_text_t text = {input_text, input_text_size}; |
827 | spvtools::AssemblyGrammar grammar(&hijack_context); |
828 | |
829 | spv_result_t result = spvTextToBinaryInternal( |
830 | grammar, hijack_context.consumer, &text, options, pBinary); |
831 | if (pDiagnostic && *pDiagnostic) (*pDiagnostic)->isTextSource = true; |
832 | |
833 | return result; |
834 | } |
835 | |
836 | void spvTextDestroy(spv_text text) { |
837 | if (text) { |
838 | if (text->str) delete[] text->str; |
839 | delete text; |
840 | } |
841 | } |
842 | |