From: Georg-Johann Lay Date: Fri, 30 Jun 2017 15:37:39 +0000 (+0100) Subject: Add support for a __gcc_isr pseudo isntruction to the AVR assembler. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=32f76c677333510350f21a40db062a8d17995c53;p=binutils-gdb.git Add support for a __gcc_isr pseudo isntruction to the AVR assembler. PR gas/21683 include * opcode/avr.h (AVR_INSN): Add one for __gcc_isr. gas * doc/c-avr.texi (AVR Options) <-mgcc-isr>: Document it. (AVR Pseudo Instructions): New node. * config/tc-avr.h (md_pre_output_hook): Define to avr_pre_output_hook. (md_undefined_symbol): Define to avr_undefined_symbol. (avr_pre_output_hook, avr_undefined_symbol): New protos. * config/tc-avr.c (struc-symbol.h): Include it. (ISR_CHUNK_Done, ISR_CHUNK_Prologue, ISR_CHUNK_Epilogue): New enums. (avr_isr, avr_gccisr_opcode) (avr_no_sreg_hash, avr_no_sreg): New static variables. (avr_opt_s) : Add field. (avr_opt): Add initializer for have_gccisr. (enum options) : Add enum. (md_longopts) <"mgcc-isr">: Add entry. (md_show_usage): Document -mgcc-isr. (md_parse_option) [OPTION_HAVE_GCCISR]: Handle it. (md_undefined_symbol): Remove. (avr_undefined_symbol, avr_pre_output_hook): New fuctions. (md_begin) : Initialize them. (avr_operand) : Add argument and set *pregno if function is called for a register constraint. [N]: Handle constraint. (avr_operands) : Pass 5th parameter to calls. [avr_opt.have_gccisr]: Call avr_update_gccisr. Call avr_gccisr_operands instead of avr_operands. (avr_update_gccisr, avr_emit_insn, avr_patch_gccisr_frag) (avr_gccisr_operands, avr_check_gccisr_done): New static functions. * testsuite/gas/avr/gccisr-01.d: New test. * testsuite/gas/avr/gccisr-01.s: New test. * testsuite/gas/avr/gccisr-02.d: New test. * testsuite/gas/avr/gccisr-02.s: New test. * testsuite/gas/avr/gccisr-03.d: New test. * testsuite/gas/avr/gccisr-03.s: New test. --- diff --git a/gas/ChangeLog b/gas/ChangeLog index ffc661ec5a2..9989f313c63 100644 --- a/gas/ChangeLog +++ b/gas/ChangeLog @@ -1,3 +1,39 @@ +2017-06-30 Georg-Johann Lay + + PR gas/21683 + * doc/c-avr.texi (AVR Options) <-mgcc-isr>: Document it. + (AVR Pseudo Instructions): New node. + * config/tc-avr.h (md_pre_output_hook): Define to avr_pre_output_hook. + (md_undefined_symbol): Define to avr_undefined_symbol. + (avr_pre_output_hook, avr_undefined_symbol): New protos. + * config/tc-avr.c (struc-symbol.h): Include it. + (ISR_CHUNK_Done, ISR_CHUNK_Prologue, ISR_CHUNK_Epilogue): New enums. + (avr_isr, avr_gccisr_opcode) + (avr_no_sreg_hash, avr_no_sreg): New static variables. + (avr_opt_s) : Add field. + (avr_opt): Add initializer for have_gccisr. + (enum options) : Add enum. + (md_longopts) <"mgcc-isr">: Add entry. + (md_show_usage): Document -mgcc-isr. + (md_parse_option) [OPTION_HAVE_GCCISR]: Handle it. + (md_undefined_symbol): Remove. + (avr_undefined_symbol, avr_pre_output_hook): New fuctions. + (md_begin) : Initialize them. + (avr_operand) : Add argument and set *pregno if function + is called for a register constraint. + [N]: Handle constraint. + (avr_operands) : Pass 5th parameter to calls. + [avr_opt.have_gccisr]: Call avr_update_gccisr. Call + avr_gccisr_operands instead of avr_operands. + (avr_update_gccisr, avr_emit_insn, avr_patch_gccisr_frag) + (avr_gccisr_operands, avr_check_gccisr_done): New static functions. + * testsuite/gas/avr/gccisr-01.d: New test. + * testsuite/gas/avr/gccisr-01.s: New test. + * testsuite/gas/avr/gccisr-02.d: New test. + * testsuite/gas/avr/gccisr-02.s: New test. + * testsuite/gas/avr/gccisr-03.d: New test. + * testsuite/gas/avr/gccisr-03.s: New test. + 2017-06-30 Maciej W. Rozycki * config/tc-mips.c (match_float_constant): Update description. diff --git a/gas/config/tc-avr.c b/gas/config/tc-avr.c index 79837c84fa1..7e3f7191067 100644 --- a/gas/config/tc-avr.c +++ b/gas/config/tc-avr.c @@ -23,6 +23,7 @@ #include "as.h" #include "safe-ctype.h" #include "subsegs.h" +#include "struc-symbol.h" #include "dwarf2dbg.h" #include "dw2gencfi.h" #include "elf/avr.h" @@ -54,6 +55,107 @@ struct avr_opcodes_s avr_opcodes[] = {NULL, NULL, NULL, 0, 0, 0} }; + +/* Stuff for the `__gcc_isr' pseudo instruction. + + Purpose of the pseudo instruction is to emit more efficient ISR prologues + and epilogues than GCC currently does. GCC has no explicit (on RTL level) + modelling of SREG, TMP_REG or ZERO_REG. These regs are used implicitly + during instruction printing. That doesn't hurt too much for ordinary + functions, however for small ISRs there might be some overhead. + + As implementing http://gcc.gnu.org/PR20296 would imply an almost complete + rewite of GCC's AVR back-end (which might pop up less optimized code in + other places), we provide a pseudo-instruction which is resolved by GAS + into ISR prologue / epilogue as expected by GCC. + + Using GAS for this purpose has the additional benefit that it can scan + code emit by inline asm which is opaque to GCC. + + The pseudo-instruction is only supposed to handle the starting of + prologue and the ending of epilogues (without RETI) which deal with + SREG, TMP_REG and ZERO_REG and one additional, optional general purpose + register. + + __gcc_isr consists of 3 different "chunks": + + __gcc_isr 1 + Chunk 1 (ISR_CHUNK_Prologue) + Start the ISR code. Will be replaced by ISR prologue by next Done chunk. + Must be the 1st chunk in a file or follow a Done chunk from previous + ISR (which has been patched already). + + It will finish the current frag and emit a new frag of + type rs_machine_dependent, subtype ISR_CHUNK_Prologue. + + __gcc_isr 2 + Chunk 2 (ISR_CHUNK_Epilogue) + Will be replaced by ISR epilogue by next Done chunk. Must follow + chunk 1 (Prologue) or chunk 2 (Epilogue). Functions might come + without epilogue or with more than one epilogue, and even code + located statically after the last epilogue might belong to a function. + + It will finish the current frag and emit a new frag of + type rs_machine_dependent, subtype ISR_CHUNK_Epilogue. + + __gcc_isr 0, Rx + Chunk 0 (ISR_CHUNK_Done) + Must follow chunk 1 (Prologue) or chunk 2 (Epilogue) and finishes + the ISR code. Only GCC can know where a function's code ends. + + It triggers the patch-up of all rs_machine_dependent frags in the + current frag chain and turns them into ordinary rs_fill code frags. + + If Rx is a register > ZERO_REG then GCC also wants to push / pop Rx. + If neither TMP_REG nor ZERO_REG are needed, Rx will be used in + the push / pop sequence avoiding the need for TMP_REG / ZERO_REG. + If Rx <= ZERO_REG then GCC doesn't assume anything about Rx. + + Assumptions: + + o GCC takes care of code that is opaque to GAS like tail calls + or non-local goto. + + o Using SEI / CLI does not count as clobbering SREG. This is + because a final RETI will restore the I-flag. + + o Using OUT or ST* is supposed not to clobber SREG. Sequences like + + IN-SREG + CLI + Atomic-Code + OUT-SREG + + will still work as expected because the scan will reveal any + clobber of SREG other than I-flag and emit PUSH / POP of SREG. +*/ + +enum + { + ISR_CHUNK_Done = 0, + ISR_CHUNK_Prologue = 1, + ISR_CHUNK_Epilogue = 2 + }; + +static struct +{ + /* Previous __gcc_isr chunk (one of the enums above) + and it's location for diagnostics. */ + int prev_chunk; + unsigned line; + const char *file; + /* Replacer for __gcc_isr.n_pushed once we know how many regs are + pushed by the Prologue chunk. */ + symbolS *sym_n_pushed; + + /* Set and used during parse from chunk 1 (Prologue) up to chunk 0 (Done). + Set by `avr_update_gccisr' and used by `avr_patch_gccisr_frag'. */ + int need_reg_tmp; + int need_reg_zero; + int need_sreg; +} avr_isr; + +static void avr_gccisr_operands (struct avr_opcodes_s*, char**); +static void avr_update_gccisr (struct avr_opcodes_s*, int, int); +static struct avr_opcodes_s *avr_gccisr_opcode; + const char comment_chars[] = ";"; const char line_comment_chars[] = "#"; const char line_separator_chars[] = "$"; @@ -359,9 +461,10 @@ struct avr_opt_s int no_wrap; /* -mno-wrap: reject rjmp/rcall with 8K wrap-around. */ int no_link_relax; /* -mno-link-relax / -mlink-relax: generate (or not) relocations for linker relaxation. */ + int have_gccisr; /* Whether "__gcc_isr" is a known (pseudo) insn. */ }; -static struct avr_opt_s avr_opt = { 0, 0, 0, 0 }; +static struct avr_opt_s avr_opt = { 0, 0, 0, 0, 0 }; const char EXP_CHARS[] = "eE"; const char FLT_CHARS[] = "dD"; @@ -416,6 +519,33 @@ static struct hash_control *avr_hash; /* Reloc modifiers hash control (hh8,hi8,lo8,pm_xx). */ static struct hash_control *avr_mod_hash; +/* Whether some opcode does not change SREG. */ +static struct hash_control *avr_no_sreg_hash; + +static const char* const avr_no_sreg[] = + { + /* Arithmetic */ + "ldi", "swap", "mov", "movw", + /* Special instructions. I-Flag will be restored by RETI, and we don't + consider I-Flag as being clobbered when changed. */ + "sei", "cli", "reti", "brie", "brid", + "nop", "wdr", "sleep", + /* Load / Store */ + "ld", "ldd", "lds", "pop", "in", "lpm", "elpm", + "st", "std", "sts", "push", "out", + /* Jumps and Calls. Calls might call code that changes SREG. + GCC has to filter out ABI calls. The non-ABI transparent calls + must use [R]CALL and are filtered out now by not mentioning them. */ + "rjmp", "jmp", "ijmp", "ret", + /* Skipping. Branches need SREG to be set, hence we regard them + as if they changed SREG and don't list them here. */ + "sbrc", "sbrs", "sbic", "sbis", "cpse", + /* I/O Manipulation */ + "sbi", "cbi", + /* Read-Modify-Write */ + "lac", "las", "lat", "xch" + }; + #define OPTION_MMCU 'm' enum options { @@ -424,7 +554,8 @@ enum options OPTION_NO_WRAP, OPTION_ISA_RMW, OPTION_LINK_RELAX, - OPTION_NO_LINK_RELAX + OPTION_NO_LINK_RELAX, + OPTION_HAVE_GCCISR }; struct option md_longopts[] = @@ -436,6 +567,7 @@ struct option md_longopts[] = { "mrmw", no_argument, NULL, OPTION_ISA_RMW }, { "mlink-relax", no_argument, NULL, OPTION_LINK_RELAX }, { "mno-link-relax", no_argument, NULL, OPTION_NO_LINK_RELAX }, + { "mgcc-isr", no_argument, NULL, OPTION_HAVE_GCCISR }, { NULL, no_argument, NULL, 0 } }; @@ -544,6 +676,7 @@ md_show_usage (FILE *stream) " -mrmw accept Read-Modify-Write instructions\n" " -mlink-relax generate relocations for linker relaxation (default)\n" " -mno-link-relax don't generate relocations for linker relaxation.\n" + " -mgcc-isr accept the __gcc_isr pseudo-instruction.\n" )); show_mcu_list (stream); } @@ -610,14 +743,38 @@ md_parse_option (int c, const char *arg) case OPTION_NO_LINK_RELAX: avr_opt.no_link_relax = 1; return 1; + case OPTION_HAVE_GCCISR: + avr_opt.have_gccisr = 1; + return 1; } return 0; } + +/* Implement `md_undefined_symbol' */ +/* If we are in `__gcc_isr' chunk, pop up `__gcc_isr.n_pushed.' + instead of `__gcc_isr.n_pushed'. This will be resolved by the Done + chunk in `avr_patch_gccisr_frag' to the number of PUSHes produced by + the Prologue chunk. */ + symbolS * -md_undefined_symbol (char *name ATTRIBUTE_UNUSED) +avr_undefined_symbol (char *name) { + if (ISR_CHUNK_Done != avr_isr.prev_chunk + && 0 == strcmp (name, "__gcc_isr.n_pushed")) + { + if (!avr_isr.sym_n_pushed) + { + static unsigned suffix; + char xname[30]; + sprintf (xname, "%s.%03u", name, (++suffix) % 1000); + avr_isr.sym_n_pushed = symbol_new (xname, undefined_section, + (valueT) 0, &zero_address_frag); + } + return avr_isr.sym_n_pushed; + } + return NULL; } @@ -659,6 +816,17 @@ md_begin (void) hash_insert (avr_mod_hash, EXP_MOD_NAME (i), m.ptr); } + avr_no_sreg_hash = hash_new (); + + for (i = 0; i < ARRAY_SIZE (avr_no_sreg); ++i) + { + gas_assert (hash_find (avr_hash, avr_no_sreg[i])); + hash_insert (avr_no_sreg_hash, avr_no_sreg[i], (char*) 4 /* dummy */); + } + + avr_gccisr_opcode = (struct avr_opcodes_s*) hash_find (avr_hash, "__gcc_isr"); + gas_assert (avr_gccisr_opcode); + bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach); linkrelax = !avr_opt.no_link_relax; } @@ -855,7 +1023,8 @@ static unsigned int avr_operand (struct avr_opcodes_s *opcode, int where, const char *op, - char **line) + char **line, + int *pregno) { expressionS op_expr; unsigned int op_mask = 0; @@ -904,6 +1073,9 @@ avr_operand (struct avr_opcodes_s *opcode, } } + if (pregno) + *pregno = op_mask; + if (avr_mcu->mach == bfd_mach_avrtiny) { if (op_mask < 16 || op_mask > 31) @@ -1079,6 +1251,16 @@ avr_operand (struct avr_opcodes_s *opcode, } break; + case 'N': + { + unsigned int x; + + x = avr_get_constant (str, 255); + str = input_line_pointer; + op_mask = x; + } + break; + case 'K': input_line_pointer = str; avr_offset_expression (& op_expr); @@ -1145,6 +1327,8 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) char *str = *line; int where = frag - frag_now->fr_literal; static unsigned int prev = 0; /* Previous opcode. */ + int regno1 = -2; + int regno2 = -2; /* Opcode have operands. */ if (*op) @@ -1157,7 +1341,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) /* Parse first operand. */ if (REGISTER_P (*op)) reg1_present = 1; - reg1 = avr_operand (opcode, where, op, &str); + reg1 = avr_operand (opcode, where, op, &str, ®no1); ++op; /* Parse second operand. */ @@ -1170,6 +1354,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) { reg2 = reg1; reg2_present = 1; + regno2 = regno1; } else { @@ -1181,7 +1366,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) as_bad (_("`,' required")); str = skip_space (str); - reg2 = avr_operand (opcode, where, op, &str); + reg2 = avr_operand (opcode, where, op, &str, ®no2); } if (reg1_present && reg2_present) @@ -1194,6 +1379,9 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) bin |= reg1 | reg2; } + if (avr_opt.have_gccisr) + avr_update_gccisr (opcode, regno1, regno2); + /* Detect undefined combinations (like ld r31,Z+). */ if (!avr_opt.all_opcodes && AVR_UNDEF_P (bin)) as_warn (_("undefined combination of operands")); @@ -1698,6 +1886,13 @@ md_assemble (char *str) return; } + if (opcode == avr_gccisr_opcode + && !avr_opt.have_gccisr) + { + as_bad (_("pseudo instruction `%s' not supported"), op); + return; + } + /* Special case for opcodes with optional operands (lpm, elpm) - version with operands exists in avr_opcodes[] in the next entry. */ @@ -1711,7 +1906,10 @@ md_assemble (char *str) { char *t = input_line_pointer; - avr_operands (opcode, &str); + if (opcode == avr_gccisr_opcode) + avr_gccisr_operands (opcode, &str); + else + avr_operands (opcode, &str); if (*skip_space (str)) as_bad (_("garbage at end of line")); input_line_pointer = t; @@ -2211,3 +2409,385 @@ avr_post_relax_hook (void) { avr_create_and_fill_property_section (); } + + +/* Accumulate information about instruction sequence to `avr_isr': + wheter TMP_REG, ZERO_REG and SREG might be touched. Used during parse. + REG1 is either -1 or a register number used by the instruction as input + or output operand. Similar for REG2. */ + +static void +avr_update_gccisr (struct avr_opcodes_s *opcode, int reg1, int reg2) +{ + const int tiny_p = avr_mcu->mach == bfd_mach_avrtiny; + const int reg_tmp = tiny_p ? 16 : 0; + const int reg_zero = 1 + reg_tmp; + + if (ISR_CHUNK_Done == avr_isr.prev_chunk + || (avr_isr.need_sreg + && avr_isr.need_reg_tmp + && avr_isr.need_reg_zero)) + { + /* Nothing (more) to do */ + return; + } + + /* SREG: Look up instructions that don't clobber SREG. */ + + if (!avr_isr.need_sreg + && !hash_find (avr_no_sreg_hash, opcode->name)) + { + avr_isr.need_sreg = 1; + } + + /* Handle explicit register operands. Record *any* use as clobber. + This is because TMP_REG and ZERO_REG are not global and using + them makes no sense without a previous set. */ + + avr_isr.need_reg_tmp |= reg1 == reg_tmp || reg2 == reg_tmp; + avr_isr.need_reg_zero |= reg1 == reg_zero || reg2 == reg_zero; + + /* Handle implicit register operands and some opaque stuff. */ + + if (strstr (opcode->name, "lpm") + && '?' == *opcode->constraints) + { + avr_isr.need_reg_tmp = 1; + } + + if (strstr (opcode->name, "call") + || strstr (opcode->name, "mul") + || 0 == strcmp (opcode->name, "des") + || (0 == strcmp (opcode->name, "movw") + && (reg1 == reg_tmp || reg2 == reg_tmp))) + { + avr_isr.need_reg_tmp = 1; + avr_isr.need_reg_zero = 1; + } +} + + +/* Emit some 1-word instruction to **PWHERE and advance *PWHERE by the number + of octets written. INSN specifies the desired instruction and REG is the + register used by it. This function is only used with restricted subset of + instructions as might be emit by `__gcc_isr'. IN / OUT will use SREG + and LDI loads 0. */ + +static void +avr_emit_insn (const char *insn, int reg, char **pwhere) +{ + const int sreg = 0x3f; + unsigned bin = 0; + const struct avr_opcodes_s *op + = (struct avr_opcodes_s*) hash_find (avr_hash, insn); + + /* We only have to deal with: IN, OUT, PUSH, POP, CLR, LDI 0. All of + these deal with at least one Reg and are 1-word instructions. */ + + gas_assert (op && 1 == op->insn_size); + gas_assert (reg >= 0 && reg <= 31); + + if (strchr (op->constraints, 'r')) + { + bin = op->bin_opcode | (reg << 4); + } + else if (strchr (op->constraints, 'd')) + { + gas_assert (reg >= 16); + bin = op->bin_opcode | ((reg & 0xf) << 4); + } + else + abort(); + + if (strchr (op->constraints, 'P')) + { + bin |= ((sreg & 0x30) << 5) | (sreg & 0x0f); + } + else if (0 == strcmp ("r=r", op->constraints)) + { + bin |= ((reg & 0x10) << 5) | (reg & 0x0f); + } + else + gas_assert (0 == strcmp ("r", op->constraints) + || 0 == strcmp ("ldi", op->name)); + + bfd_putl16 ((bfd_vma) bin, *pwhere); + (*pwhere) += 2 * op->insn_size; +} + + +/* Turn rs_machine_dependent frag *FR into an ordinary rs_fill code frag, + using information gathered in `avr_isr'. REG is the register number as + supplied by Done chunk "__gcc_isr 0,REG". */ + +static void +avr_patch_gccisr_frag (fragS *fr, int reg) +{ + int treg; + int n_pushed = 0; + char *where = fr->fr_literal; + const int tiny_p = avr_mcu->mach == bfd_mach_avrtiny; + const int reg_tmp = tiny_p ? 16 : 0; + const int reg_zero = 1 + reg_tmp; + + /* Clearing ZERO_REG on non-Tiny needs CLR which clobbers SREG. */ + + avr_isr.need_sreg |= !tiny_p && avr_isr.need_reg_zero; + + /* A working register to PUSH / POP the SREG. We might use the register + as supplied by ISR_CHUNK_Done for that purpose as GCC wants to push + it anyways. If GCC passes ZERO_REG or TMP_REG, it has no clue (and + no additional regs to safe) and we use that reg. */ + + treg + = avr_isr.need_reg_tmp ? reg_tmp + : avr_isr.need_reg_zero ? reg_zero + : avr_isr.need_sreg ? reg + : reg > reg_zero ? reg + : -1; + + if (treg >= 0) + { + /* Non-empty prologue / epilogue */ + + if (ISR_CHUNK_Prologue == fr->fr_subtype) + { + avr_emit_insn ("push", treg, &where); + n_pushed++; + + if (avr_isr.need_sreg) + { + avr_emit_insn ("in", treg, &where); + avr_emit_insn ("push", treg, &where); + n_pushed++; + } + + if (avr_isr.need_reg_zero) + { + if (reg_zero != treg) + { + avr_emit_insn ("push", reg_zero, &where); + n_pushed++; + } + avr_emit_insn (tiny_p ? "ldi" : "clr", reg_zero, &where); + } + + if (reg > reg_zero && reg != treg) + { + avr_emit_insn ("push", reg, &where); + n_pushed++; + } + } + else if (ISR_CHUNK_Epilogue == fr->fr_subtype) + { + /* Same logic as in Prologue but in reverse order and with counter + parts of either instruction: POP instead of PUSH and OUT instead + of IN. Clearing ZERO_REG has no couter part. */ + + if (reg > reg_zero && reg != treg) + avr_emit_insn ("pop", reg, &where); + + if (avr_isr.need_reg_zero + && reg_zero != treg) + avr_emit_insn ("pop", reg_zero, &where); + + if (avr_isr.need_sreg) + { + avr_emit_insn ("pop", treg, &where); + avr_emit_insn ("out", treg, &where); + } + + avr_emit_insn ("pop", treg, &where); + } + else + abort(); + } /* treg >= 0 */ + + if (ISR_CHUNK_Prologue == fr->fr_subtype + && avr_isr.sym_n_pushed) + { + symbolS *sy = avr_isr.sym_n_pushed; + /* Turn magic `__gcc_isr.n_pushed' into its now known value. */ + + sy->sy_value.X_op = O_constant; + sy->sy_value.X_add_number = n_pushed; + S_SET_SEGMENT (sy, expr_section); + avr_isr.sym_n_pushed = NULL; + } + + /* Turn frag into ordinary code frag of now known size. */ + + fr->fr_var = 0; + fr->fr_fix = (offsetT) (where - fr->fr_literal); + gas_assert (fr->fr_fix <= fr->fr_offset); + fr->fr_offset = 0; + fr->fr_type = rs_fill; + fr->fr_subtype = 0; +} + + +/* Implements `__gcc_isr' pseudo-instruction. For Prologue and Epilogue + chunks, emit a new rs_machine_dependent frag. For Done chunks, traverse + the current segment and patch all rs_machine_dependent frags to become + appropriate rs_fill code frags. If chunks are seen in an odd ordering, + throw an error instead. */ + +static void +avr_gccisr_operands (struct avr_opcodes_s *opcode, char **line) +{ + int bad = 0; + int chunk, reg = 0; + char *str = *line; + + gas_assert (avr_opt.have_gccisr); + + /* We only use operands "N" and "r" which don't pop new fix-ups. */ + + /* 1st operand: Which chunk of __gcc_isr: 0...2. */ + + chunk = avr_operand (opcode, -1, "N", &str, NULL); + if (chunk < 0 || chunk > 2) + as_bad (_("%s requires value 0-2 as operand 1"), opcode->name); + + if (ISR_CHUNK_Done == chunk) + { + /* 2nd operand: A register to push / pop. */ + + str = skip_space (str); + if (*str == '\0' || *str++ != ',') + as_bad (_("`,' required")); + else + avr_operand (opcode, -1, "r", &str, ®); + } + + *line = str; + + /* Chunks must follow in a specific order: + - Prologue: Exactly one + - Epilogue: Any number + - Done: Exactly one. */ + bad |= ISR_CHUNK_Prologue == chunk && avr_isr.prev_chunk != ISR_CHUNK_Done; + bad |= ISR_CHUNK_Epilogue == chunk && avr_isr.prev_chunk == ISR_CHUNK_Done; + bad |= ISR_CHUNK_Done == chunk && avr_isr.prev_chunk == ISR_CHUNK_Done; + if (bad) + { + if (avr_isr.file) + as_bad (_("`%s %d' after `%s %d' from %s:%u"), opcode->name, chunk, + opcode->name, avr_isr.prev_chunk, avr_isr.file, avr_isr.line); + else + as_bad (_("`%s %d' but no chunk open yet"), opcode->name, chunk); + } + + if (!had_errors()) + { + /* The longest sequence (prologue) might have up to 6 insns (words): + + push R0 + in R0, SREG + push R0 + push R1 + clr R1 + push Rx + */ + unsigned int size = 2 * 6; + fragS *fr; + + switch (chunk) + { + case ISR_CHUNK_Prologue: + avr_isr.need_reg_tmp = 0; + avr_isr.need_reg_zero = 0; + avr_isr.need_sreg = 0; + avr_isr.sym_n_pushed = NULL; + /* FALLTHRU */ + + case ISR_CHUNK_Epilogue: + /* Emit a new rs_machine_dependent fragment into the fragment chain. + It will be patched and cleaned up once we see the matching + ISR_CHUNK_Done. */ + frag_wane (frag_now); + frag_new (0); + frag_more (size); + + frag_now->fr_var = 1; + frag_now->fr_offset = size; + frag_now->fr_fix = 0; + frag_now->fr_type = rs_machine_dependent; + frag_now->fr_subtype = chunk; + frag_new (size); + break; + + case ISR_CHUNK_Done: + /* Traverse all frags of the current subseg and turn ones of type + rs_machine_dependent into ordinary code as expected by GCC. */ + + for (fr = frchain_now->frch_root; fr; fr = fr->fr_next) + if (fr->fr_type == rs_machine_dependent) + avr_patch_gccisr_frag (fr, reg); + break; + + default: + abort(); + break; + } + } /* !had_errors */ + + avr_isr.prev_chunk = chunk; + avr_isr.file = as_where (&avr_isr.line); +} + + +/* Callback used by the function below. Diagnose any dangling stuff from + `__gcc_isr', i.e. frags of type rs_machine_dependent. Such frags should + have been resolved during parse by ISR_CHUNK_Done. If such a frag is + seen, report an error and turn it into something harmless. */ + +static void +avr_check_gccisr_done (bfd *abfd ATTRIBUTE_UNUSED, + segT section, + void *xxx ATTRIBUTE_UNUSED) +{ + segment_info_type *info = seg_info (section); + + if (SEG_NORMAL (section) + /* BFD may have introduced its own sections without using + subseg_new, so it is possible that seg_info is NULL. */ + && info) + { + fragS *fr; + frchainS *frch; + + for (frch = info->frchainP; frch; frch = frch->frch_next) + for (fr = frch->frch_root; fr; fr = fr->fr_next) + if (fr->fr_type == rs_machine_dependent) + { + if (avr_isr.file) + as_bad_where (avr_isr.file, avr_isr.line, + _("dangling `__gcc_isr %d'"), avr_isr.prev_chunk); + else if (!had_errors()) + as_bad (_("dangling `__gcc_isr'")); + + avr_isr.file = NULL; + + /* Avoid Internal errors due to rs_machine_dependent in the + remainder: Turn frag into something harmless. */ + fr->fr_var = 0; + fr->fr_fix = 0; + fr->fr_offset = 0; + fr->fr_type = rs_fill; + fr->fr_subtype = 0; + } + } +} + + +/* Implement `md_pre_output_hook' */ +/* Run over all relevant sections and diagnose any dangling `__gcc_isr'. + This runs after parsing all inputs but before relaxing and writing. */ + +void +avr_pre_output_hook (void) +{ + if (avr_opt.have_gccisr) + bfd_map_over_sections (stdoutput, avr_check_gccisr_done, NULL); +} diff --git a/gas/config/tc-avr.h b/gas/config/tc-avr.h index 399656fa2ba..0cfe9ff92a0 100644 --- a/gas/config/tc-avr.h +++ b/gas/config/tc-avr.h @@ -220,6 +220,12 @@ extern bfd_boolean avr_allow_local_subtract (expressionS *, expressionS *, segT) #define elf_tc_final_processing avr_elf_final_processing extern void avr_elf_final_processing (void); +#define md_pre_output_hook avr_pre_output_hook () +extern void avr_pre_output_hook (void); + +#define md_undefined_symbol avr_undefined_symbol +extern symbolS* avr_undefined_symbol (char*); + #define md_post_relax_hook avr_post_relax_hook () extern void avr_post_relax_hook (void); diff --git a/gas/doc/c-avr.texi b/gas/doc/c-avr.texi index f829e796856..e419964aee7 100644 --- a/gas/doc/c-avr.texi +++ b/gas/doc/c-avr.texi @@ -18,6 +18,7 @@ * AVR Options:: Options * AVR Syntax:: Syntax * AVR Opcodes:: Opcodes +* AVR Pseudo Instructions:: Pseudo Instructions @end menu @node AVR Options @@ -151,6 +152,10 @@ Disable support for link-time relaxation. The assembler will resolve relocations when it can, and may be able to better compress some debug information. +@cindex @code{-mgcc-isr} command line option, AVR +@item -mgcc-isr +Enable the @code{__gcc_isr} pseudo instruction. + @end table @@ -441,3 +446,60 @@ The following table summarizes the AVR opcodes, and their arguments. 1001010100011001 eicall 1001010000011001 eijmp @end smallexample + +@node AVR Pseudo Instructions +@section Pseudo Instructions + +The only available pseudo-instruction @code{__gcc_isr} can be activated by +option @option{-mgcc-isr}. + +@table @code + +@item __gcc_isr 1 +Emit code chunk to be used in avr-gcc ISR prologue. +It will expand to at most six 1-word instructions, all optional: +push of @code{tmp_reg}, push of @code{SREG}, +push and clear of @code{zero_reg}, push of @var{Reg}. + +@item __gcc_isr 2 +Emit code chunk to be used in an avr-gcc ISR epilogue. +It will expand to at most five 1-word instructions, all optional: +pop of @var{Reg}, pop of @code{zero_reg}, +pop of @code{SREG}, pop of @code{tmp_reg}. + +@item __gcc_isr 0, @var{Reg} +Finish avr-gcc ISR function. Scan code since the last prologue +for usage of: @code{SREG}, @code{tmp_reg}, @code{zero_reg}. +Prologue chunk and epilogue chunks will be replaced by appropriate code +to save / restore @code{SREG}, @code{tmp_reg}, @code{zero_reg} and @var{Reg}. + +@end table + +Example input: + +@example +__vector1: + __gcc_isr 1 + lds r24, var + inc r24 + sts var, r24 + __gcc_isr 2 + reti + __gcc_isr 0, r24 +@end example + +Example output: + +@example +00000000 <__vector1>: + 0: 8f 93 push r24 + 2: 8f b7 in r24, 0x3f + 4: 8f 93 push r24 + 6: 80 91 60 00 lds r24, 0x0060 ; 0x800060 + a: 83 95 inc r24 + c: 80 93 60 00 sts 0x0060, r24 ; 0x800060 + 10: 8f 91 pop r24 + 12: 8f bf out 0x3f, r24 + 14: 8f 91 pop r24 + 16: 18 95 reti +@end example diff --git a/gas/testsuite/gas/avr/gccisr-01.d b/gas/testsuite/gas/avr/gccisr-01.d new file mode 100644 index 00000000000..91e1e617946 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-01.d @@ -0,0 +1,141 @@ +#name: gccisr-01: __gcc_isr pseudo instruction +#as: -mgcc-isr -mavr4 +#objdump: -dz +#target: avr-*-* + +.*: +file format elf32-avr + + +Disassembly of section \.text: + +00000000 <__start1>: + 0: 68 94 set + +00000002 <__vec1_start>: + 2: 0f 92 push r0 + 4: 0f b6 in r0, 0x3f ; 63 + 6: 0f 92 push r0 + 8: 01 30 cpi r16, 0x01 ; 1 + a: 0f 90 pop r0 + c: 0f be out 0x3f, r0 ; 63 + e: 0f 90 pop r0 + 10: e8 94 clt + +00000012 <__data1>: + 12: 00 e0 ldi r16, 0x00 ; 0 + 14: 08 00 \.word 0x0008 ; \?\?\?\? + +00000016 <__start2>: + 16: 68 94 set + +00000018 <__vec2_start>: + 18: e1 e0 ldi r30, 0x01 ; 1 + 1a: f0 91 00 00 lds r31, 0x0000 ; 0x800000 <__data6\+0x7fff40> + 1e: f0 93 00 00 sts 0x0000, r31 ; 0x800000 <__data6\+0x7fff40> + 22: 12 01 movw r2, r4 + 24: 12 95 swap r17 + 26: 18 95 reti + 28: 78 10 cpse r7, r8 + 2a: 78 94 sei + 2c: f8 94 cli + 2e: af b6 in r10, 0x3f ; 63 + 30: af be out 0x3f, r10 ; 63 + 32: 18 95 reti + 34: e8 94 clt + +00000036 <__data2>: + 36: 00 e0 ldi r16, 0x00 ; 0 + 38: 0f 00 \.word 0x000f ; \?\?\?\? + +0000003a <__start3>: + 3a: 68 94 set + +0000003c <__vec3_start>: + 3c: 1f 92 push r1 + 3e: 1f b6 in r1, 0x3f ; 63 + 40: 1f 92 push r1 + 42: 11 24 eor r1, r1 + 44: 8f 93 push r24 + 46: 8f 91 pop r24 + 48: 1f 90 pop r1 + 4a: 1f be out 0x3f, r1 ; 63 + 4c: 1f 90 pop r1 + 4e: 18 95 reti + 50: 8f 91 pop r24 + 52: 1f 90 pop r1 + 54: 1f be out 0x3f, r1 ; 63 + 56: 1f 90 pop r1 + 58: 18 95 reti + 5a: 13 94 inc r1 + 5c: e8 94 clt + +0000005e <__data3>: + 5e: 00 e0 ldi r16, 0x00 ; 0 + 60: 11 00 \.word 0x0011 ; \?\?\?\? + +00000062 <__start4>: + 62: 68 94 set + +00000064 <__vec4_start>: + 64: 0f 92 push r0 + 66: 0f b6 in r0, 0x3f ; 63 + 68: 0f 92 push r0 + 6a: 1f 92 push r1 + 6c: 11 24 eor r1, r1 + 6e: 8f 93 push r24 + 70: 8f 91 pop r24 + 72: 1f 90 pop r1 + 74: 0f 90 pop r0 + 76: 0f be out 0x3f, r0 ; 63 + 78: 0f 90 pop r0 + 7a: 18 95 reti + 7c: 8f 91 pop r24 + 7e: 1f 90 pop r1 + 80: 0f 90 pop r0 + 82: 0f be out 0x3f, r0 ; 63 + 84: 0f 90 pop r0 + 86: 18 95 reti + 88: 01 9f mul r16, r17 + 8a: e8 94 clt + +0000008c <__data4>: + 8c: 00 e0 ldi r16, 0x00 ; 0 + 8e: 14 00 \.word 0x0014 ; \?\?\?\? + +00000090 <__start5>: + 90: 68 94 set + +00000092 <__vec5_start>: + 92: 0f 92 push r0 + 94: c8 95 lpm + 96: 0f 90 pop r0 + 98: 18 95 reti + 9a: 0f 90 pop r0 + 9c: 18 95 reti + 9e: e8 94 clt + +000000a0 <__data5>: + a0: 00 e0 ldi r16, 0x00 ; 0 + a2: 07 00 \.word 0x0007 ; \?\?\?\? + +000000a4 <__start6>: + a4: 68 94 set + +000000a6 <__vec6_start>: + a6: af 93 push r26 + a8: af b7 in r26, 0x3f ; 63 + aa: af 93 push r26 + ac: af 91 pop r26 + ae: af bf out 0x3f, r26 ; 63 + b0: af 91 pop r26 + b2: 18 95 reti + b4: af 91 pop r26 + b6: af bf out 0x3f, r26 ; 63 + b8: af 91 pop r26 + ba: 18 95 reti + bc: 88 94 clc + be: e8 94 clt + +000000c0 <__data6>: + c0: 00 e0 ldi r16, 0x00 ; 0 + c2: 0d 00 \.word 0x000d ; \?\?\?\? diff --git a/gas/testsuite/gas/avr/gccisr-01.s b/gas/testsuite/gas/avr/gccisr-01.s new file mode 100644 index 00000000000..82cf9f61254 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-01.s @@ -0,0 +1,127 @@ +.text + +;;; Use SREG + +__start1: + set + +__vec1_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + cpi r16,1 + __gcc_isr 2 + __gcc_isr 0,r0 + clt +__vec1_end: +__data1: + ldi r16, foo - 2 + .word (__vec1_end - __vec1_start) / 2 + +;;; Nothing used. + +__start2: + set + +__vec2_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + ldi r30, 1 + lds r31, 0 + sts 0, r31 + movw r2, r4 + swap r17 + __gcc_isr 2 + reti + __gcc_isr 2 + cpse r7, r8 + sei + cli + in r10, 0x3f + out 0x3f, r10 + reti + __gcc_isr 0,r0 + clt +__vec2_end: +__data2: + ldi r16, foo - 0 + .word (__vec2_end - __vec2_start) / 2 + +;;; Use SREG, ZERO and R24 + +__start3: + set + +__vec3_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + __gcc_isr 2 + reti + __gcc_isr 2 + reti + inc r1 + __gcc_isr 0,r24 + clt +__vec3_end: +__data3: + ldi r16, foo - 3 + .word (__vec3_end - __vec3_start) / 2 + +;;; Use SREG, ZERO, TMP and R24 + +__start4: + set + +__vec4_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + __gcc_isr 2 + reti + __gcc_isr 2 + reti + mul 16, 17 + __gcc_isr 0,r24 + clt +__vec4_end: +__data4: + ldi r16, foo - 4 + .word (__vec4_end - __vec4_start) / 2 + +;;; Use TMP + +__start5: + set + +__vec5_start: + __gcc_isr 1 + lpm + foo = __gcc_isr.n_pushed + __gcc_isr 2 + reti + __gcc_isr 2 + reti + __gcc_isr 0,r0 + clt +__vec5_end: +__data5: + ldi r16, foo - 1 + .word (__vec5_end - __vec5_start) / 2 + +;;; Use SREG, R26 + +__start6: + set + +__vec6_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + __gcc_isr 2 + reti + __gcc_isr 2 + reti + clc + __gcc_isr 0,r26 + clt +__vec6_end: +__data6: + ldi r16, foo - 2 + .word (__vec6_end - __vec6_start) / 2 diff --git a/gas/testsuite/gas/avr/gccisr-02.d b/gas/testsuite/gas/avr/gccisr-02.d new file mode 100644 index 00000000000..b0724c4e9c0 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-02.d @@ -0,0 +1,43 @@ +#name: gccisr-02: __gcc_isr pseudo instruction +#as: -mgcc-isr -mavrtiny +#objdump: -dz +#target: avr-*-* + +.*: +file format elf32-avr + + +Disassembly of section \.text: + +00000000 <__start1>: + 0: 68 94 set + +00000002 <__vec1_start>: + 2: 0f 93 push r16 + 4: 0f b7 in r16, 0x3f ; 63 + 6: 0f 93 push r16 + 8: 21 30 cpi r18, 0x01 ; 1 + a: 0f 91 pop r16 + c: 0f bf out 0x3f, r16 ; 63 + e: 0f 91 pop r16 + 10: e8 94 clt + +00000012 <__data1>: + 12: 00 e0 ldi r16, 0x00 ; 0 + 14: 08 00 \.word 0x0008 ; \?\?\?\? + +00000016 <__start2>: + 16: 68 94 set + +00000018 <__vec2_start>: + 18: 1f 93 push r17 + 1a: 10 e0 ldi r17, 0x00 ; 0 + 1c: 1f 91 pop r17 + 1e: 18 95 reti + 20: e1 2f mov r30, r17 + 22: 1f 91 pop r17 + 24: 18 95 reti + 26: e8 94 clt + +00000028 <__data2>: + 28: 00 e0 ldi r16, 0x00 ; 0 + 2a: 08 00 \.word 0x0008 ; \?\?\?\? diff --git a/gas/testsuite/gas/avr/gccisr-02.s b/gas/testsuite/gas/avr/gccisr-02.s new file mode 100644 index 00000000000..167c42d1de8 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-02.s @@ -0,0 +1,38 @@ +.text + +;;; Use SREG + +__start1: + set + +__vec1_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + cpi r18,1 + __gcc_isr 2 + __gcc_isr 0,r16 + clt +__vec1_end: +__data1: + ldi r16, foo - 2 + .word (__vec1_end - __vec1_start) / 2 + +;;; Use ZERO + +__start2: + set + +__vec2_start: + __gcc_isr 1 + foo = __gcc_isr.n_pushed + __gcc_isr 2 + reti + mov r30,r17 + __gcc_isr 2 + reti + __gcc_isr 0,r16 + clt +__vec2_end: +__data2: + ldi r16, foo - 1 + .word (__vec2_end - __vec2_start) / 2 diff --git a/gas/testsuite/gas/avr/gccisr-03.d b/gas/testsuite/gas/avr/gccisr-03.d new file mode 100644 index 00000000000..0eaa28d1571 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-03.d @@ -0,0 +1,4 @@ +#name: __gcc_isr pseudo instruction, test gccisr-03 +#as: +#error: pseudo instruction `__gcc_isr' not supported +#target: avr-*-* diff --git a/gas/testsuite/gas/avr/gccisr-03.s b/gas/testsuite/gas/avr/gccisr-03.s new file mode 100644 index 00000000000..39938c803b7 --- /dev/null +++ b/gas/testsuite/gas/avr/gccisr-03.s @@ -0,0 +1,6 @@ +.text + +;;; + +__start1: + __gcc_isr 1 diff --git a/include/ChangeLog b/include/ChangeLog index b9405040106..9400f16c9a6 100644 --- a/include/ChangeLog +++ b/include/ChangeLog @@ -1,3 +1,8 @@ +2017-06-30 Georg-Johann Lay + + PR gas/21683 + * opcode/avr.h (AVR_INSN): Add one for __gcc_isr. + 2017-06-30 Maciej W. Rozycki Andrew Bennett diff --git a/include/opcode/avr.h b/include/opcode/avr.h index 1c73022ed49..2212816cbf8 100644 --- a/include/opcode/avr.h +++ b/include/opcode/avr.h @@ -110,6 +110,7 @@ z - Z pointer register (for [e]lpm Rd,Z[+]) M - immediate value from 0 to 255 n - immediate value from 0 to 255 ( n = ~M ). Relocation impossible + N - immediate value from 0 to 255. Relocation impossible s - immediate value from 0 to 7 P - Port address value from 0 to 63. (in, out) p - Port address value from 0 to 31. (cbi, sbi, sbic, sbis) @@ -306,3 +307,7 @@ AVR_INSN (eijmp, "", "1001010000011001", 1, AVR_ISA_EIND, 0x9419) /* DES instruction for encryption and decryption. */ AVR_INSN (des, "E", "10010100EEEE1011", 1, AVR_ISA_DES, 0x940B) +/* Operands are evaluated by hand and won't pop new fux-ups. + The pseudo-insn is hidden behind NOP so that avr-dis.c don't see it. */ +AVR_INSN (__gcc_isr, "", "0000000000000000", 1, AVR_ISA_1200, 0x0) +