/************************************************************************** * * Copyright 2008 VMware, Inc. * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ #include "util/u_debug.h" #include "util/u_memory.h" #include "util/u_prim.h" #include "pipe/p_defines.h" #include "util/u_inlines.h" #include "tgsi_text.h" #include "tgsi_build.h" #include "tgsi_info.h" #include "tgsi_parse.h" #include "tgsi_sanity.h" #include "tgsi_strings.h" #include "tgsi_util.h" #include "tgsi_dump.h" static boolean is_alpha_underscore( const char *cur ) { return (*cur >= 'a' && *cur <= 'z') || (*cur >= 'A' && *cur <= 'Z') || *cur == '_'; } static boolean is_digit( const char *cur ) { return *cur >= '0' && *cur <= '9'; } static boolean is_digit_alpha_underscore( const char *cur ) { return is_digit( cur ) || is_alpha_underscore( cur ); } static char uprcase( char c ) { if (c >= 'a' && c <= 'z') return c + 'A' - 'a'; return c; } /* * Ignore case of str1 and assume str1 is already uppercase. * Return TRUE iff str1 and str2 are equal. */ static int streq_nocase_uprcase(const char *str1, const char *str2) { while (*str1 && *str2) { if (*str1 != uprcase(*str2)) return FALSE; str1++; str2++; } return *str1 == 0 && *str2 == 0; } /* Return TRUE if both strings match. * The second string is terminated by zero. * The pointer to the first string is moved at end of the read word * on success. */ static boolean str_match_no_case( const char **pcur, const char *str ) { const char *cur = *pcur; while (*str != '\0' && *str == uprcase( *cur )) { str++; cur++; } if (*str == '\0') { *pcur = cur; return TRUE; } return FALSE; } /* Return TRUE if both strings match. * The first string is be terminated by a non-digit non-letter non-underscore * character, the second string is terminated by zero. * The pointer to the first string is moved at end of the read word * on success. */ static boolean str_match_nocase_whole( const char **pcur, const char *str ) { const char *cur = *pcur; if (str_match_no_case(&cur, str) && !is_digit_alpha_underscore(cur)) { *pcur = cur; return TRUE; } return FALSE; } /* Return the array index that matches starting at *pcur, where the string at * *pcur is terminated by a non-digit non-letter non-underscore. * Returns -1 if no match is found. * * On success, the pointer to the first string is moved to the end of the read * word. */ static int str_match_name_from_array(const char **pcur, const char * const *array, unsigned array_size) { for (unsigned j = 0; j < array_size; ++j) { if (str_match_nocase_whole(pcur, array[j])) return j; } return -1; } /* Return the format corresponding to the name at *pcur. * Returns -1 if there is no format name. * * On success, the pointer to the string is moved to the end of the read format * name. */ static int str_match_format(const char **pcur) { for (unsigned i = 0; i < PIPE_FORMAT_COUNT; i++) { const struct util_format_description *desc = util_format_description(i); if (desc && str_match_nocase_whole(pcur, desc->name)) { return i; } } return -1; } /* Eat zero or more whitespaces. */ static void eat_opt_white( const char **pcur ) { while (**pcur == ' ' || **pcur == '\t' || **pcur == '\n') (*pcur)++; } /* Eat one or more whitespaces. * Return TRUE if at least one whitespace eaten. */ static boolean eat_white( const char **pcur ) { const char *cur = *pcur; eat_opt_white( pcur ); return *pcur > cur; } /* Parse unsigned integer. * No checks for overflow. */ static boolean parse_uint( const char **pcur, uint *val ) { const char *cur = *pcur; if (is_digit( cur )) { *val = *cur++ - '0'; while (is_digit( cur )) *val = *val * 10 + *cur++ - '0'; *pcur = cur; return TRUE; } return FALSE; } static boolean parse_int( const char **pcur, int *val ) { const char *cur = *pcur; int sign = (*cur == '-' ? -1 : 1); if (*cur == '+' || *cur == '-') cur++; if (parse_uint(&cur, (uint *)val)) { *val *= sign; *pcur = cur; return TRUE; } return FALSE; } static boolean parse_identifier( const char **pcur, char *ret, size_t len ) { const char *cur = *pcur; int i = 0; if (is_alpha_underscore( cur )) { ret[i++] = *cur++; while (is_alpha_underscore( cur ) || is_digit( cur )) { if (i == len - 1) return FALSE; ret[i++] = *cur++; } ret[i++] = '\0'; *pcur = cur; return TRUE; } return FALSE; } /* Parse floating point. */ static boolean parse_float( const char **pcur, float *val ) { const char *cur = *pcur; boolean integral_part = FALSE; boolean fractional_part = FALSE; if (*cur == '0' && *(cur + 1) == 'x') { union fi fi; fi.ui = strtoul(cur, NULL, 16); *val = fi.f; cur += 10; goto out; } *val = (float) atof( cur ); if (*cur == '-' || *cur == '+') cur++; if (is_digit( cur )) { cur++; integral_part = TRUE; while (is_digit( cur )) cur++; } if (*cur == '.') { cur++; if (is_digit( cur )) { cur++; fractional_part = TRUE; while (is_digit( cur )) cur++; } } if (!integral_part && !fractional_part) return FALSE; if (uprcase( *cur ) == 'E') { cur++; if (*cur == '-' || *cur == '+') cur++; if (is_digit( cur )) { cur++; while (is_digit( cur )) cur++; } else return FALSE; } out: *pcur = cur; return TRUE; } static boolean parse_double( const char **pcur, uint32_t *val0, uint32_t *val1) { const char *cur = *pcur; union { double dval; uint32_t uval[2]; } v; v.dval = strtod(cur, (char**)pcur); if (*pcur == cur) return FALSE; *val0 = v.uval[0]; *val1 = v.uval[1]; return TRUE; } static boolean parse_int64( const char **pcur, uint32_t *val0, uint32_t *val1) { const char *cur = *pcur; union { int64_t i64val; uint32_t uval[2]; } v; v.i64val = strtoll(cur, (char**)pcur, 0); if (*pcur == cur) return FALSE; *val0 = v.uval[0]; *val1 = v.uval[1]; return TRUE; } static boolean parse_uint64( const char **pcur, uint32_t *val0, uint32_t *val1) { const char *cur = *pcur; union { uint64_t u64val; uint32_t uval[2]; } v; v.u64val = strtoull(cur, (char**)pcur, 0); if (*pcur == cur) return FALSE; *val0 = v.uval[0]; *val1 = v.uval[1]; return TRUE; } struct translate_ctx { const char *text; const char *cur; struct tgsi_token *tokens; struct tgsi_token *tokens_cur; struct tgsi_token *tokens_end; struct tgsi_header *header; unsigned processor : 4; unsigned implied_array_size : 6; unsigned num_immediates; }; static void report_error( struct translate_ctx *ctx, const char *msg ) { int line = 1; int column = 1; const char *itr = ctx->text; while (itr != ctx->cur) { if (*itr == '\n') { column = 1; ++line; } ++column; ++itr; } debug_printf( "\nTGSI asm error: %s [%d : %d] \n", msg, line, column ); } /* Parse shader header. * Return TRUE for one of the following headers. * FRAG * GEOM * VERT */ static boolean parse_header( struct translate_ctx *ctx ) { uint processor; if (str_match_nocase_whole( &ctx->cur, "FRAG" )) processor = PIPE_SHADER_FRAGMENT; else if (str_match_nocase_whole( &ctx->cur, "VERT" )) processor = PIPE_SHADER_VERTEX; else if (str_match_nocase_whole( &ctx->cur, "GEOM" )) processor = PIPE_SHADER_GEOMETRY; else if (str_match_nocase_whole( &ctx->cur, "TESS_CTRL" )) processor = PIPE_SHADER_TESS_CTRL; else if (str_match_nocase_whole( &ctx->cur, "TESS_EVAL" )) processor = PIPE_SHADER_TESS_EVAL; else if (str_match_nocase_whole( &ctx->cur, "COMP" )) processor = PIPE_SHADER_COMPUTE; else { report_error( ctx, "Unknown header" ); return FALSE; } if (ctx->tokens_cur >= ctx->tokens_end) return FALSE; ctx->header = (struct tgsi_header *) ctx->tokens_cur++; *ctx->header = tgsi_build_header(); if (ctx->tokens_cur >= ctx->tokens_end) return FALSE; *(struct tgsi_processor *) ctx->tokens_cur++ = tgsi_build_processor( processor, ctx->header ); ctx->processor = processor; return TRUE; } static boolean parse_label( struct translate_ctx *ctx, uint *val ) { const char *cur = ctx->cur; if (parse_uint( &cur, val )) { eat_opt_white( &cur ); if (*cur == ':') { cur++; ctx->cur = cur; return TRUE; } } return FALSE; } static boolean parse_file( const char **pcur, uint *file ) { uint i; for (i = 0; i < TGSI_FILE_COUNT; i++) { const char *cur = *pcur; if (str_match_nocase_whole( &cur, tgsi_file_name(i) )) { *pcur = cur; *file = i; return TRUE; } } return FALSE; } static boolean parse_opt_writemask( struct translate_ctx *ctx, uint *writemask ) { const char *cur; cur = ctx->cur; eat_opt_white( &cur ); if (*cur == '.') { cur++; *writemask = TGSI_WRITEMASK_NONE; eat_opt_white( &cur ); if (uprcase( *cur ) == 'X') { cur++; *writemask |= TGSI_WRITEMASK_X; } if (uprcase( *cur ) == 'Y') { cur++; *writemask |= TGSI_WRITEMASK_Y; } if (uprcase( *cur ) == 'Z') { cur++; *writemask |= TGSI_WRITEMASK_Z; } if (uprcase( *cur ) == 'W') { cur++; *writemask |= TGSI_WRITEMASK_W; } if (*writemask == TGSI_WRITEMASK_NONE) { report_error( ctx, "Writemask expected" ); return FALSE; } ctx->cur = cur; } else { *writemask = TGSI_WRITEMASK_XYZW; } return TRUE; } /* <register_file_bracket> ::= <file> `[' */ static boolean parse_register_file_bracket( struct translate_ctx *ctx, uint *file ) { if (!parse_file( &ctx->cur, file )) { report_error( ctx, "Unknown register file" ); return FALSE; } eat_opt_white( &ctx->cur ); if (*ctx->cur != '[') { report_error( ctx, "Expected `['" ); return FALSE; } ctx->cur++; return TRUE; } /* <register_file_bracket_index> ::= <register_file_bracket> <uint> */ static boolean parse_register_file_bracket_index( struct translate_ctx *ctx, uint *file, int *index ) { uint uindex; if (!parse_register_file_bracket( ctx, file )) return FALSE; eat_opt_white( &ctx->cur ); if (!parse_uint( &ctx->cur, &uindex )) { report_error( ctx, "Expected literal unsigned integer" ); return FALSE; } *index = (int) uindex; return TRUE; } /* Parse simple 1d register operand. * <register_dst> ::= <register_file_bracket_index> `]' */ static boolean parse_register_1d(struct translate_ctx *ctx, uint *file, int *index ) { if (!parse_register_file_bracket_index( ctx, file, index )) return FALSE; eat_opt_white( &ctx->cur ); if (*ctx->cur != ']') { report_error( ctx, "Expected `]'" ); return FALSE; } ctx->cur++; return TRUE; } struct parsed_bracket { int index; uint ind_file; int ind_index; uint ind_comp; uint ind_array; }; static boolean parse_register_bracket( struct translate_ctx *ctx, struct parsed_bracket *brackets) { const char *cur; uint uindex; memset(brackets, 0, sizeof(struct parsed_bracket)); eat_opt_white( &ctx->cur ); cur = ctx->cur; if (parse_file( &cur, &brackets->ind_file )) { if (!parse_register_1d( ctx, &brackets->ind_file, &brackets->ind_index )) return FALSE; eat_opt_white( &ctx->cur ); if (*ctx->cur == '.') { ctx->cur++; eat_opt_white(&ctx->cur); switch (uprcase(*ctx->cur)) { case 'X': brackets->ind_comp = TGSI_SWIZZLE_X; break; case 'Y': brackets->ind_comp = TGSI_SWIZZLE_Y; break; case 'Z': brackets->ind_comp = TGSI_SWIZZLE_Z; break; case 'W': brackets->ind_comp = TGSI_SWIZZLE_W; break; default: report_error(ctx, "Expected indirect register swizzle component `x', `y', `z' or `w'"); return FALSE; } ctx->cur++; eat_opt_white(&ctx->cur); } if (*ctx->cur == '+' || *ctx->cur == '-') parse_int( &ctx->cur, &brackets->index ); else brackets->index = 0; } else { if (!parse_uint( &ctx->cur, &uindex )) { report_error( ctx, "Expected literal unsigned integer" ); return FALSE; } brackets->index = (int) uindex; brackets->ind_file = TGSI_FILE_NULL; brackets->ind_index = 0; } eat_opt_white( &ctx->cur ); if (*ctx->cur != ']') { report_error( ctx, "Expected `]'" ); return FALSE; } ctx->cur++; if (*ctx->cur == '(') { ctx->cur++; eat_opt_white( &ctx->cur ); if (!parse_uint( &ctx->cur, &brackets->ind_array )) { report_error( ctx, "Expected literal unsigned integer" ); return FALSE; } eat_opt_white( &ctx->cur ); if (*ctx->cur != ')') { report_error( ctx, "Expected `)'" ); return FALSE; } ctx->cur++; } return TRUE; } static boolean parse_opt_register_src_bracket( struct translate_ctx *ctx, struct parsed_bracket *brackets, int *parsed_brackets) { const char *cur = ctx->cur; *parsed_brackets = 0; eat_opt_white( &cur ); if (cur[0] == '[') { ++cur; ctx->cur = cur; if (!parse_register_bracket(ctx, brackets)) return FALSE; *parsed_brackets = 1; } return TRUE; } /* Parse source register operand. * <register_src> ::= <register_file_bracket_index> `]' | * <register_file_bracket> <register_dst> [`.' (`x' | `y' | `z' | `w')] `]' | * <register_file_bracket> <register_dst> [`.' (`x' | `y' | `z' | `w')] `+' <uint> `]' | * <register_file_bracket> <register_dst> [`.' (`x' | `y' | `z' | `w')] `-' <uint> `]' */ static boolean parse_register_src( struct translate_ctx *ctx, uint *file, struct parsed_bracket *brackets) { brackets->ind_comp = TGSI_SWIZZLE_X; if (!parse_register_file_bracket( ctx, file )) return FALSE; if (!parse_register_bracket( ctx, brackets )) return FALSE; return TRUE; } struct parsed_dcl_bracket { uint first; uint last; }; static boolean parse_register_dcl_bracket( struct translate_ctx *ctx, struct parsed_dcl_bracket *bracket) { uint uindex; memset(bracket, 0, sizeof(struct parsed_dcl_bracket)); eat_opt_white( &ctx->cur ); if (!parse_uint( &ctx->cur, &uindex )) { /* it can be an empty bracket [] which means its range * is from 0 to some implied size */ if (ctx->cur[0] == ']' && ctx->implied_array_size != 0) { bracket->first = 0; bracket->last = ctx->implied_array_size - 1; goto cleanup; } report_error( ctx, "Expected literal unsigned integer" ); return FALSE; } bracket->first = uindex; eat_opt_white( &ctx->cur ); if (ctx->cur[0] == '.' && ctx->cur[1] == '.') { uint uindex; ctx->cur += 2; eat_opt_white( &ctx->cur ); if (!parse_uint( &ctx->cur, &uindex )) { report_error( ctx, "Expected literal integer" ); return FALSE; } bracket->last = (int) uindex; eat_opt_white( &ctx->cur ); } else { bracket->last = bracket->first; } cleanup: if (*ctx->cur != ']') { report_error( ctx, "Expected `]' or `..'" ); return FALSE; } ctx->cur++; return TRUE; } /* Parse register declaration. * <register_dcl> ::= <register_file_bracket_index> `]' | * <register_file_bracket_index> `..' <index> `]' */ static boolean parse_register_dcl( struct translate_ctx *ctx, uint *file, struct parsed_dcl_bracket *brackets, int *num_brackets) { const char *cur; *num_brackets = 0; if (!parse_register_file_bracket( ctx, file )) return FALSE; if (!parse_register_dcl_bracket( ctx, &brackets[0] )) return FALSE; *num_brackets = 1; cur = ctx->cur; eat_opt_white( &cur ); if (cur[0] == '[') { bool is_in = *file == TGSI_FILE_INPUT; bool is_out = *file == TGSI_FILE_OUTPUT; ++cur; ctx->cur = cur; if (!parse_register_dcl_bracket( ctx, &brackets[1] )) return FALSE; /* for geometry shader we don't really care about * the first brackets it's always the size of the * input primitive. so we want to declare just * the index relevant to the semantics which is in * the second bracket */ /* tessellation has similar constraints to geometry shader */ if ((ctx->processor == PIPE_SHADER_GEOMETRY && is_in) || (ctx->processor == PIPE_SHADER_TESS_EVAL && is_in) || (ctx->processor == PIPE_SHADER_TESS_CTRL && (is_in || is_out))) { brackets[0] = brackets[1]; *num_brackets = 1; } else { *num_brackets = 2; } } return TRUE; } /* Parse destination register operand.*/ static boolean parse_register_dst( struct translate_ctx *ctx, uint *file, struct parsed_bracket *brackets) { brackets->ind_comp = TGSI_SWIZZLE_X; if (!parse_register_file_bracket( ctx, file )) return FALSE; if (!parse_register_bracket( ctx, brackets )) return FALSE; return TRUE; } static boolean parse_dst_operand( struct translate_ctx *ctx, struct tgsi_full_dst_register *dst ) { uint file; uint writemask; const char *cur; struct parsed_bracket bracket[2]; int parsed_opt_brackets; if (!parse_register_dst( ctx, &file, &bracket[0] )) return FALSE; if (!parse_opt_register_src_bracket(ctx, &bracket[1], &parsed_opt_brackets)) return FALSE; cur = ctx->cur; eat_opt_white( &cur ); if (!parse_opt_writemask( ctx, &writemask )) return FALSE; dst->Register.File = file; if (parsed_opt_brackets) { dst->Register.Dimension = 1; dst->Dimension.Indirect = 0; dst->Dimension.Dimension = 0; dst->Dimension.Index = bracket[0].index; if (bracket[0].ind_file != TGSI_FILE_NULL) { dst->Dimension.Indirect = 1; dst->DimIndirect.File = bracket[0].ind_file; dst->DimIndirect.Index = bracket[0].ind_index; dst->DimIndirect.Swizzle = bracket[0].ind_comp; dst->DimIndirect.ArrayID = bracket[0].ind_array; } bracket[0] = bracket[1]; } dst->Register.Index = bracket[0].index; dst->Register.WriteMask = writemask; if (bracket[0].ind_file != TGSI_FILE_NULL) { dst->Register.Indirect = 1; dst->Indirect.File = bracket[0].ind_file; dst->Indirect.Index = bracket[0].ind_index; dst->Indirect.Swizzle = bracket[0].ind_comp; dst->Indirect.ArrayID = bracket[0].ind_array; } return TRUE; } static boolean parse_optional_swizzle( struct translate_ctx *ctx, uint *swizzle, boolean *parsed_swizzle, int components) { const char *cur = ctx->cur; *parsed_swizzle = FALSE; eat_opt_white( &cur ); if (*cur == '.') { uint i; cur++; eat_opt_white( &cur ); for (i = 0; i < components; i++) { if (uprcase( *cur ) == 'X') swizzle[i] = TGSI_SWIZZLE_X; else if (uprcase( *cur ) == 'Y') swizzle[i] = TGSI_SWIZZLE_Y; else if (uprcase( *cur ) == 'Z') swizzle[i] = TGSI_SWIZZLE_Z; else if (uprcase( *cur ) == 'W') swizzle[i] = TGSI_SWIZZLE_W; else { report_error( ctx, "Expected register swizzle component `x', `y', `z' or `w'" ); return FALSE; } cur++; } *parsed_swizzle = TRUE; ctx->cur = cur; } return TRUE; } static boolean parse_src_operand( struct translate_ctx *ctx, struct tgsi_full_src_register *src ) { uint file; uint swizzle[4]; boolean parsed_swizzle; struct parsed_bracket bracket[2]; int parsed_opt_brackets; if (*ctx->cur == '-') { ctx->cur++; eat_opt_white( &ctx->cur ); src->Register.Negate = 1; } if (*ctx->cur == '|') { ctx->cur++; eat_opt_white( &ctx->cur ); src->Register.Absolute = 1; } if (!parse_register_src(ctx, &file, &bracket[0])) return FALSE; if (!parse_opt_register_src_bracket(ctx, &bracket[1], &parsed_opt_brackets)) return FALSE; src->Register.File = file; if (parsed_opt_brackets) { src->Register.Dimension = 1; src->Dimension.Indirect = 0; src->Dimension.Dimension = 0; src->Dimension.Index = bracket[0].index; if (bracket[0].ind_file != TGSI_FILE_NULL) { src->Dimension.Indirect = 1; src->DimIndirect.File = bracket[0].ind_file; src->DimIndirect.Index = bracket[0].ind_index; src->DimIndirect.Swizzle = bracket[0].ind_comp; src->DimIndirect.ArrayID = bracket[0].ind_array; } bracket[0] = bracket[1]; } src->Register.Index = bracket[0].index; if (bracket[0].ind_file != TGSI_FILE_NULL) { src->Register.Indirect = 1; src->Indirect.File = bracket[0].ind_file; src->Indirect.Index = bracket[0].ind_index; src->Indirect.Swizzle = bracket[0].ind_comp; src->Indirect.ArrayID = bracket[0].ind_array; } /* Parse optional swizzle. */ if (parse_optional_swizzle( ctx, swizzle, &parsed_swizzle, 4 )) { if (parsed_swizzle) { src->Register.SwizzleX = swizzle[0]; src->Register.SwizzleY = swizzle[1]; src->Register.SwizzleZ = swizzle[2]; src->Register.SwizzleW = swizzle[3]; } } if (src->Register.Absolute) { eat_opt_white( &ctx->cur ); if (*ctx->cur != '|') { report_error( ctx, "Expected `|'" ); return FALSE; } ctx->cur++; } return TRUE; } static boolean parse_texoffset_operand( struct translate_ctx *ctx, struct tgsi_texture_offset *src ) { uint file; uint swizzle[3]; boolean parsed_swizzle; struct parsed_bracket bracket; if (!parse_register_src(ctx, &file, &bracket)) return FALSE; src->File = file; src->Index = bracket.index; /* Parse optional swizzle. */ if (parse_optional_swizzle( ctx, swizzle, &parsed_swizzle, 3 )) { if (parsed_swizzle) { src->SwizzleX = swizzle[0]; src->SwizzleY = swizzle[1]; src->SwizzleZ = swizzle[2]; } } return TRUE; } static boolean match_inst(const char **pcur, unsigned *saturate, unsigned *precise, const struct tgsi_opcode_info *info) { const char *cur = *pcur; const char *mnemonic = tgsi_get_opcode_name(info->opcode); /* simple case: the whole string matches the instruction name */ if (str_match_nocase_whole(&cur, mnemonic)) { *pcur = cur; *saturate = 0; *precise = 0; return TRUE; } if (str_match_no_case(&cur, mnemonic)) { /* the instruction has a suffix, figure it out */ if (str_match_no_case(&cur, "_SAT")) { *pcur = cur; *saturate = 1; } if (str_match_no_case(&cur, "_PRECISE")) { *pcur = cur; *precise = 1; } if (!is_digit_alpha_underscore(cur)) return TRUE; } return FALSE; } static boolean parse_instruction( struct translate_ctx *ctx, boolean has_label ) { uint i; uint saturate = 0; uint precise = 0; const struct tgsi_opcode_info *info; struct tgsi_full_instruction inst; const char *cur; uint advance; inst = tgsi_default_full_instruction(); /* Parse instruction name. */ eat_opt_white( &ctx->cur ); for (i = 0; i < TGSI_OPCODE_LAST; i++) { cur = ctx->cur; info = tgsi_get_opcode_info( i ); if (match_inst(&cur, &saturate, &precise, info)) { if (info->num_dst + info->num_src + info->is_tex == 0) { ctx->cur = cur; break; } else if (*cur == '\0' || eat_white( &cur )) { ctx->cur = cur; break; } } } if (i == TGSI_OPCODE_LAST) { if (has_label) report_error( ctx, "Unknown opcode" ); else report_error( ctx, "Expected `DCL', `IMM' or a label" ); return FALSE; } inst.Instruction.Opcode = i; inst.Instruction.Saturate = saturate; inst.Instruction.Precise = precise; inst.Instruction.NumDstRegs = info->num_dst; inst.Instruction.NumSrcRegs = info->num_src; if (i >= TGSI_OPCODE_SAMPLE && i <= TGSI_OPCODE_GATHER4) { /* * These are not considered tex opcodes here (no additional * target argument) however we're required to set the Texture * bit so we can set the number of tex offsets. */ inst.Instruction.Texture = 1; inst.Texture.Texture = TGSI_TEXTURE_UNKNOWN; } if ((i >= TGSI_OPCODE_LOAD && i <= TGSI_OPCODE_ATOMIMAX) || i == TGSI_OPCODE_RESQ) { inst.Instruction.Memory = 1; inst.Memory.Qualifier = 0; } assume(info->num_dst <= TGSI_FULL_MAX_DST_REGISTERS); assume(info->num_src <= TGSI_FULL_MAX_SRC_REGISTERS); /* Parse instruction operands. */ for (i = 0; i < info->num_dst + info->num_src + info->is_tex; i++) { if (i > 0) { eat_opt_white( &ctx->cur ); if (*ctx->cur != ',') { report_error( ctx, "Expected `,'" ); return FALSE; } ctx->cur++; eat_opt_white( &ctx->cur ); } if (i < info->num_dst) { if (!parse_dst_operand( ctx, &inst.Dst[i] )) return FALSE; } else if (i < info->num_dst + info->num_src) { if (!parse_src_operand( ctx, &inst.Src[i - info->num_dst] )) return FALSE; } else { uint j; for (j = 0; j < TGSI_TEXTURE_COUNT; j++) { if (str_match_nocase_whole( &ctx->cur, tgsi_texture_names[j] )) { inst.Instruction.Texture = 1; inst.Texture.Texture = j; break; } } if (j == TGSI_TEXTURE_COUNT) { report_error( ctx, "Expected texture target" ); return FALSE; } } } cur = ctx->cur; eat_opt_white( &cur ); for (i = 0; inst.Instruction.Texture && *cur == ',' && i < TGSI_FULL_MAX_TEX_OFFSETS; i++) { cur++; eat_opt_white( &cur ); ctx->cur = cur; if (!parse_texoffset_operand( ctx, &inst.TexOffsets[i] )) return FALSE; cur = ctx->cur; eat_opt_white( &cur ); } inst.Texture.NumOffsets = i; cur = ctx->cur; eat_opt_white(&cur); for (; inst.Instruction.Memory && *cur == ','; ctx->cur = cur, eat_opt_white(&cur)) { int j; cur++; eat_opt_white(&cur); j = str_match_name_from_array(&cur, tgsi_memory_names, ARRAY_SIZE(tgsi_memory_names)); if (j >= 0) { inst.Memory.Qualifier |= 1U << j; continue; } j = str_match_name_from_array(&cur, tgsi_texture_names, ARRAY_SIZE(tgsi_texture_names)); if (j >= 0) { inst.Memory.Texture = j; continue; } j = str_match_format(&cur); if (j >= 0) { inst.Memory.Format = j; continue; } ctx->cur = cur; report_error(ctx, "Expected memory qualifier, texture target, or format\n"); return FALSE; } cur = ctx->cur; eat_opt_white( &cur ); if (info->is_branch && *cur == ':') { uint target; cur++; eat_opt_white( &cur ); if (!parse_uint( &cur, &target )) { report_error( ctx, "Expected a label" ); return FALSE; } inst.Instruction.Label = 1; inst.Label.Label = target; ctx->cur = cur; } advance = tgsi_build_full_instruction( &inst, ctx->tokens_cur, ctx->header, (uint) (ctx->tokens_end - ctx->tokens_cur) ); if (advance == 0) return FALSE; ctx->tokens_cur += advance; return TRUE; } /* parses a 4-touple of the form {x, y, z, w} * where x, y, z, w are numbers */ static boolean parse_immediate_data(struct translate_ctx *ctx, unsigned type, union tgsi_immediate_data *values) { unsigned i; int ret; eat_opt_white( &ctx->cur ); if (*ctx->cur != '{') { report_error( ctx, "Expected `{'" ); return FALSE; } ctx->cur++; for (i = 0; i < 4; i++) { eat_opt_white( &ctx->cur ); if (i > 0) { if (*ctx->cur != ',') { report_error( ctx, "Expected `,'" ); return FALSE; } ctx->cur++; eat_opt_white( &ctx->cur ); } switch (type) { case TGSI_IMM_FLOAT64: ret = parse_double(&ctx->cur, &values[i].Uint, &values[i+1].Uint); i++; break; case TGSI_IMM_INT64: ret = parse_int64(&ctx->cur, &values[i].Uint, &values[i+1].Uint); i++; break; case TGSI_IMM_UINT64: ret = parse_uint64(&ctx->cur, &values[i].Uint, &values[i+1].Uint); i++; break; case TGSI_IMM_FLOAT32: ret = parse_float(&ctx->cur, &values[i].Float); break; case TGSI_IMM_UINT32: ret = parse_uint(&ctx->cur, &values[i].Uint); break; case TGSI_IMM_INT32: ret = parse_int(&ctx->cur, &values[i].Int); break; default: assert(0); ret = FALSE; break; } if (!ret) { report_error( ctx, "Expected immediate constant" ); return FALSE; } } eat_opt_white( &ctx->cur ); if (*ctx->cur != '}') { report_error( ctx, "Expected `}'" ); return FALSE; } ctx->cur++; return TRUE; } static boolean parse_declaration( struct translate_ctx *ctx ) { struct tgsi_full_declaration decl; uint file; struct parsed_dcl_bracket brackets[2]; int num_brackets; uint writemask; const char *cur, *cur2; uint advance; boolean is_vs_input; if (!eat_white( &ctx->cur )) { report_error( ctx, "Syntax error" ); return FALSE; } if (!parse_register_dcl( ctx, &file, brackets, &num_brackets)) return FALSE; if (!parse_opt_writemask( ctx, &writemask )) return FALSE; decl = tgsi_default_full_declaration(); decl.Declaration.File = file; decl.Declaration.UsageMask = writemask; if (num_brackets == 1) { decl.Range.First = brackets[0].first; decl.Range.Last = brackets[0].last; } else { decl.Range.First = brackets[1].first; decl.Range.Last = brackets[1].last; decl.Declaration.Dimension = 1; decl.Dim.Index2D = brackets[0].first; } is_vs_input = (file == TGSI_FILE_INPUT && ctx->processor == PIPE_SHADER_VERTEX); cur = ctx->cur; eat_opt_white( &cur ); if (*cur == ',') { cur2 = cur; cur2++; eat_opt_white( &cur2 ); if (str_match_nocase_whole( &cur2, "ARRAY" )) { int arrayid; if (*cur2 != '(') { report_error( ctx, "Expected `('" ); return FALSE; } cur2++; eat_opt_white( &cur2 ); if (!parse_int( &cur2, &arrayid )) { report_error( ctx, "Expected `,'" ); return FALSE; } eat_opt_white( &cur2 ); if (*cur2 != ')') { report_error( ctx, "Expected `)'" ); return FALSE; } cur2++; decl.Declaration.Array = 1; decl.Array.ArrayID = arrayid; ctx->cur = cur = cur2; } } if (*cur == ',' && !is_vs_input) { uint i, j; cur++; eat_opt_white( &cur ); if (file == TGSI_FILE_IMAGE) { for (i = 0; i < TGSI_TEXTURE_COUNT; i++) { if (str_match_nocase_whole(&cur, tgsi_texture_names[i])) { decl.Image.Resource = i; break; } } if (i == TGSI_TEXTURE_COUNT) { report_error(ctx, "Expected texture target"); return FALSE; } cur2 = cur; eat_opt_white(&cur2); while (*cur2 == ',') { cur2++; eat_opt_white(&cur2); if (str_match_nocase_whole(&cur2, "RAW")) { decl.Image.Raw = 1; } else if (str_match_nocase_whole(&cur2, "WR")) { decl.Image.Writable = 1; } else { int format = str_match_format(&cur2); if (format < 0) break; decl.Image.Format = format; } cur = cur2; eat_opt_white(&cur2); } ctx->cur = cur; } else if (file == TGSI_FILE_SAMPLER_VIEW) { for (i = 0; i < TGSI_TEXTURE_COUNT; i++) { if (str_match_nocase_whole(&cur, tgsi_texture_names[i])) { decl.SamplerView.Resource = i; break; } } if (i == TGSI_TEXTURE_COUNT) { report_error(ctx, "Expected texture target"); return FALSE; } eat_opt_white( &cur ); if (*cur != ',') { report_error( ctx, "Expected `,'" ); return FALSE; } ++cur; eat_opt_white( &cur ); for (j = 0; j < 4; ++j) { for (i = 0; i < TGSI_RETURN_TYPE_COUNT; ++i) { if (str_match_nocase_whole(&cur, tgsi_return_type_names[i])) { switch (j) { case 0: decl.SamplerView.ReturnTypeX = i; break; case 1: decl.SamplerView.ReturnTypeY = i; break; case 2: decl.SamplerView.ReturnTypeZ = i; break; case 3: decl.SamplerView.ReturnTypeW = i; break; default: assert(0); } break; } } if (i == TGSI_RETURN_TYPE_COUNT) { if (j == 0 || j > 2) { report_error(ctx, "Expected type name"); return FALSE; } break; } else { cur2 = cur; eat_opt_white( &cur2 ); if (*cur2 == ',') { cur2++; eat_opt_white( &cur2 ); cur = cur2; continue; } else break; } } if (j < 4) { decl.SamplerView.ReturnTypeY = decl.SamplerView.ReturnTypeZ = decl.SamplerView.ReturnTypeW = decl.SamplerView.ReturnTypeX; } ctx->cur = cur; } else if (file == TGSI_FILE_BUFFER) { if (str_match_nocase_whole(&cur, "ATOMIC")) { decl.Declaration.Atomic = 1; ctx->cur = cur; } } else if (file == TGSI_FILE_MEMORY) { if (str_match_nocase_whole(&cur, "GLOBAL")) { /* Note this is a no-op global is the default */ decl.Declaration.MemType = TGSI_MEMORY_TYPE_GLOBAL; ctx->cur = cur; } else if (str_match_nocase_whole(&cur, "SHARED")) { decl.Declaration.MemType = TGSI_MEMORY_TYPE_SHARED; ctx->cur = cur; } else if (str_match_nocase_whole(&cur, "PRIVATE")) { decl.Declaration.MemType = TGSI_MEMORY_TYPE_PRIVATE; ctx->cur = cur; } else if (str_match_nocase_whole(&cur, "INPUT")) { decl.Declaration.MemType = TGSI_MEMORY_TYPE_INPUT; ctx->cur = cur; } } else { if (str_match_nocase_whole(&cur, "LOCAL")) { decl.Declaration.Local = 1; ctx->cur = cur; } cur = ctx->cur; eat_opt_white( &cur ); if (*cur == ',') { cur++; eat_opt_white( &cur ); for (i = 0; i < TGSI_SEMANTIC_COUNT; i++) { if (str_match_nocase_whole(&cur, tgsi_semantic_names[i])) { uint index; cur2 = cur; eat_opt_white( &cur2 ); if (*cur2 == '[') { cur2++; eat_opt_white( &cur2 ); if (!parse_uint( &cur2, &index )) { report_error( ctx, "Expected literal integer" ); return FALSE; } eat_opt_white( &cur2 ); if (*cur2 != ']') { report_error( ctx, "Expected `]'" ); return FALSE; } cur2++; decl.Semantic.Index = index; cur = cur2; } decl.Declaration.Semantic = 1; decl.Semantic.Name = i; ctx->cur = cur; break; } } } } } cur = ctx->cur; eat_opt_white( &cur ); if (*cur == ',' && file == TGSI_FILE_OUTPUT && ctx->processor == PIPE_SHADER_GEOMETRY) { cur++; eat_opt_white(&cur); if (str_match_nocase_whole(&cur, "STREAM")) { uint stream[4]; eat_opt_white(&cur); if (*cur != '(') { report_error(ctx, "Expected '('"); return FALSE; } cur++; for (int i = 0; i < 4; ++i) { eat_opt_white(&cur); if (!parse_uint(&cur, &stream[i])) { report_error(ctx, "Expected literal integer"); return FALSE; } eat_opt_white(&cur); if (i < 3) { if (*cur != ',') { report_error(ctx, "Expected ','"); return FALSE; } cur++; } } if (*cur != ')') { report_error(ctx, "Expected ')'"); return FALSE; } cur++; decl.Semantic.StreamX = stream[0]; decl.Semantic.StreamY = stream[1]; decl.Semantic.StreamZ = stream[2]; decl.Semantic.StreamW = stream[3]; ctx->cur = cur; } } cur = ctx->cur; eat_opt_white( &cur ); if (*cur == ',' && !is_vs_input) { uint i; cur++; eat_opt_white( &cur ); for (i = 0; i < TGSI_INTERPOLATE_COUNT; i++) { if (str_match_nocase_whole( &cur, tgsi_interpolate_names[i] )) { decl.Declaration.Interpolate = 1; decl.Interp.Interpolate = i; ctx->cur = cur; break; } } if (i == TGSI_INTERPOLATE_COUNT) { report_error( ctx, "Expected semantic or interpolate attribute" ); return FALSE; } } cur = ctx->cur; eat_opt_white( &cur ); if (*cur == ',' && !is_vs_input) { uint i; cur++; eat_opt_white( &cur ); for (i = 0; i < TGSI_INTERPOLATE_LOC_COUNT; i++) { if (str_match_nocase_whole( &cur, tgsi_interpolate_locations[i] )) { decl.Interp.Location = i; ctx->cur = cur; break; } } } advance = tgsi_build_full_declaration( &decl, ctx->tokens_cur, ctx->header, (uint) (ctx->tokens_end - ctx->tokens_cur) ); if (advance == 0) return FALSE; ctx->tokens_cur += advance; return TRUE; } static boolean parse_immediate( struct translate_ctx *ctx ) { struct tgsi_full_immediate imm; uint advance; int type; if (*ctx->cur == '[') { uint uindex; ++ctx->cur; eat_opt_white( &ctx->cur ); if (!parse_uint( &ctx->cur, &uindex )) { report_error( ctx, "Expected literal unsigned integer" ); return FALSE; } if (uindex != ctx->num_immediates) { report_error( ctx, "Immediates must be sorted" ); return FALSE; } eat_opt_white( &ctx->cur ); if (*ctx->cur != ']') { report_error( ctx, "Expected `]'" ); return FALSE; } ctx->cur++; } if (!eat_white( &ctx->cur )) { report_error( ctx, "Syntax error" ); return FALSE; } for (type = 0; type < ARRAY_SIZE(tgsi_immediate_type_names); ++type) { if (str_match_nocase_whole(&ctx->cur, tgsi_immediate_type_names[type])) break; } if (type == ARRAY_SIZE(tgsi_immediate_type_names)) { report_error( ctx, "Expected immediate type" ); return FALSE; } imm = tgsi_default_full_immediate(); imm.Immediate.NrTokens += 4; imm.Immediate.DataType = type; parse_immediate_data(ctx, type, imm.u); advance = tgsi_build_full_immediate( &imm, ctx->tokens_cur, ctx->header, (uint) (ctx->tokens_end - ctx->tokens_cur) ); if (advance == 0) return FALSE; ctx->tokens_cur += advance; ctx->num_immediates++; return TRUE; } static boolean parse_primitive( const char **pcur, uint *primitive ) { uint i; for (i = 0; i < PIPE_PRIM_MAX; i++) { const char *cur = *pcur; if (str_match_nocase_whole( &cur, tgsi_primitive_names[i])) { *primitive = i; *pcur = cur; return TRUE; } } return FALSE; } static boolean parse_fs_coord_origin( const char **pcur, uint *fs_coord_origin ) { uint i; for (i = 0; i < ARRAY_SIZE(tgsi_fs_coord_origin_names); i++) { const char *cur = *pcur; if (str_match_nocase_whole( &cur, tgsi_fs_coord_origin_names[i])) { *fs_coord_origin = i; *pcur = cur; return TRUE; } } return FALSE; } static boolean parse_fs_coord_pixel_center( const char **pcur, uint *fs_coord_pixel_center ) { uint i; for (i = 0; i < ARRAY_SIZE(tgsi_fs_coord_pixel_center_names); i++) { const char *cur = *pcur; if (str_match_nocase_whole( &cur, tgsi_fs_coord_pixel_center_names[i])) { *fs_coord_pixel_center = i; *pcur = cur; return TRUE; } } return FALSE; } static boolean parse_property_next_shader( const char **pcur, uint *next_shader ) { uint i; for (i = 0; i < ARRAY_SIZE(tgsi_processor_type_names); i++) { const char *cur = *pcur; if (str_match_nocase_whole( &cur, tgsi_processor_type_names[i])) { *next_shader = i; *pcur = cur; return TRUE; } } return FALSE; } static boolean parse_property( struct translate_ctx *ctx ) { struct tgsi_full_property prop; uint property_name; uint values[8]; uint advance; char id[64]; if (!eat_white( &ctx->cur )) { report_error( ctx, "Syntax error" ); return FALSE; } if (!parse_identifier( &ctx->cur, id, sizeof(id) )) { report_error( ctx, "Syntax error" ); return FALSE; } for (property_name = 0; property_name < TGSI_PROPERTY_COUNT; ++property_name) { if (streq_nocase_uprcase(tgsi_property_names[property_name], id)) { break; } } if (property_name >= TGSI_PROPERTY_COUNT) { debug_printf( "\nError: Unknown property : '%s'", id ); return FALSE; } eat_opt_white( &ctx->cur ); switch(property_name) { case TGSI_PROPERTY_GS_INPUT_PRIM: case TGSI_PROPERTY_GS_OUTPUT_PRIM: if (!parse_primitive(&ctx->cur, &values[0] )) { report_error( ctx, "Unknown primitive name as property!" ); return FALSE; } if (property_name == TGSI_PROPERTY_GS_INPUT_PRIM && ctx->processor == PIPE_SHADER_GEOMETRY) { ctx->implied_array_size = u_vertices_per_prim(values[0]); } break; case TGSI_PROPERTY_FS_COORD_ORIGIN: if (!parse_fs_coord_origin(&ctx->cur, &values[0] )) { report_error( ctx, "Unknown coord origin as property: must be UPPER_LEFT or LOWER_LEFT!" ); return FALSE; } break; case TGSI_PROPERTY_FS_COORD_PIXEL_CENTER: if (!parse_fs_coord_pixel_center(&ctx->cur, &values[0] )) { report_error( ctx, "Unknown coord pixel center as property: must be HALF_INTEGER or INTEGER!" ); return FALSE; } break; case TGSI_PROPERTY_NEXT_SHADER: if (!parse_property_next_shader(&ctx->cur, &values[0] )) { report_error( ctx, "Unknown next shader property value." ); return FALSE; } break; case TGSI_PROPERTY_FS_COLOR0_WRITES_ALL_CBUFS: default: if (!parse_uint(&ctx->cur, &values[0] )) { report_error( ctx, "Expected unsigned integer as property!" ); return FALSE; } } prop = tgsi_default_full_property(); prop.Property.PropertyName = property_name; prop.Property.NrTokens += 1; prop.u[0].Data = values[0]; advance = tgsi_build_full_property( &prop, ctx->tokens_cur, ctx->header, (uint) (ctx->tokens_end - ctx->tokens_cur) ); if (advance == 0) return FALSE; ctx->tokens_cur += advance; return TRUE; } static boolean translate( struct translate_ctx *ctx ) { eat_opt_white( &ctx->cur ); if (!parse_header( ctx )) return FALSE; if (ctx->processor == PIPE_SHADER_TESS_CTRL || ctx->processor == PIPE_SHADER_TESS_EVAL) ctx->implied_array_size = 32; while (*ctx->cur != '\0') { uint label_val = 0; if (!eat_white( &ctx->cur )) { report_error( ctx, "Syntax error" ); return FALSE; } if (*ctx->cur == '\0') break; if (parse_label( ctx, &label_val )) { if (!parse_instruction( ctx, TRUE )) return FALSE; } else if (str_match_nocase_whole( &ctx->cur, "DCL" )) { if (!parse_declaration( ctx )) return FALSE; } else if (str_match_nocase_whole( &ctx->cur, "IMM" )) { if (!parse_immediate( ctx )) return FALSE; } else if (str_match_nocase_whole( &ctx->cur, "PROPERTY" )) { if (!parse_property( ctx )) return FALSE; } else if (!parse_instruction( ctx, FALSE )) { return FALSE; } } return TRUE; } boolean tgsi_text_translate( const char *text, struct tgsi_token *tokens, uint num_tokens ) { struct translate_ctx ctx = {0}; ctx.text = text; ctx.cur = text; ctx.tokens = tokens; ctx.tokens_cur = tokens; ctx.tokens_end = tokens + num_tokens; if (!translate( &ctx )) return FALSE; return tgsi_sanity_check( tokens ); }