/* Table of relaxations for Xtensa assembly. Copyright (C) 2003-2021 Free Software Foundation, Inc. This file is part of GAS, the GNU Assembler. GAS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GAS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GAS; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ /* This file contains the code for generating runtime data structures for relaxation pattern matching from statically specified strings. Each action contains an instruction pattern to match and preconditions for the match as well as an expansion if the pattern matches. The preconditions can specify that two operands are the same or an operand is a specific constant or register. The expansion uses the bound variables from the pattern to specify that specific operands from the pattern should be used in the result. The code determines whether the condition applies to a constant or a register depending on the type of the operand. You may get unexpected results if you don't match the rule against the operand type correctly. The patterns match a language like: INSN_PATTERN ::= INSN_TEMPL ( '|' PRECOND )* ( '?' OPTIONPRED )* INSN_TEMPL ::= OPCODE ' ' [ OPERAND (',' OPERAND)* ] OPCODE ::= id OPERAND ::= CONSTANT | VARIABLE | SPECIALFN '(' VARIABLE ')' SPECIALFN ::= 'HI24S' | 'F32MINUS' | 'LOW8' | 'HI16' | 'LOW16' VARIABLE ::= '%' id PRECOND ::= OPERAND CMPOP OPERAND CMPOP ::= '==' | '!=' OPTIONPRED ::= OPTIONNAME ('+' OPTIONNAME) OPTIONNAME ::= '"' id '"' The replacement language INSN_REPL ::= INSN_LABEL_LIT ( ';' INSN_LABEL_LIT )* INSN_LABEL_LIT ::= INSN_TEMPL | 'LABEL' | 'LITERAL' VARIABLE The operands in a PRECOND must be constants or variables bound by the INSN_PATTERN. The configuration options define a predicate on the availability of options which must be TRUE for this rule to be valid. Examples are requiring "density" for replacements with density instructions, requiring "const16" for replacements that require const16 instructions, etc. The names are interpreted by the assembler to a truth value for a particular frag. The operands in the INSN_REPL must be constants, variables bound in the associated INSN_PATTERN, special variables that are bound in the INSN_REPL by LABEL or LITERAL definitions, or special value manipulation functions. A simple example of a replacement pattern: {"movi.n %as,%imm", "movi %as,%imm"} would convert the narrow movi.n instruction to the wide movi instruction. A more complex example of a branch around: {"beqz %as,%label", "bnez %as,%LABEL;j %label;LABEL"} would convert a branch to a negated branch to the following instruction with a jump to the original label. An Xtensa-specific example that generates a literal: {"movi %at,%imm", "LITERAL %imm; l32r %at,%LITERAL"} will convert a movi instruction to an l32r of a literal literal defined in the literal pool. Even more complex is a conversion of a load with immediate offset to a load of a freshly generated literal, an explicit add and a load with 0 offset. This transformation is only valid, though when the first and second operands are not the same as specified by the "| %at!=%as" precondition clause. {"l32i %at,%as,%imm | %at!=%as", "LITERAL %imm; l32r %at,%LITERAL; add %at,%at,%as; l32i %at,%at,0"} */ #include "as.h" #include "xtensa-isa.h" #include "xtensa-relax.h" #include #include "xtensa-config.h" #ifndef XCHAL_HAVE_WIDE_BRANCHES #define XCHAL_HAVE_WIDE_BRANCHES 0 #endif /* Imported from bfd. */ extern xtensa_isa xtensa_default_isa; /* The opname_list is a small list of names that we use for opcode and operand variable names to simplify ownership of these commonly used strings. Strings entered in the table can be compared by pointer equality. */ typedef struct opname_list_struct opname_list; typedef opname_list opname_e; struct opname_list_struct { char *opname; opname_list *next; }; static opname_list *local_opnames = NULL; /* The "opname_map" and its element structure "opname_map_e" are used for binding an operand number to a name or a constant. */ typedef struct opname_map_e_struct opname_map_e; typedef struct opname_map_struct opname_map; struct opname_map_e_struct { const char *operand_name; /* If null, then use constant_value. */ int operand_num; unsigned constant_value; opname_map_e *next; }; struct opname_map_struct { opname_map_e *head; opname_map_e **tail; }; /* The "precond_list" and its element structure "precond_e" represents explicit preconditions comparing operand variables and constants. In the "precond_e" structure, a variable is identified by the name in the "opname" field. If that field is NULL, then the operand is the constant in field "opval". */ typedef struct precond_e_struct precond_e; typedef struct precond_list_struct precond_list; struct precond_e_struct { const char *opname1; unsigned opval1; CmpOp cmpop; const char *opname2; unsigned opval2; precond_e *next; }; struct precond_list_struct { precond_e *head; precond_e **tail; }; /* The insn_templ represents the INSN_TEMPL instruction template. It is an opcode name with a list of operands. These are used for instruction patterns and replacement patterns. */ typedef struct insn_templ_struct insn_templ; struct insn_templ_struct { const char *opcode_name; opname_map operand_map; }; /* The insn_pattern represents an INSN_PATTERN instruction pattern. It is an instruction template with preconditions that specify when it actually matches a given instruction. */ typedef struct insn_pattern_struct insn_pattern; struct insn_pattern_struct { insn_templ t; precond_list preconds; ReqOptionList *options; }; /* The "insn_repl" and associated element structure "insn_repl_e" instruction replacement list is a list of instructions/LITERALS/LABELS with constant operands or operands with names bound to the operand names in the associated pattern. */ typedef struct insn_repl_e_struct insn_repl_e; struct insn_repl_e_struct { insn_templ t; insn_repl_e *next; }; typedef struct insn_repl_struct insn_repl; struct insn_repl_struct { insn_repl_e *head; insn_repl_e **tail; }; /* The split_rec is a vector of allocated char * pointers. */ typedef struct split_rec_struct split_rec; struct split_rec_struct { char **vec; int count; }; /* The "string_pattern_pair" is a set of pairs containing instruction patterns and replacement strings. */ typedef struct string_pattern_pair_struct string_pattern_pair; struct string_pattern_pair_struct { const char *pattern; const char *replacement; }; /* The widen_spec_list is a list of valid substitutions that generate wider representations. These are generally used to specify replacements for instructions whose immediates do not fit their encodings. A valid transition may require multiple steps of one-to-one instruction replacements with a final multiple instruction replacement. As an example, here are the transitions required to replace an 'addi.n' with an 'addi', 'addmi'. addi.n a4, 0x1010 => addi a4, 0x1010 => addmi a4, 0x1010 => addmi a4, 0x1000, addi a4, 0x10. See the comments in xg_assembly_relax for some important details regarding how these chains must be built. */ static string_pattern_pair widen_spec_list[] = { {"add.n %ar,%as,%at ? IsaUseDensityInstruction", "add %ar,%as,%at"}, {"addi.n %ar,%as,%imm ? IsaUseDensityInstruction", "addi %ar,%as,%imm"}, {"beqz.n %as,%label ? IsaUseDensityInstruction", "beqz %as,%label"}, {"bnez.n %as,%label ? IsaUseDensityInstruction", "bnez %as,%label"}, {"l32i.n %at,%as,%imm ? IsaUseDensityInstruction", "l32i %at,%as,%imm"}, {"mov.n %at,%as ? IsaUseDensityInstruction", "or %at,%as,%as"}, {"movi.n %as,%imm ? IsaUseDensityInstruction", "movi %as,%imm"}, {"nop.n ? IsaUseDensityInstruction ? realnop", "nop"}, {"nop.n ? IsaUseDensityInstruction ? no-realnop", "or 1,1,1"}, {"ret.n %as ? IsaUseDensityInstruction", "ret %as"}, {"retw.n %as ? IsaUseDensityInstruction", "retw %as"}, {"s32i.n %at,%as,%imm ? IsaUseDensityInstruction", "s32i %at,%as,%imm"}, {"srli %at,%as,%imm", "extui %at,%as,%imm,F32MINUS(%imm)"}, {"slli %ar,%as,0", "or %ar,%as,%as"}, /* Widening with literals or const16. */ {"movi %at,%imm ? IsaUseL32R ", "LITERAL %imm; l32r %at,%LITERAL"}, {"movi %at,%imm ? IsaUseConst16", "const16 %at,HI16U(%imm); const16 %at,LOW16U(%imm)"}, {"addi %ar,%as,%imm", "addmi %ar,%as,%imm"}, /* LOW8 is the low 8 bits of the Immed MID8S is the middle 8 bits of the Immed */ {"addmi %ar,%as,%imm", "addmi %ar,%as,HI24S(%imm); addi %ar,%ar,LOW8(%imm)"}, /* In the end convert to either an l32r or const16. */ {"addmi %ar,%as,%imm | %ar!=%as ? IsaUseL32R", "LITERAL %imm; l32r %ar,%LITERAL; add %ar,%as,%ar"}, {"addmi %ar,%as,%imm | %ar!=%as ? IsaUseConst16", "const16 %ar,HI16U(%imm); const16 %ar,LOW16U(%imm); add %ar,%as,%ar"}, /* Widening the load instructions with too-large immediates */ {"l8ui %at,%as,%imm | %at!=%as ? IsaUseL32R", "LITERAL %imm; l32r %at,%LITERAL; add %at,%at,%as; l8ui %at,%at,0"}, {"l16si %at,%as,%imm | %at!=%as ? IsaUseL32R", "LITERAL %imm; l32r %at,%LITERAL; add %at,%at,%as; l16si %at,%at,0"}, {"l16ui %at,%as,%imm | %at!=%as ? IsaUseL32R", "LITERAL %imm; l32r %at,%LITERAL; add %at,%at,%as; l16ui %at,%at,0"}, {"l32i %at,%as,%imm | %at!=%as ? IsaUseL32R", "LITERAL %imm; l32r %at,%LITERAL; add %at,%at,%as; l32i %at,%at,0"}, /* Widening load instructions with const16s. */ {"l8ui %at,%as,%imm | %at!=%as ? IsaUseConst16", "const16 %at,HI16U(%imm); const16 %at,LOW16U(%imm); add %at,%at,%as; l8ui %at,%at,0"}, {"l16si %at,%as,%imm | %at!=%as ? IsaUseConst16", "const16 %at,HI16U(%imm); const16 %at,LOW16U(%imm); add %at,%at,%as; l16si %at,%at,0"}, {"l16ui %at,%as,%imm | %at!=%as ? IsaUseConst16", "const16 %at,HI16U(%imm); const16 %at,LOW16U(%imm); add %at,%at,%as; l16ui %at,%at,0"}, {"l32i %at,%as,%imm | %at!=%as ? IsaUseConst16", "const16 %at,HI16U(%imm); const16 %at,LOW16U(%imm); add %at,%at,%as; l32i %at,%at,0"}, /* Widening loops with literals. */ {"loop %as,%label | %as!=1 ? IsaUseLoops ? IsaUseL32R", "loop %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "LITERAL %label;" "l32r %as, %LITERAL;" "nop;" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, {"loopgtz %as,%label | %as!=1 ? IsaUseLoops ? IsaUseL32R", "beqz %as,%label;" "bltz %as,%label;" "loopgtz %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "LITERAL %label;" "l32r %as, %LITERAL;" "nop;" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, {"loopnez %as,%label | %as!=1 ? IsaUseLoops ? IsaUseL32R", "beqz %as,%label;" "loopnez %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "LITERAL %label;" "l32r %as, %LITERAL;" "nop;" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, /* Widening loops with const16. */ {"loop %as,%label | %as!=1 ? IsaUseLoops ? IsaUseConst16", "loop %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "const16 %as,HI16U(%label);" "const16 %as,LOW16U(%label);" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, {"loopgtz %as,%label | %as!=1 ? IsaUseLoops ? IsaUseConst16", "beqz %as,%label;" "bltz %as,%label;" "loopgtz %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "const16 %as,HI16U(%label);" "const16 %as,LOW16U(%label);" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, {"loopnez %as,%label | %as!=1 ? IsaUseLoops ? IsaUseConst16", "beqz %as,%label;" "loopnez %as,%LABEL;" "rsr.lend %as;" /* LEND */ "wsr.lbeg %as;" /* LBEG */ "const16 %as,HI16U(%label);" "const16 %as,LOW16U(%label);" "wsr.lend %as;" "isync;" "rsr.lcount %as;" /* LCOUNT */ "addi %as, %as, 1;" "LABEL"}, /* Relaxing to wide branches. Order is important here. With wide branches, there is more than one correct relaxation for an out-of-range branch. Put the wide branch relaxations first in the table since they are more efficient than the branch-around relaxations. */ {"beqz %as,%label ? IsaUseWideBranches", "WIDE.beqz %as,%label"}, {"bnez %as,%label ? IsaUseWideBranches", "WIDE.bnez %as,%label"}, {"bgez %as,%label ? IsaUseWideBranches", "WIDE.bgez %as,%label"}, {"bltz %as,%label ? IsaUseWideBranches", "WIDE.bltz %as,%label"}, {"beqi %as,%imm,%label ? IsaUseWideBranches", "WIDE.beqi %as,%imm,%label"}, {"bnei %as,%imm,%label ? IsaUseWideBranches", "WIDE.bnei %as,%imm,%label"}, {"bgei %as,%imm,%label ? IsaUseWideBranches", "WIDE.bgei %as,%imm,%label"}, {"blti %as,%imm,%label ? IsaUseWideBranches", "WIDE.blti %as,%imm,%label"}, {"bgeui %as,%imm,%label ? IsaUseWideBranches", "WIDE.bgeui %as,%imm,%label"}, {"bltui %as,%imm,%label ? IsaUseWideBranches", "WIDE.bltui %as,%imm,%label"}, {"bbci %as,%imm,%label ? IsaUseWideBranches", "WIDE.bbci %as,%imm,%label"}, {"bbsi %as,%imm,%label ? IsaUseWideBranches", "WIDE.bbsi %as,%imm,%label"}, {"beq %as,%at,%label ? IsaUseWideBranches", "WIDE.beq %as,%at,%label"}, {"bne %as,%at,%label ? IsaUseWideBranches", "WIDE.bne %as,%at,%label"}, {"bge %as,%at,%label ? IsaUseWideBranches", "WIDE.bge %as,%at,%label"}, {"blt %as,%at,%label ? IsaUseWideBranches", "WIDE.blt %as,%at,%label"}, {"bgeu %as,%at,%label ? IsaUseWideBranches", "WIDE.bgeu %as,%at,%label"}, {"bltu %as,%at,%label ? IsaUseWideBranches", "WIDE.bltu %as,%at,%label"}, {"bany %as,%at,%label ? IsaUseWideBranches", "WIDE.bany %as,%at,%label"}, {"bnone %as,%at,%label ? IsaUseWideBranches", "WIDE.bnone %as,%at,%label"}, {"ball %as,%at,%label ? IsaUseWideBranches", "WIDE.ball %as,%at,%label"}, {"bnall %as,%at,%label ? IsaUseWideBranches", "WIDE.bnall %as,%at,%label"}, {"bbc %as,%at,%label ? IsaUseWideBranches", "WIDE.bbc %as,%at,%label"}, {"bbs %as,%at,%label ? IsaUseWideBranches", "WIDE.bbs %as,%at,%label"}, /* Widening branch comparisons eq/ne to zero. Prefer relaxing to narrow branches if the density option is available. */ {"beqz %as,%label ? IsaUseDensityInstruction", "bnez.n %as,%LABEL;j %label;LABEL"}, {"bnez %as,%label ? IsaUseDensityInstruction", "beqz.n %as,%LABEL;j %label;LABEL"}, {"beqz %as,%label", "bnez %as,%LABEL;j %label;LABEL"}, {"bnez %as,%label", "beqz %as,%LABEL;j %label;LABEL"}, {"WIDE.beqz %as,%label ? IsaUseDensityInstruction", "bnez.n %as,%LABEL;j %label;LABEL"}, {"WIDE.bnez %as,%label ? IsaUseDensityInstruction", "beqz.n %as,%LABEL;j %label;LABEL"}, {"WIDE.beqz %as,%label", "bnez %as,%LABEL;j %label;LABEL"}, {"WIDE.bnez %as,%label", "beqz %as,%LABEL;j %label;LABEL"}, /* Widening expect-taken branches. */ {"beqzt %as,%label ? IsaUsePredictedBranches", "bnez %as,%LABEL;j %label;LABEL"}, {"bnezt %as,%label ? IsaUsePredictedBranches", "beqz %as,%LABEL;j %label;LABEL"}, {"beqt %as,%at,%label ? IsaUsePredictedBranches", "bne %as,%at,%LABEL;j %label;LABEL"}, {"bnet %as,%at,%label ? IsaUsePredictedBranches", "beq %as,%at,%LABEL;j %label;LABEL"}, /* Widening branches from the Xtensa boolean option. */ {"bt %bs,%label ? IsaUseBooleans", "bf %bs,%LABEL;j %label;LABEL"}, {"bf %bs,%label ? IsaUseBooleans", "bt %bs,%LABEL;j %label;LABEL"}, /* Other branch-around-jump widenings. */ {"bgez %as,%label", "bltz %as,%LABEL;j %label;LABEL"}, {"bltz %as,%label", "bgez %as,%LABEL;j %label;LABEL"}, {"beqi %as,%imm,%label", "bnei %as,%imm,%LABEL;j %label;LABEL"}, {"bnei %as,%imm,%label", "beqi %as,%imm,%LABEL;j %label;LABEL"}, {"bgei %as,%imm,%label", "blti %as,%imm,%LABEL;j %label;LABEL"}, {"blti %as,%imm,%label", "bgei %as,%imm,%LABEL;j %label;LABEL"}, {"bgeui %as,%imm,%label", "bltui %as,%imm,%LABEL;j %label;LABEL"}, {"bltui %as,%imm,%label", "bgeui %as,%imm,%LABEL;j %label;LABEL"}, {"bbci %as,%imm,%label", "bbsi %as,%imm,%LABEL;j %label;LABEL"}, {"bbsi %as,%imm,%label", "bbci %as,%imm,%LABEL;j %label;LABEL"}, {"beq %as,%at,%label", "bne %as,%at,%LABEL;j %label;LABEL"}, {"bne %as,%at,%label", "beq %as,%at,%LABEL;j %label;LABEL"}, {"bge %as,%at,%label", "blt %as,%at,%LABEL;j %label;LABEL"}, {"blt %as,%at,%label", "bge %as,%at,%LABEL;j %label;LABEL"}, {"bgeu %as,%at,%label", "bltu %as,%at,%LABEL;j %label;LABEL"}, {"bltu %as,%at,%label", "bgeu %as,%at,%LABEL;j %label;LABEL"}, {"bany %as,%at,%label", "bnone %as,%at,%LABEL;j %label;LABEL"}, {"bnone %as,%at,%label", "bany %as,%at,%LABEL;j %label;LABEL"}, {"ball %as,%at,%label", "bnall %as,%at,%LABEL;j %label;LABEL"}, {"bnall %as,%at,%label", "ball %as,%at,%LABEL;j %label;LABEL"}, {"bbc %as,%at,%label", "bbs %as,%at,%LABEL;j %label;LABEL"}, {"bbs %as,%at,%label", "bbc %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bgez %as,%label", "bltz %as,%LABEL;j %label;LABEL"}, {"WIDE.bltz %as,%label", "bgez %as,%LABEL;j %label;LABEL"}, {"WIDE.beqi %as,%imm,%label", "bnei %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bnei %as,%imm,%label", "beqi %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bgei %as,%imm,%label", "blti %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.blti %as,%imm,%label", "bgei %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bgeui %as,%imm,%label", "bltui %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bltui %as,%imm,%label", "bgeui %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bbci %as,%imm,%label", "bbsi %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.bbsi %as,%imm,%label", "bbci %as,%imm,%LABEL;j %label;LABEL"}, {"WIDE.beq %as,%at,%label", "bne %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bne %as,%at,%label", "beq %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bge %as,%at,%label", "blt %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.blt %as,%at,%label", "bge %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bgeu %as,%at,%label", "bltu %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bltu %as,%at,%label", "bgeu %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bany %as,%at,%label", "bnone %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bnone %as,%at,%label", "bany %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.ball %as,%at,%label", "bnall %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bnall %as,%at,%label", "ball %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bbc %as,%at,%label", "bbs %as,%at,%LABEL;j %label;LABEL"}, {"WIDE.bbs %as,%at,%label", "bbc %as,%at,%LABEL;j %label;LABEL"}, /* Expanding calls with literals. */ {"call0 %label,%ar0 ? IsaUseL32R", "LITERAL %label; l32r a0,%LITERAL; callx0 a0,%ar0"}, {"call4 %label,%ar4 ? IsaUseL32R", "LITERAL %label; l32r a4,%LITERAL; callx4 a4,%ar4"}, {"call8 %label,%ar8 ? IsaUseL32R", "LITERAL %label; l32r a8,%LITERAL; callx8 a8,%ar8"}, {"call12 %label,%ar12 ? IsaUseL32R", "LITERAL %label; l32r a12,%LITERAL; callx12 a12,%ar12"}, /* Expanding calls with const16. */ {"call0 %label,%ar0 ? IsaUseConst16", "const16 a0,HI16U(%label); const16 a0,LOW16U(%label); callx0 a0,%ar0"}, {"call4 %label,%ar4 ? IsaUseConst16", "const16 a4,HI16U(%label); const16 a4,LOW16U(%label); callx4 a4,%ar4"}, {"call8 %label,%ar8 ? IsaUseConst16", "const16 a8,HI16U(%label); const16 a8,LOW16U(%label); callx8 a8,%ar8"}, {"call12 %label,%ar12 ? IsaUseConst16", "const16 a12,HI16U(%label); const16 a12,LOW16U(%label); callx12 a12,%ar12"}, /* Expanding j.l with literals. */ {"j %label ? FREEREG ? IsaUseL32R", "LITERAL %label; l32r FREEREG,%LITERAL; jx FREEREG"}, /* Expanding j.l with const16. */ {"j %label ? FREEREG ? IsaUseConst16", "const16 FREEREG,HI16U(%label); const16 FREEREG,LOW16U(%label); jx FREEREG"}, }; #define WIDEN_COUNT (sizeof (widen_spec_list) / sizeof (string_pattern_pair)) /* The simplify_spec_list specifies simplifying transformations that will reduce the instruction width or otherwise simplify an instruction. These are usually applied before relaxation in the assembler. It is always legal to simplify. Even for "addi as, 0", the "addi.n as, 0" will eventually be widened back to an "addi 0" after the widening table is applied. Note: The usage of this table has changed somewhat so that it is entirely specific to "narrowing" instructions to use the density option. This table is not used at all when the density option is not available. */ string_pattern_pair simplify_spec_list[] = { {"add %ar,%as,%at ? IsaUseDensityInstruction", "add.n %ar,%as,%at"}, {"addi.n %ar,%as,0 ? IsaUseDensityInstruction", "mov.n %ar,%as"}, {"addi %ar,%as,0 ? IsaUseDensityInstruction", "mov.n %ar,%as"}, {"addi %ar,%as,%imm ? IsaUseDensityInstruction", "addi.n %ar,%as,%imm"}, {"addmi %ar,%as,%imm ? IsaUseDensityInstruction", "addi.n %ar,%as,%imm"}, {"beqz %as,%label ? IsaUseDensityInstruction", "beqz.n %as,%label"}, {"bnez %as,%label ? IsaUseDensityInstruction", "bnez.n %as,%label"}, {"l32i %at,%as,%imm ? IsaUseDensityInstruction", "l32i.n %at,%as,%imm"}, {"movi %as,%imm ? IsaUseDensityInstruction", "movi.n %as,%imm"}, {"nop ? realnop ? IsaUseDensityInstruction", "nop.n"}, {"or %ar,%as,%at | %ar==%as | %as==%at ? IsaUseDensityInstruction", "nop.n"}, {"or %ar,%as,%at | %ar!=%as | %as==%at ? IsaUseDensityInstruction", "mov.n %ar,%as"}, {"ret %as ? IsaUseDensityInstruction", "ret.n %as"}, {"retw %as ? IsaUseDensityInstruction", "retw.n %as"}, {"s32i %at,%as,%imm ? IsaUseDensityInstruction", "s32i.n %at,%as,%imm"}, {"slli %ar,%as,0 ? IsaUseDensityInstruction", "mov.n %ar,%as"} }; #define SIMPLIFY_COUNT \ (sizeof (simplify_spec_list) / sizeof (string_pattern_pair)) /* Externally visible functions. */ extern bfd_boolean xg_has_userdef_op_fn (OpType); extern long xg_apply_userdef_op_fn (OpType, long); static void append_transition (TransitionTable *tt, xtensa_opcode opcode, TransitionRule *t, transition_cmp_fn cmp) { TransitionList *tl = XNEW (TransitionList); TransitionList *prev; TransitionList **t_p; gas_assert (tt != NULL); gas_assert (opcode < tt->num_opcodes); prev = tt->table[opcode]; tl->rule = t; tl->next = NULL; if (prev == NULL) { tt->table[opcode] = tl; return; } for (t_p = &tt->table[opcode]; (*t_p) != NULL; t_p = &(*t_p)->next) { if (cmp && cmp (t, (*t_p)->rule) < 0) { /* Insert it here. */ tl->next = *t_p; *t_p = tl; return; } } (*t_p) = tl; } static void append_condition (TransitionRule *tr, Precondition *cond) { PreconditionList *pl = XNEW (PreconditionList); PreconditionList *prev = tr->conditions; PreconditionList *nxt; pl->precond = cond; pl->next = NULL; if (prev == NULL) { tr->conditions = pl; return; } nxt = prev->next; while (nxt != NULL) { prev = nxt; nxt = nxt->next; } prev->next = pl; } static void append_value_condition (TransitionRule *tr, CmpOp cmp, unsigned op1, unsigned op2) { Precondition *cond = XNEW (Precondition); cond->cmp = cmp; cond->op_num = op1; cond->typ = OP_OPERAND; cond->op_data = op2; append_condition (tr, cond); } static void append_constant_value_condition (TransitionRule *tr, CmpOp cmp, unsigned op1, unsigned cnst) { Precondition *cond = XNEW (Precondition); cond->cmp = cmp; cond->op_num = op1; cond->typ = OP_CONSTANT; cond->op_data = cnst; append_condition (tr, cond); } static void append_build_insn (TransitionRule *tr, BuildInstr *bi) { BuildInstr *prev = tr->to_instr; BuildInstr *nxt; bi->next = NULL; if (prev == NULL) { tr->to_instr = bi; return; } nxt = prev->next; while (nxt != 0) { prev = nxt; nxt = prev->next; } prev->next = bi; } static void append_op (BuildInstr *bi, BuildOp *b_op) { BuildOp *prev = bi->ops; BuildOp *nxt; if (prev == NULL) { bi->ops = b_op; return; } nxt = prev->next; while (nxt != NULL) { prev = nxt; nxt = nxt->next; } prev->next = b_op; } static void append_literal_op (BuildInstr *bi, unsigned op1, unsigned src_op) { BuildOp *b_op = XNEW (BuildOp); b_op->op_num = op1; b_op->typ = OP_LITERAL; b_op->op_data = src_op; b_op->next = NULL; append_op (bi, b_op); } static void append_label_op (BuildInstr *bi, unsigned op1) { BuildOp *b_op = XNEW (BuildOp); b_op->op_num = op1; b_op->typ = OP_LABEL; b_op->op_data = 0; b_op->next = NULL; append_op (bi, b_op); } static void append_constant_op (BuildInstr *bi, unsigned op1, unsigned cnst) { BuildOp *b_op = XNEW (BuildOp); b_op->op_num = op1; b_op->typ = OP_CONSTANT; b_op->op_data = cnst; b_op->next = NULL; append_op (bi, b_op); } static void append_field_op (BuildInstr *bi, unsigned op1, unsigned src_op) { BuildOp *b_op = XNEW (BuildOp); b_op->op_num = op1; b_op->typ = OP_OPERAND; b_op->op_data = src_op; b_op->next = NULL; append_op (bi, b_op); } /* These could be generated but are not currently. */ static void append_user_fn_field_op (BuildInstr *bi, unsigned op1, OpType typ, unsigned src_op) { BuildOp *b_op = XNEW (BuildOp); b_op->op_num = op1; b_op->typ = typ; b_op->op_data = src_op; b_op->next = NULL; append_op (bi, b_op); } /* These operand functions are the semantics of user-defined operand functions. */ static long operand_function_HI24S (long a) { if (a & 0x80) return (a & (~0xff)) + 0x100; else return (a & (~0xff)); } static long operand_function_F32MINUS (long a) { return (32 - a); } static long operand_function_LOW8 (long a) { if (a & 0x80) return (a & 0xff) | ~0xff; else return (a & 0xff); } static long operand_function_LOW16U (long a) { return (a & 0xffff); } static long operand_function_HI16U (long a) { unsigned long b = a & 0xffff0000; return (long) (b >> 16); } bfd_boolean xg_has_userdef_op_fn (OpType op) { switch (op) { case OP_OPERAND_F32MINUS: case OP_OPERAND_LOW8: case OP_OPERAND_HI24S: case OP_OPERAND_LOW16U: case OP_OPERAND_HI16U: return TRUE; default: break; } return FALSE; } long xg_apply_userdef_op_fn (OpType op, long a) { switch (op) { case OP_OPERAND_F32MINUS: return operand_function_F32MINUS (a); case OP_OPERAND_LOW8: return operand_function_LOW8 (a); case OP_OPERAND_HI24S: return operand_function_HI24S (a); case OP_OPERAND_LOW16U: return operand_function_LOW16U (a); case OP_OPERAND_HI16U: return operand_function_HI16U (a); default: break; } return FALSE; } /* Generate a transition table. */ static const char * enter_opname_n (const char *name, int len) { opname_e *op; for (op = local_opnames; op != NULL; op = op->next) { if (strlen (op->opname) == (unsigned) len && strncmp (op->opname, name, len) == 0) return op->opname; } op = XNEW (opname_e); op->opname = xmemdup0 (name, len); return op->opname; } static const char * enter_opname (const char *name) { opname_e *op; for (op = local_opnames; op != NULL; op = op->next) { if (strcmp (op->opname, name) == 0) return op->opname; } op = XNEW (opname_e); op->opname = xstrdup (name); return op->opname; } static void init_opname_map (opname_map *m) { m->head = NULL; m->tail = &m->head; } static void clear_opname_map (opname_map *m) { opname_map_e *e; while (m->head != NULL) { e = m->head; m->head = e->next; free (e); } m->tail = &m->head; } static bfd_boolean same_operand_name (const opname_map_e *m1, const opname_map_e *m2) { if (m1->operand_name == NULL || m2->operand_name == NULL) return FALSE; return (m1->operand_name == m2->operand_name); } static opname_map_e * get_opmatch (opname_map *map, const char *operand_name) { opname_map_e *m; for (m = map->head; m != NULL; m = m->next) { if (strcmp (m->operand_name, operand_name) == 0) return m; } return NULL; } static bfd_boolean op_is_constant (const opname_map_e *m1) { return (m1->operand_name == NULL); } static unsigned op_get_constant (const opname_map_e *m1) { gas_assert (m1->operand_name == NULL); return m1->constant_value; } static void init_precond_list (precond_list *l) { l->head = NULL; l->tail = &l->head; } static void clear_precond_list (precond_list *l) { precond_e *e; while (l->head != NULL) { e = l->head; l->head = e->next; free (e); } l->tail = &l->head; } static void init_insn_templ (insn_templ *t) { t->opcode_name = NULL; init_opname_map (&t->operand_map); } static void clear_insn_templ (insn_templ *t) { clear_opname_map (&t->operand_map); } static void init_insn_pattern (insn_pattern *p) { init_insn_templ (&p->t); init_precond_list (&p->preconds); p->options = NULL; } static void clear_insn_pattern (insn_pattern *p) { clear_insn_templ (&p->t); clear_precond_list (&p->preconds); } static void init_insn_repl (insn_repl *r) { r->head = NULL; r->tail = &r->head; } static void clear_insn_repl (insn_repl *r) { insn_repl_e *e; while (r->head != NULL) { e = r->head; r->head = e->next; clear_insn_templ (&e->t); } r->tail = &r->head; } static int insn_templ_operand_count (const insn_templ *t) { int i = 0; const opname_map_e *op; for (op = t->operand_map.head; op != NULL; op = op->next, i++) ; return i; } /* Convert a string to a number. E.G.: parse_constant("10", &num) */ static bfd_boolean parse_constant (const char *in, unsigned *val_p) { unsigned val = 0; const char *p; if (in == NULL) return FALSE; p = in; while (*p != '\0') { if (*p >= '0' && *p <= '9') val = val * 10 + (*p - '0'); else return FALSE; ++p; } *val_p = val; return TRUE; } static bfd_boolean parse_special_fn (const char *name, const char **fn_name_p, const char **arg_name_p) { const char *p_start; const char *p_end; p_start = strchr (name, '('); if (p_start == NULL) return FALSE; p_end = strchr (p_start, ')'); if (p_end == NULL) return FALSE; if (p_end[1] != '\0') return FALSE; *fn_name_p = enter_opname_n (name, p_start - name); *arg_name_p = enter_opname_n (p_start + 1, p_end - p_start - 1); return TRUE; } static const char * skip_white (const char *p) { if (p == NULL) return p; while (*p == ' ') ++p; return p; } static void trim_whitespace (char *in) { char *last_white = NULL; char *p = in; while (p && *p != '\0') { while (*p == ' ') { if (last_white == NULL) last_white = p; p++; } if (*p != '\0') { last_white = NULL; p++; } } if (last_white) *last_white = '\0'; } /* Split a string into component strings where "c" is the delimiter. Place the result in the split_rec. */ static void split_string (split_rec *rec, const char *in, char c, bfd_boolean elide_whitespace) { int cnt = 0; int i; const char *p = in; while (p != NULL && *p != '\0') { cnt++; p = strchr (p, c); if (p) p++; } rec->count = cnt; rec->vec = NULL; if (rec->count == 0) return; rec->vec = XNEWVEC (char *, cnt); for (i = 0; i < cnt; i++) rec->vec[i] = 0; p = in; for (i = 0; i < cnt; i++) { const char *q; int len; q = p; if (elide_whitespace) q = skip_white (q); p = strchr (q, c); if (p == NULL) rec->vec[i] = xstrdup (q); else { len = p - q; rec->vec[i] = xmemdup0 (q, len); p++; } if (elide_whitespace) trim_whitespace (rec->vec[i]); } } static void clear_split_rec (split_rec *rec) { int i; for (i = 0; i < rec->count; i++) free (rec->vec[i]); if (rec->count > 0) free (rec->vec); } /* Initialize a split record. The split record must be initialized before split_string is called. */ static void init_split_rec (split_rec *rec) { rec->vec = NULL; rec->count = 0; } /* Parse an instruction template like "insn op1, op2, op3". */ static bfd_boolean parse_insn_templ (const char *s, insn_templ *t) { const char *p = s; int insn_name_len; split_rec oprec; int i; /* First find the first whitespace. */ init_split_rec (&oprec); p = skip_white (p); insn_name_len = strcspn (s, " "); if (insn_name_len == 0) return FALSE; init_insn_templ (t); t->opcode_name = enter_opname_n (p, insn_name_len); p = p + insn_name_len; /* Split by ',' and skip beginning and trailing whitespace. */ split_string (&oprec, p, ',', TRUE); for (i = 0; i < oprec.count; i++) { const char *opname = oprec.vec[i]; opname_map_e *e = XNEW (opname_map_e); e->next = NULL; e->operand_name = NULL; e->constant_value = 0; e->operand_num = i; /* If it begins with a number, assume that it is a number. */ if (opname && opname[0] >= '0' && opname[0] <= '9') { unsigned val; if (parse_constant (opname, &val)) e->constant_value = val; else { free (e); clear_split_rec (&oprec); clear_insn_templ (t); return FALSE; } } else e->operand_name = enter_opname (oprec.vec[i]); *t->operand_map.tail = e; t->operand_map.tail = &e->next; } clear_split_rec (&oprec); return TRUE; } static bfd_boolean parse_precond (const char *s, precond_e *precond) { /* All preconditions are currently of the form: a == b or a != b or a == k (where k is a constant). Later we may use some special functions like DENSITY == 1 to identify when density is available. */ const char *p = s; int len; precond->opname1 = NULL; precond->opval1 = 0; precond->cmpop = OP_EQUAL; precond->opname2 = NULL; precond->opval2 = 0; precond->next = NULL; p = skip_white (p); len = strcspn (p, " !="); if (len == 0) return FALSE; precond->opname1 = enter_opname_n (p, len); p = p + len; p = skip_white (p); /* Check for "==" and "!=". */ if (strncmp (p, "==", 2) == 0) precond->cmpop = OP_EQUAL; else if (strncmp (p, "!=", 2) == 0) precond->cmpop = OP_NOTEQUAL; else return FALSE; p = p + 2; p = skip_white (p); /* No trailing whitespace from earlier parsing. */ if (p[0] >= '0' && p[0] <= '9') { unsigned val; if (parse_constant (p, &val)) precond->opval2 = val; else return FALSE; } else precond->opname2 = enter_opname (p); return TRUE; } static void clear_req_or_option_list (ReqOrOption **r_p) { if (*r_p == NULL) return; free ((*r_p)->option_name); clear_req_or_option_list (&(*r_p)->next); *r_p = NULL; } static void clear_req_option_list (ReqOption **r_p) { if (*r_p == NULL) return; clear_req_or_option_list (&(*r_p)->or_option_terms); clear_req_option_list (&(*r_p)->next); *r_p = NULL; } static ReqOrOption * clone_req_or_option_list (ReqOrOption *req_or_option) { ReqOrOption *new_req_or_option; if (req_or_option == NULL) return NULL; new_req_or_option = XNEW (ReqOrOption); new_req_or_option->option_name = xstrdup (req_or_option->option_name); new_req_or_option->is_true = req_or_option->is_true; new_req_or_option->next = NULL; new_req_or_option->next = clone_req_or_option_list (req_or_option->next); return new_req_or_option; } static ReqOption * clone_req_option_list (ReqOption *req_option) { ReqOption *new_req_option; if (req_option == NULL) return NULL; new_req_option = XNEW (ReqOption); new_req_option->or_option_terms = NULL; new_req_option->next = NULL; new_req_option->or_option_terms = clone_req_or_option_list (req_option->or_option_terms); new_req_option->next = clone_req_option_list (req_option->next); return new_req_option; } static bfd_boolean parse_option_cond (const char *s, ReqOption *option) { int i; split_rec option_term_rec; /* All option or conditions are of the form: optionA + no-optionB + ... "Ands" are divided by "?". */ init_split_rec (&option_term_rec); split_string (&option_term_rec, s, '+', TRUE); if (option_term_rec.count == 0) { clear_split_rec (&option_term_rec); return FALSE; } for (i = 0; i < option_term_rec.count; i++) { char *option_name = option_term_rec.vec[i]; bfd_boolean is_true = TRUE; ReqOrOption *req; ReqOrOption **r_p; if (strncmp (option_name, "no-", 3) == 0) { option_name = xstrdup (&option_name[3]); is_true = FALSE; } else option_name = xstrdup (option_name); req = XNEW (ReqOrOption); req->option_name = option_name; req->is_true = is_true; req->next = NULL; /* Append to list. */ for (r_p = &option->or_option_terms; (*r_p) != NULL; r_p = &(*r_p)->next) ; (*r_p) = req; } return TRUE; } /* Parse a string like: "insn op1, op2, op3, op4 | op1 != op2 | op2 == op3 | op4 == 1". I.E., instruction "insn" with 4 operands where operand 1 and 2 are not the same and operand 2 and 3 are the same and operand 4 is 1. or: "insn op1 | op1 == 1 / density + boolean / no-useroption". i.e. instruction "insn" with 1 operands where operand 1 is 1 when "density" or "boolean" options are available and "useroption" is not available. Because the current implementation of this parsing scheme uses split_string, it requires that '|' and '?' are only used as delimiters for predicates and required options. */ static bfd_boolean parse_insn_pattern (const char *in, insn_pattern *insn) { split_rec rec; split_rec optionrec; int i; init_insn_pattern (insn); init_split_rec (&optionrec); split_string (&optionrec, in, '?', TRUE); if (optionrec.count == 0) { clear_split_rec (&optionrec); return FALSE; } init_split_rec (&rec); split_string (&rec, optionrec.vec[0], '|', TRUE); if (rec.count == 0) { clear_split_rec (&rec); clear_split_rec (&optionrec); return FALSE; } if (!parse_insn_templ (rec.vec[0], &insn->t)) { clear_split_rec (&rec); clear_split_rec (&optionrec); return FALSE; } for (i = 1; i < rec.count; i++) { precond_e *cond = XNEW (precond_e); if (!parse_precond (rec.vec[i], cond)) { clear_split_rec (&rec); clear_split_rec (&optionrec); clear_insn_pattern (insn); return FALSE; } /* Append the condition. */ *insn->preconds.tail = cond; insn->preconds.tail = &cond->next; } for (i = 1; i < optionrec.count; i++) { /* Handle the option conditions. */ ReqOption **r_p; ReqOption *req_option = XNEW (ReqOption); req_option->or_option_terms = NULL; req_option->next = NULL; if (!parse_option_cond (optionrec.vec[i], req_option)) { clear_split_rec (&rec); clear_split_rec (&optionrec); clear_insn_pattern (insn); clear_req_option_list (&req_option); return FALSE; } /* Append the condition. */ for (r_p = &insn->options; (*r_p) != NULL; r_p = &(*r_p)->next) ; (*r_p) = req_option; } clear_split_rec (&rec); clear_split_rec (&optionrec); return TRUE; } static bfd_boolean parse_insn_repl (const char *in, insn_repl *r_p) { /* This is a list of instruction templates separated by ';'. */ split_rec rec; int i; split_string (&rec, in, ';', TRUE); for (i = 0; i < rec.count; i++) { insn_repl_e *e = XNEW (insn_repl_e); e->next = NULL; if (!parse_insn_templ (rec.vec[i], &e->t)) { free (e); clear_insn_repl (r_p); return FALSE; } *r_p->tail = e; r_p->tail = &e->next; } return TRUE; } static bfd_boolean transition_applies (insn_pattern *initial_insn, const char *from_string ATTRIBUTE_UNUSED, const char *to_string ATTRIBUTE_UNUSED) { ReqOption *req_option; for (req_option = initial_insn->options; req_option != NULL; req_option = req_option->next) { ReqOrOption *req_or_option = req_option->or_option_terms; if (req_or_option == NULL || req_or_option->next != NULL) continue; if (strncmp (req_or_option->option_name, "IsaUse", 6) == 0) { bfd_boolean option_available = FALSE; char *option_name = req_or_option->option_name + 6; if (!strcmp (option_name, "DensityInstruction")) option_available = (XCHAL_HAVE_DENSITY == 1); else if (!strcmp (option_name, "L32R")) option_available = (XCHAL_HAVE_L32R == 1); else if (!strcmp (option_name, "Const16")) option_available = (XCHAL_HAVE_CONST16 == 1); else if (!strcmp (option_name, "Loops")) option_available = (XCHAL_HAVE_LOOPS == 1); else if (!strcmp (option_name, "WideBranches")) option_available = (XCHAL_HAVE_WIDE_BRANCHES == 1 && produce_flix == FLIX_ALL); else if (!strcmp (option_name, "PredictedBranches")) option_available = (XCHAL_HAVE_PREDICTED_BRANCHES == 1 && produce_flix == FLIX_ALL); else if (!strcmp (option_name, "Booleans")) option_available = (XCHAL_HAVE_BOOLEANS == 1); else as_warn (_("invalid configuration option '%s' in transition rule '%s'"), req_or_option->option_name, from_string); if ((option_available ^ req_or_option->is_true) != 0) return FALSE; } else if (strcmp (req_or_option->option_name, "realnop") == 0) { bfd_boolean nop_available = (xtensa_opcode_lookup (xtensa_default_isa, "nop") != XTENSA_UNDEFINED); if ((nop_available ^ req_or_option->is_true) != 0) return FALSE; } } return TRUE; } static bfd_boolean wide_branch_opcode (const char *opcode_name, const char *suffix, xtensa_opcode *popcode) { xtensa_isa isa = xtensa_default_isa; xtensa_opcode opcode; static char wbr_name_buf[20]; if (strncmp (opcode_name, "WIDE.", 5) != 0) return FALSE; strcpy (wbr_name_buf, opcode_name + 5); strcat (wbr_name_buf, suffix); opcode = xtensa_opcode_lookup (isa, wbr_name_buf); if (opcode != XTENSA_UNDEFINED) { *popcode = opcode; return TRUE; } return FALSE; } static TransitionRule * build_transition (insn_pattern *initial_insn, insn_repl *replace_insns, const char *from_string, const char *to_string) { TransitionRule *tr = NULL; xtensa_opcode opcode; xtensa_isa isa = xtensa_default_isa; BuildInstr *literal_bi; opname_map_e *op1; opname_map_e *op2; precond_e *precond; insn_repl_e *r; if (!wide_branch_opcode (initial_insn->t.opcode_name, ".w18", &opcode) && !wide_branch_opcode (initial_insn->t.opcode_name, ".w15", &opcode)) opcode = xtensa_opcode_lookup (isa, initial_insn->t.opcode_name); if (opcode == XTENSA_UNDEFINED) { /* It is OK to not be able to translate some of these opcodes. */ return NULL; } if (xtensa_opcode_num_operands (isa, opcode) != insn_templ_operand_count (&initial_insn->t)) { /* This is also OK because there are opcodes that have different numbers of operands on different architecture variations. */ return NULL; } tr = XNEW (TransitionRule); tr->opcode = opcode; tr->conditions = NULL; tr->to_instr = NULL; /* Build the conditions. First, equivalent operand condition.... */ for (op1 = initial_insn->t.operand_map.head; op1 != NULL; op1 = op1->next) { for (op2 = op1->next; op2 != NULL; op2 = op2->next) { if (same_operand_name (op1, op2)) { append_value_condition (tr, OP_EQUAL, op1->operand_num, op2->operand_num); } } } /* Now the condition that an operand value must be a constant.... */ for (op1 = initial_insn->t.operand_map.head; op1 != NULL; op1 = op1->next) { if (op_is_constant (op1)) { append_constant_value_condition (tr, OP_EQUAL, op1->operand_num, op_get_constant (op1)); } } /* Now add the explicit preconditions listed after the "|" in the spec. These are currently very limited, so we do a special case parse for them. We expect spaces, opname != opname. */ for (precond = initial_insn->preconds.head; precond != NULL; precond = precond->next) { op1 = NULL; op2 = NULL; if (precond->opname1) { op1 = get_opmatch (&initial_insn->t.operand_map, precond->opname1); if (op1 == NULL) as_fatal (_("opcode '%s': no bound opname '%s' " "for precondition in '%s'"), xtensa_opcode_name (isa, opcode), precond->opname1, from_string); } if (precond->opname2) { op2 = get_opmatch (&initial_insn->t.operand_map, precond->opname2); if (op2 == NULL) as_fatal (_("opcode '%s': no bound opname '%s' " "for precondition in '%s'"), xtensa_opcode_name (isa, opcode), precond->opname2, from_string); } if (op1 == NULL && op2 == NULL) as_fatal (_("opcode '%s': precondition only contains " "constants in '%s'"), xtensa_opcode_name (isa, opcode), from_string); else if (op1 != NULL && op2 != NULL) append_value_condition (tr, precond->cmpop, op1->operand_num, op2->operand_num); else if (op2 == NULL) append_constant_value_condition (tr, precond->cmpop, op1->operand_num, precond->opval2); else append_constant_value_condition (tr, precond->cmpop, op2->operand_num, precond->opval1); } tr->options = clone_req_option_list (initial_insn->options); /* Generate the replacement instructions. Some of these "instructions" are actually labels and literals. There can be at most one literal and at most one label. A literal must be defined (e.g., "LITERAL %imm") before use (e.g., "%LITERAL"). The labels can be used before they are defined. Also there are a number of special operands (e.g., HI24S). */ literal_bi = NULL; for (r = replace_insns->head; r != NULL; r = r->next) { BuildInstr *bi; const char *opcode_name; int operand_count; opname_map_e *op; const char *fn_name; const char *operand_arg_name; bi = XNEW (BuildInstr); append_build_insn (tr, bi); bi->opcode = XTENSA_UNDEFINED; bi->ops = NULL; bi->next = NULL; opcode_name = r->t.opcode_name; operand_count = insn_templ_operand_count (&r->t); if (strcmp (opcode_name, "LITERAL") == 0) { bi->typ = INSTR_LITERAL_DEF; if (operand_count != 1) as_fatal (_("expected one operand for generated literal")); literal_bi = bi; } else if (strcmp (opcode_name, "LABEL") == 0) { bi->typ = INSTR_LABEL_DEF; if (operand_count != 0) as_fatal (_("expected 0 operands for generated label")); } else { bi->typ = INSTR_INSTR; if (wide_branch_opcode (opcode_name, ".w18", &bi->opcode) || wide_branch_opcode (opcode_name, ".w15", &bi->opcode)) opcode_name = xtensa_opcode_name (isa, bi->opcode); else bi->opcode = xtensa_opcode_lookup (isa, opcode_name); if (bi->opcode == XTENSA_UNDEFINED) { as_warn (_("invalid opcode '%s' in transition rule '%s'"), opcode_name, to_string); return NULL; } /* Check for the right number of ops. */ if (xtensa_opcode_num_operands (isa, bi->opcode) != (int) operand_count) as_fatal (ngettext ("opcode '%s': replacement does not have %d op", "opcode '%s': replacement does not have %d ops", xtensa_opcode_num_operands (isa, bi->opcode)), opcode_name, xtensa_opcode_num_operands (isa, bi->opcode)); } for (op = r->t.operand_map.head; op != NULL; op = op->next) { unsigned idnum; if (op_is_constant (op)) append_constant_op (bi, op->operand_num, op_get_constant (op)); else if (strcmp (op->operand_name, "%LITERAL") == 0) { if (! literal_bi || ! literal_bi->ops || literal_bi->ops->next) as_fatal (_("opcode '%s': cannot find literal definition"), opcode_name); append_literal_op (bi, op->operand_num, literal_bi->ops->op_data); } else if (strcmp (op->operand_name, "%LABEL") == 0) append_label_op (bi, op->operand_num); else if (op->operand_name[0] == 'a' && parse_constant (op->operand_name + 1, &idnum)) append_constant_op (bi, op->operand_num, idnum); else if (op->operand_name[0] == '%') { opname_map_e *orig_op; orig_op = get_opmatch (&initial_insn->t.operand_map, op->operand_name); if (orig_op == NULL) as_fatal (_("opcode '%s': unidentified operand '%s' in '%s'"), opcode_name, op->operand_name, to_string); append_field_op (bi, op->operand_num, orig_op->operand_num); } else if (strcmp (op->operand_name, "FREEREG") == 0) { append_user_fn_field_op (bi, op->operand_num, OP_FREEREG, 0); } else if (parse_special_fn (op->operand_name, &fn_name, &operand_arg_name)) { opname_map_e *orig_op; OpType typ = OP_CONSTANT; if (strcmp (fn_name, "LOW8") == 0) typ = OP_OPERAND_LOW8; else if (strcmp (fn_name, "HI24S") == 0) typ = OP_OPERAND_HI24S; else if (strcmp (fn_name, "F32MINUS") == 0) typ = OP_OPERAND_F32MINUS; else if (strcmp (fn_name, "LOW16U") == 0) typ = OP_OPERAND_LOW16U; else if (strcmp (fn_name, "HI16U") == 0) typ = OP_OPERAND_HI16U; else as_fatal (_("unknown user-defined function %s"), fn_name); orig_op = get_opmatch (&initial_insn->t.operand_map, operand_arg_name); if (orig_op == NULL) as_fatal (_("opcode '%s': unidentified operand '%s' in '%s'"), opcode_name, op->operand_name, to_string); append_user_fn_field_op (bi, op->operand_num, typ, orig_op->operand_num); } else as_fatal (_("opcode '%s': could not parse operand '%s' in '%s'"), opcode_name, op->operand_name, to_string); } } return tr; } static TransitionTable * build_transition_table (const string_pattern_pair *transitions, int transition_count, transition_cmp_fn cmp) { TransitionTable *table = NULL; int num_opcodes = xtensa_isa_num_opcodes (xtensa_default_isa); int i, tnum; if (table != NULL) return table; /* Otherwise, build it now. */ table = XNEW (TransitionTable); table->num_opcodes = num_opcodes; table->table = XNEWVEC (TransitionList *, num_opcodes); for (i = 0; i < num_opcodes; i++) table->table[i] = NULL; for (tnum = 0; tnum < transition_count; tnum++) { const char *from_string = transitions[tnum].pattern; const char *to_string = transitions[tnum].replacement; insn_pattern initial_insn; insn_repl replace_insns; TransitionRule *tr; init_insn_pattern (&initial_insn); if (!parse_insn_pattern (from_string, &initial_insn)) as_fatal (_("could not parse INSN_PATTERN '%s'"), from_string); init_insn_repl (&replace_insns); if (!parse_insn_repl (to_string, &replace_insns)) as_fatal (_("could not parse INSN_REPL '%s'"), to_string); if (transition_applies (&initial_insn, from_string, to_string)) { tr = build_transition (&initial_insn, &replace_insns, from_string, to_string); if (tr) append_transition (table, tr->opcode, tr, cmp); else { #if TENSILICA_DEBUG as_warn (_("could not build transition for %s => %s"), from_string, to_string); #endif } } clear_insn_repl (&replace_insns); clear_insn_pattern (&initial_insn); } return table; } extern TransitionTable * xg_build_widen_table (transition_cmp_fn cmp) { static TransitionTable *table = NULL; if (table == NULL) table = build_transition_table (widen_spec_list, WIDEN_COUNT, cmp); return table; } extern TransitionTable * xg_build_simplify_table (transition_cmp_fn cmp) { static TransitionTable *table = NULL; if (table == NULL) table = build_transition_table (simplify_spec_list, SIMPLIFY_COUNT, cmp); return table; }