diff options
author | Georg-Johann Lay <avr@gjlay.de> | 2017-06-30 16:37:39 +0100 |
---|---|---|
committer | Nick Clifton <nickc@redhat.com> | 2017-06-30 16:37:39 +0100 |
commit | 32f76c677333510350f21a40db062a8d17995c53 (patch) | |
tree | e0471d427aaed0ea30d3ce0044a55cc5c95a1816 /gas/config | |
parent | 33f466961ce01a7db6dbec6b39aafb7af1855645 (diff) | |
download | gdb-32f76c677333510350f21a40db062a8d17995c53.zip gdb-32f76c677333510350f21a40db062a8d17995c53.tar.gz gdb-32f76c677333510350f21a40db062a8d17995c53.tar.bz2 |
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) <have_gccisr>: Add field.
(avr_opt): Add initializer for have_gccisr.
(enum options) <OPTION_HAVE_GCCISR>: 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) <avr_no_sreg_hash, avr_gccisr_opcode>: Initialize them.
(avr_operand) <pregno>: Add argument and set *pregno if function
is called for a register constraint.
[N]: Handle constraint.
(avr_operands) <avr_operand>: 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.
Diffstat (limited to 'gas/config')
-rw-r--r-- | gas/config/tc-avr.c | 594 | ||||
-rw-r--r-- | gas/config/tc-avr.h | 6 |
2 files changed, 593 insertions, 7 deletions
diff --git a/gas/config/tc-avr.c b/gas/config/tc-avr.c index 79837c8..7e3f719 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.<NUM>' + 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 399656f..0cfe9ff 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); |