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 (auto error = grammar.parseMaskOperand(type, textValue, &value)) { |
407 | return context->diagnostic(error) |
408 | << "Invalid " << spvOperandTypeStr(type) << " operand '" |
409 | << textValue << "'." ; |
410 | } |
411 | if (auto error = context->binaryEncodeU32(value, pInst)) return error; |
412 | // Prepare to parse the operands for this logical operand. |
413 | grammar.pushOperandTypesForMask(type, value, pExpectedOperands); |
414 | } break; |
415 | case SPV_OPERAND_TYPE_OPTIONAL_CIV: { |
416 | auto error = spvTextEncodeOperand( |
417 | grammar, context, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER, textValue, |
418 | pInst, pExpectedOperands); |
419 | if (error == SPV_FAILED_MATCH) { |
420 | // It's not a literal number -- is it a literal string? |
421 | error = spvTextEncodeOperand(grammar, context, |
422 | SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING, |
423 | textValue, pInst, pExpectedOperands); |
424 | } |
425 | if (error == SPV_FAILED_MATCH) { |
426 | // It's not a literal -- is it an ID? |
427 | error = |
428 | spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_OPTIONAL_ID, |
429 | textValue, pInst, pExpectedOperands); |
430 | } |
431 | if (error) { |
432 | return context->diagnostic(error) |
433 | << "Invalid word following !<integer>: " << textValue; |
434 | } |
435 | if (pExpectedOperands->empty()) { |
436 | pExpectedOperands->push_back(SPV_OPERAND_TYPE_OPTIONAL_CIV); |
437 | } |
438 | } break; |
439 | default: { |
440 | // NOTE: All non literal operands are handled here using the operand |
441 | // table. |
442 | spv_operand_desc entry; |
443 | if (grammar.lookupOperand(type, textValue, strlen(textValue), &entry)) { |
444 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
445 | << " '" << textValue << "'." ; |
446 | } |
447 | if (context->binaryEncodeU32(entry->value, pInst)) { |
448 | return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) |
449 | << " '" << textValue << "'." ; |
450 | } |
451 | |
452 | // Prepare to parse the operands for this logical operand. |
453 | spvPushOperandTypes(entry->operandTypes, pExpectedOperands); |
454 | } break; |
455 | } |
456 | return SPV_SUCCESS; |
457 | } |
458 | |
459 | namespace { |
460 | |
461 | /// Encodes an instruction started by !<integer> at the given position in text. |
462 | /// |
463 | /// Puts the encoded words into *pInst. If successful, moves position past the |
464 | /// instruction and returns SPV_SUCCESS. Otherwise, returns an error code and |
465 | /// leaves position pointing to the error in text. |
466 | spv_result_t encodeInstructionStartingWithImmediate( |
467 | const spvtools::AssemblyGrammar& grammar, |
468 | spvtools::AssemblyContext* context, spv_instruction_t* pInst) { |
469 | std::string firstWord; |
470 | spv_position_t nextPosition = {}; |
471 | auto error = context->getWord(&firstWord, &nextPosition); |
472 | if (error) return context->diagnostic(error) << "Internal Error" ; |
473 | |
474 | if ((error = encodeImmediate(context, firstWord.c_str(), pInst))) { |
475 | return error; |
476 | } |
477 | while (context->advance() != SPV_END_OF_STREAM) { |
478 | // A beginning of a new instruction means we're done. |
479 | if (context->isStartOfNewInst()) return SPV_SUCCESS; |
480 | |
481 | // Otherwise, there must be an operand that's either a literal, an ID, or |
482 | // an immediate. |
483 | std::string operandValue; |
484 | if ((error = context->getWord(&operandValue, &nextPosition))) |
485 | return context->diagnostic(error) << "Internal Error" ; |
486 | |
487 | if (operandValue == "=" ) |
488 | return context->diagnostic() << firstWord << " not allowed before =." ; |
489 | |
490 | // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be |
491 | // expanded. |
492 | spv_operand_pattern_t dummyExpectedOperands; |
493 | error = spvTextEncodeOperand( |
494 | grammar, context, SPV_OPERAND_TYPE_OPTIONAL_CIV, operandValue.c_str(), |
495 | pInst, &dummyExpectedOperands); |
496 | if (error) return error; |
497 | context->setPosition(nextPosition); |
498 | } |
499 | return SPV_SUCCESS; |
500 | } |
501 | |
502 | /// @brief Translate single Opcode and operands to binary form |
503 | /// |
504 | /// @param[in] grammar the grammar to use for compilation |
505 | /// @param[in, out] context the dynamic compilation info |
506 | /// @param[in] text stream to translate |
507 | /// @param[out] pInst returned binary Opcode |
508 | /// @param[in,out] pPosition in the text stream |
509 | /// |
510 | /// @return result code |
511 | spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar, |
512 | spvtools::AssemblyContext* context, |
513 | spv_instruction_t* pInst) { |
514 | // Check for !<integer> first. |
515 | if ('!' == context->peek()) { |
516 | return encodeInstructionStartingWithImmediate(grammar, context, pInst); |
517 | } |
518 | |
519 | std::string firstWord; |
520 | spv_position_t nextPosition = {}; |
521 | spv_result_t error = context->getWord(&firstWord, &nextPosition); |
522 | if (error) return context->diagnostic() << "Internal Error" ; |
523 | |
524 | std::string opcodeName; |
525 | std::string result_id; |
526 | spv_position_t result_id_position = {}; |
527 | if (context->startsWithOp()) { |
528 | opcodeName = firstWord; |
529 | } else { |
530 | result_id = firstWord; |
531 | if ('%' != result_id.front()) { |
532 | return context->diagnostic() |
533 | << "Expected <opcode> or <result-id> at the beginning " |
534 | "of an instruction, found '" |
535 | << result_id << "'." ; |
536 | } |
537 | result_id_position = context->position(); |
538 | |
539 | // The '=' sign. |
540 | context->setPosition(nextPosition); |
541 | if (context->advance()) |
542 | return context->diagnostic() << "Expected '=', found end of stream." ; |
543 | std::string equal_sign; |
544 | error = context->getWord(&equal_sign, &nextPosition); |
545 | if ("=" != equal_sign) |
546 | return context->diagnostic() << "'=' expected after result id." ; |
547 | |
548 | // The <opcode> after the '=' sign. |
549 | context->setPosition(nextPosition); |
550 | if (context->advance()) |
551 | return context->diagnostic() << "Expected opcode, found end of stream." ; |
552 | error = context->getWord(&opcodeName, &nextPosition); |
553 | if (error) return context->diagnostic(error) << "Internal Error" ; |
554 | if (!context->startsWithOp()) { |
555 | return context->diagnostic() |
556 | << "Invalid Opcode prefix '" << opcodeName << "'." ; |
557 | } |
558 | } |
559 | |
560 | // NOTE: The table contains Opcode names without the "Op" prefix. |
561 | const char* pInstName = opcodeName.data() + 2; |
562 | |
563 | spv_opcode_desc opcodeEntry; |
564 | error = grammar.lookupOpcode(pInstName, &opcodeEntry); |
565 | if (error) { |
566 | return context->diagnostic(error) |
567 | << "Invalid Opcode name '" << opcodeName << "'" ; |
568 | } |
569 | if (opcodeEntry->hasResult && result_id.empty()) { |
570 | return context->diagnostic() |
571 | << "Expected <result-id> at the beginning of an instruction, found '" |
572 | << firstWord << "'." ; |
573 | } |
574 | if (!opcodeEntry->hasResult && !result_id.empty()) { |
575 | return context->diagnostic() |
576 | << "Cannot set ID " << result_id << " because " << opcodeName |
577 | << " does not produce a result ID." ; |
578 | } |
579 | pInst->opcode = opcodeEntry->opcode; |
580 | context->setPosition(nextPosition); |
581 | // Reserve the first word for the instruction. |
582 | spvInstructionAddWord(pInst, 0); |
583 | |
584 | // Maintains the ordered list of expected operand types. |
585 | // For many instructions we only need the {numTypes, operandTypes} |
586 | // entries in opcodeEntry. However, sometimes we need to modify |
587 | // the list as we parse the operands. This occurs when an operand |
588 | // has its own logical operands (such as the LocalSize operand for |
589 | // ExecutionMode), or for extended instructions that may have their |
590 | // own operands depending on the selected extended instruction. |
591 | spv_operand_pattern_t expectedOperands; |
592 | expectedOperands.reserve(opcodeEntry->numTypes); |
593 | for (auto i = 0; i < opcodeEntry->numTypes; i++) |
594 | expectedOperands.push_back( |
595 | opcodeEntry->operandTypes[opcodeEntry->numTypes - i - 1]); |
596 | |
597 | while (!expectedOperands.empty()) { |
598 | const spv_operand_type_t type = expectedOperands.back(); |
599 | expectedOperands.pop_back(); |
600 | |
601 | // Expand optional tuples lazily. |
602 | if (spvExpandOperandSequenceOnce(type, &expectedOperands)) continue; |
603 | |
604 | if (type == SPV_OPERAND_TYPE_RESULT_ID && !result_id.empty()) { |
605 | // Handle the <result-id> for value generating instructions. |
606 | // We've already consumed it from the text stream. Here |
607 | // we inject its words into the instruction. |
608 | spv_position_t temp_pos = context->position(); |
609 | error = spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_RESULT_ID, |
610 | result_id.c_str(), pInst, nullptr); |
611 | result_id_position = context->position(); |
612 | // Because we are injecting we have to reset the position afterwards. |
613 | context->setPosition(temp_pos); |
614 | if (error) return error; |
615 | } else { |
616 | // Find the next word. |
617 | error = context->advance(); |
618 | if (error == SPV_END_OF_STREAM) { |
619 | if (spvOperandIsOptional(type)) { |
620 | // This would have been the last potential operand for the |
621 | // instruction, |
622 | // and we didn't find one. We're finished parsing this instruction. |
623 | break; |
624 | } else { |
625 | return context->diagnostic() |
626 | << "Expected operand for " << opcodeName |
627 | << " instruction, but found the end of the stream." ; |
628 | } |
629 | } |
630 | assert(error == SPV_SUCCESS && "Somebody added another way to fail" ); |
631 | |
632 | if (context->isStartOfNewInst()) { |
633 | if (spvOperandIsOptional(type)) { |
634 | break; |
635 | } else { |
636 | return context->diagnostic() |
637 | << "Expected operand for " << opcodeName |
638 | << " instruction, but found the next instruction instead." ; |
639 | } |
640 | } |
641 | |
642 | std::string operandValue; |
643 | error = context->getWord(&operandValue, &nextPosition); |
644 | if (error) return context->diagnostic(error) << "Internal Error" ; |
645 | |
646 | error = spvTextEncodeOperand(grammar, context, type, operandValue.c_str(), |
647 | pInst, &expectedOperands); |
648 | |
649 | if (error == SPV_FAILED_MATCH && spvOperandIsOptional(type)) |
650 | return SPV_SUCCESS; |
651 | |
652 | if (error) return error; |
653 | |
654 | context->setPosition(nextPosition); |
655 | } |
656 | } |
657 | |
658 | if (spvOpcodeGeneratesType(pInst->opcode)) { |
659 | if (context->recordTypeDefinition(pInst) != SPV_SUCCESS) { |
660 | return SPV_ERROR_INVALID_TEXT; |
661 | } |
662 | } else if (opcodeEntry->hasType) { |
663 | // SPIR-V dictates that if an instruction has both a return value and a |
664 | // type ID then the type id is first, and the return value is second. |
665 | assert(opcodeEntry->hasResult && |
666 | "Unknown opcode: has a type but no result." ); |
667 | context->recordTypeIdForValue(pInst->words[2], pInst->words[1]); |
668 | } |
669 | |
670 | if (pInst->words.size() > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) { |
671 | return context->diagnostic() |
672 | << opcodeName << " Instruction too long: " << pInst->words.size() |
673 | << " words, but the limit is " |
674 | << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX; |
675 | } |
676 | |
677 | pInst->words[0] = |
678 | spvOpcodeMake(uint16_t(pInst->words.size()), opcodeEntry->opcode); |
679 | |
680 | return SPV_SUCCESS; |
681 | } |
682 | |
683 | enum { kAssemblerVersion = 0 }; |
684 | |
685 | // Populates a binary stream's |header|. The target environment is specified via |
686 | // |env| and Id bound is via |bound|. |
687 | spv_result_t (spv_target_env env, const uint32_t bound, |
688 | uint32_t* ) { |
689 | if (!header) return SPV_ERROR_INVALID_BINARY; |
690 | |
691 | header[SPV_INDEX_MAGIC_NUMBER] = SpvMagicNumber; |
692 | header[SPV_INDEX_VERSION_NUMBER] = spvVersionForTargetEnv(env); |
693 | header[SPV_INDEX_GENERATOR_NUMBER] = |
694 | SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, kAssemblerVersion); |
695 | header[SPV_INDEX_BOUND] = bound; |
696 | header[SPV_INDEX_SCHEMA] = 0; // NOTE: Reserved |
697 | |
698 | return SPV_SUCCESS; |
699 | } |
700 | |
701 | // Collects all numeric ids in the module source into |numeric_ids|. |
702 | // This function is essentially a dry-run of spvTextToBinary. |
703 | spv_result_t GetNumericIds(const spvtools::AssemblyGrammar& grammar, |
704 | const spvtools::MessageConsumer& consumer, |
705 | const spv_text text, |
706 | std::set<uint32_t>* numeric_ids) { |
707 | spvtools::AssemblyContext context(text, consumer); |
708 | |
709 | if (!text->str) return context.diagnostic() << "Missing assembly text." ; |
710 | |
711 | if (!grammar.isValid()) { |
712 | return SPV_ERROR_INVALID_TABLE; |
713 | } |
714 | |
715 | // Skip past whitespace and comments. |
716 | context.advance(); |
717 | |
718 | while (context.hasText()) { |
719 | spv_instruction_t inst; |
720 | |
721 | // Operand parsing sometimes involves knowing the opcode of the instruction |
722 | // being parsed. A malformed input might feature such an operand *before* |
723 | // the opcode is known. To guard against accessing an uninitialized opcode, |
724 | // the instruction's opcode is initialized to a default value. |
725 | inst.opcode = SpvOpMax; |
726 | |
727 | if (spvTextEncodeOpcode(grammar, &context, &inst)) { |
728 | return SPV_ERROR_INVALID_TEXT; |
729 | } |
730 | |
731 | if (context.advance()) break; |
732 | } |
733 | |
734 | *numeric_ids = context.GetNumericIds(); |
735 | return SPV_SUCCESS; |
736 | } |
737 | |
738 | // Translates a given assembly language module into binary form. |
739 | // If a diagnostic is generated, it is not yet marked as being |
740 | // for a text-based input. |
741 | spv_result_t spvTextToBinaryInternal(const spvtools::AssemblyGrammar& grammar, |
742 | const spvtools::MessageConsumer& consumer, |
743 | const spv_text text, |
744 | const uint32_t options, |
745 | spv_binary* pBinary) { |
746 | // The ids in this set will have the same values both in source and binary. |
747 | // All other ids will be generated by filling in the gaps. |
748 | std::set<uint32_t> ids_to_preserve; |
749 | |
750 | if (options & SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS) { |
751 | // Collect all numeric ids from the source into ids_to_preserve. |
752 | const spv_result_t result = |
753 | GetNumericIds(grammar, consumer, text, &ids_to_preserve); |
754 | if (result != SPV_SUCCESS) return result; |
755 | } |
756 | |
757 | spvtools::AssemblyContext context(text, consumer, std::move(ids_to_preserve)); |
758 | |
759 | if (!text->str) return context.diagnostic() << "Missing assembly text." ; |
760 | |
761 | if (!grammar.isValid()) { |
762 | return SPV_ERROR_INVALID_TABLE; |
763 | } |
764 | if (!pBinary) return SPV_ERROR_INVALID_POINTER; |
765 | |
766 | std::vector<spv_instruction_t> instructions; |
767 | |
768 | // Skip past whitespace and comments. |
769 | context.advance(); |
770 | |
771 | while (context.hasText()) { |
772 | instructions.push_back({}); |
773 | spv_instruction_t& inst = instructions.back(); |
774 | |
775 | if (auto error = spvTextEncodeOpcode(grammar, &context, &inst)) { |
776 | return error; |
777 | } |
778 | |
779 | if (context.advance()) break; |
780 | } |
781 | |
782 | size_t totalSize = SPV_INDEX_INSTRUCTION; |
783 | for (auto& inst : instructions) { |
784 | totalSize += inst.words.size(); |
785 | } |
786 | |
787 | uint32_t* data = new uint32_t[totalSize]; |
788 | if (!data) return SPV_ERROR_OUT_OF_MEMORY; |
789 | uint64_t currentIndex = SPV_INDEX_INSTRUCTION; |
790 | for (auto& inst : instructions) { |
791 | memcpy(data + currentIndex, inst.words.data(), |
792 | sizeof(uint32_t) * inst.words.size()); |
793 | currentIndex += inst.words.size(); |
794 | } |
795 | |
796 | if (auto error = SetHeader(grammar.target_env(), context.getBound(), data)) |
797 | return error; |
798 | |
799 | spv_binary binary = new spv_binary_t(); |
800 | if (!binary) { |
801 | delete[] data; |
802 | return SPV_ERROR_OUT_OF_MEMORY; |
803 | } |
804 | binary->code = data; |
805 | binary->wordCount = totalSize; |
806 | |
807 | *pBinary = binary; |
808 | |
809 | return SPV_SUCCESS; |
810 | } |
811 | |
812 | } // anonymous namespace |
813 | |
814 | spv_result_t spvTextToBinary(const spv_const_context context, |
815 | const char* input_text, |
816 | const size_t input_text_size, spv_binary* pBinary, |
817 | spv_diagnostic* pDiagnostic) { |
818 | return spvTextToBinaryWithOptions(context, input_text, input_text_size, |
819 | SPV_TEXT_TO_BINARY_OPTION_NONE, pBinary, |
820 | pDiagnostic); |
821 | } |
822 | |
823 | spv_result_t spvTextToBinaryWithOptions(const spv_const_context context, |
824 | const char* input_text, |
825 | const size_t input_text_size, |
826 | const uint32_t options, |
827 | spv_binary* pBinary, |
828 | spv_diagnostic* pDiagnostic) { |
829 | spv_context_t hijack_context = *context; |
830 | if (pDiagnostic) { |
831 | *pDiagnostic = nullptr; |
832 | spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); |
833 | } |
834 | |
835 | spv_text_t text = {input_text, input_text_size}; |
836 | spvtools::AssemblyGrammar grammar(&hijack_context); |
837 | |
838 | spv_result_t result = spvTextToBinaryInternal( |
839 | grammar, hijack_context.consumer, &text, options, pBinary); |
840 | if (pDiagnostic && *pDiagnostic) (*pDiagnostic)->isTextSource = true; |
841 | |
842 | return result; |
843 | } |
844 | |
845 | void spvTextDestroy(spv_text text) { |
846 | if (text) { |
847 | if (text->str) delete[] text->str; |
848 | delete text; |
849 | } |
850 | } |
851 | |