X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=gas%2Fconfig%2Ftc-avr.c;h=3e0b3102c9fecab0820817d922dba5d2dc036d18;hb=9fcc34577ed663db6143053cd0bf0c271bb6a3e2;hp=999ffb487153f3572774a1d56a747f3ff33bc1d7;hpb=f73e41ef3703be34db1c7e08271e6ac8bd758144;p=deliverable%2Fbinutils-gdb.git diff --git a/gas/config/tc-avr.c b/gas/config/tc-avr.c index 999ffb4871..3e0b3102c9 100644 --- a/gas/config/tc-avr.c +++ b/gas/config/tc-avr.c @@ -1,6 +1,6 @@ /* tc-avr.c -- Assembler code for the ATMEL AVR - Copyright (C) 1999-2016 Free Software Foundation, Inc. + Copyright (C) 1999-2020 Free Software Foundation, Inc. Contributed by Denis Chertykov This file is part of GAS, the GNU Assembler. @@ -54,6 +54,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[] = "$"; @@ -72,19 +173,19 @@ struct mcu_type_s static struct mcu_type_s mcu_types[] = { {"avr1", AVR_ISA_AVR1, bfd_mach_avr1}, -/* TODO: insruction set for avr2 architecture should be AVR_ISA_AVR2, +/* TODO: instruction set for avr2 architecture should be AVR_ISA_AVR2, but set to AVR_ISA_AVR25 for some following version of GCC (from 4.3) for backward compatibility. */ {"avr2", AVR_ISA_AVR25, bfd_mach_avr2}, {"avr25", AVR_ISA_AVR25, bfd_mach_avr25}, -/* TODO: insruction set for avr3 architecture should be AVR_ISA_AVR3, +/* TODO: instruction set for avr3 architecture should be AVR_ISA_AVR3, but set to AVR_ISA_AVR3_ALL for some following version of GCC (from 4.3) for backward compatibility. */ {"avr3", AVR_ISA_AVR3_ALL, bfd_mach_avr3}, {"avr31", AVR_ISA_AVR31, bfd_mach_avr31}, {"avr35", AVR_ISA_AVR35, bfd_mach_avr35}, {"avr4", AVR_ISA_AVR4, bfd_mach_avr4}, -/* TODO: insruction set for avr5 architecture should be AVR_ISA_AVR5, +/* TODO: instruction set for avr5 architecture should be AVR_ISA_AVR5, but set to AVR_ISA_AVR51 for some following version of GCC (from 4.3) for backward compatibility. */ {"avr5", AVR_ISA_AVR51, bfd_mach_avr5}, @@ -300,6 +401,21 @@ static struct mcu_type_s mcu_types[] = {"atxmega16e5", AVR_ISA_XMEGA, bfd_mach_avrxmega2}, {"atxmega8e5", AVR_ISA_XMEGA, bfd_mach_avrxmega2}, {"atxmega32x1", AVR_ISA_XMEGA, bfd_mach_avrxmega2}, + {"attiny212", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny214", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny412", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny414", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny416", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny417", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny814", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny816", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny817", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny1614", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny1616", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny1617", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny3214", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny3216", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, + {"attiny3217", AVR_ISA_XMEGA, bfd_mach_avrxmega3}, {"atxmega64a3", AVR_ISA_XMEGA, bfd_mach_avrxmega4}, {"atxmega64a3u",AVR_ISA_XMEGAU, bfd_mach_avrxmega4}, {"atxmega64a4u",AVR_ISA_XMEGAU, bfd_mach_avrxmega4}, @@ -355,9 +471,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"; @@ -398,7 +515,7 @@ static struct exp_mod_s exp_mod[] = {"hhi8", BFD_RELOC_AVR_MS8_LDI, BFD_RELOC_AVR_MS8_LDI_NEG, 0}, }; -/* A union used to store indicies into the exp_mod[] array +/* A union used to store indices into the exp_mod[] array in a hash table which expects void * data types. */ typedef union { @@ -412,6 +529,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 { @@ -420,7 +564,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[] = @@ -432,6 +577,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 } }; @@ -525,7 +671,7 @@ md_show_usage (FILE *stream) " avr51 - enhanced AVR core with up to 128K program memory\n" " avr6 - enhanced AVR core with up to 256K program memory\n" " avrxmega2 - XMEGA, > 8K, < 64K FLASH, < 64K RAM\n" - " avrxmega3 - XMEGA, > 8K, <= 64K FLASH, > 64K RAM\n" + " avrxmega3 - XMEGA, RAM + FLASH < 64K, Flash visible in RAM\n" " avrxmega4 - XMEGA, > 64K, <= 128K FLASH, <= 64K RAM\n" " avrxmega5 - XMEGA, > 64K, <= 128K FLASH, > 64K RAM\n" " avrxmega6 - XMEGA, > 128K, <= 256K FLASH, <= 64K RAM\n" @@ -540,6 +686,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); } @@ -606,14 +753,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; } @@ -655,6 +826,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; } @@ -851,7 +1033,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; @@ -900,6 +1083,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) @@ -1075,6 +1261,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); @@ -1129,6 +1325,15 @@ avr_operand (struct avr_opcodes_s *opcode, return op_mask; } +/* TC_FRAG_INIT hook */ + +void +avr_frag_init (fragS *frag) +{ + memset (& frag->tc_frag_data, 0, sizeof frag->tc_frag_data); +} + + /* Parse instruction operands. Return binary opcode. */ @@ -1140,7 +1345,8 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) char *frag = frag_more (opcode->insn_size * 2); 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) @@ -1153,7 +1359,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. */ @@ -1166,6 +1372,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) { reg2 = reg1; reg2_present = 1; + regno2 = regno1; } else { @@ -1177,7 +1384,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) @@ -1190,6 +1397,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")); @@ -1200,7 +1410,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) (AVR core bug, fixed in the newer devices). */ if (!(avr_opt.no_skip_bug || (avr_mcu->isa & (AVR_ISA_MUL | AVR_ISA_MOVW))) - && AVR_SKIP_P (prev)) + && AVR_SKIP_P (frag_now->tc_frag_data.prev_opcode)) as_warn (_("skipping two-word instruction")); bfd_putl32 ((bfd_vma) bin, frag); @@ -1208,7 +1418,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) else bfd_putl16 ((bfd_vma) bin, frag); - prev = bin; + frag_now->tc_frag_data.prev_opcode = bin; *line = str; return bin; } @@ -1219,7 +1429,7 @@ avr_operands (struct avr_opcodes_s *opcode, char **line) valueT md_section_align (asection *seg, valueT addr) { - int align = bfd_get_section_alignment (stdoutput, seg); + int align = bfd_section_alignment (seg); return ((addr + (1 << align) - 1) & (-1UL << align)); } @@ -1616,9 +1826,9 @@ tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED, return NULL; } - reloc = xmalloc (sizeof (arelent)); + reloc = XNEW (arelent); - reloc->sym_ptr_ptr = xmalloc (sizeof (asymbol *)); + reloc->sym_ptr_ptr = XNEW (asymbol *); *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; @@ -1673,7 +1883,7 @@ md_assemble (char *str) specifications with same mnemonic who's ISA bits matches. This requires include/opcode/avr.h to have the instructions with - same mnenomic to be specified in sequence. */ + same mnemonic to be specified in sequence. */ while ((opcode->isa & avr_mcu->isa) != opcode->isa) { @@ -1694,6 +1904,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. */ @@ -1707,7 +1924,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; @@ -1731,8 +1951,6 @@ const exp_mod_data_t exp_mod_data[] = { "hi8", 1, BFD_RELOC_AVR_8_HI, "`hi8' " }, { "hlo8", 1, BFD_RELOC_AVR_8_HLO, "`hlo8' " }, { "hh8", 1, BFD_RELOC_AVR_8_HLO, "`hh8' " }, - /* End of list. */ - { NULL, 0, 0, NULL } }; /* Parse special CONS expression: pm (expression) or alternatively @@ -1742,16 +1960,17 @@ const exp_mod_data_t exp_mod_data[] = const exp_mod_data_t * avr_parse_cons_expression (expressionS *exp, int nbytes) { - const exp_mod_data_t *pexp = &exp_mod_data[0]; char *tmp; + unsigned int i; tmp = input_line_pointer = skip_space (input_line_pointer); /* The first entry of exp_mod_data[] contains an entry if no expression modifier is present. Skip it. */ - for (pexp++; pexp->name; pexp++) + for (i = 0; i < ARRAY_SIZE (exp_mod_data); i++) { + const exp_mod_data_t *pexp = &exp_mod_data[i]; int len = strlen (pexp->name); if (nbytes == pexp->nbytes @@ -1820,7 +2039,7 @@ avr_cons_fix_new (fragS *frag, } if (bad) - as_bad (_("illegal %srelocation size: %d"), pexp_mod_data->error, nbytes); + as_bad (_("illegal %s relocation size: %d"), pexp_mod_data->error, nbytes); } static bfd_boolean @@ -1844,7 +2063,7 @@ tc_cfi_frame_initial_instructions (void) cfi_add_CFA_def_cfa (32, return_size); /* Note that AVR consistently uses post-decrement, which means that things - do not line up the same way as for targers that use pre-decrement. */ + do not line up the same way as for targets that use pre-decrement. */ cfi_add_CFA_offset (DWARF2_DEFAULT_RETURN_COLUMN, 1-return_size); } @@ -2010,7 +2229,7 @@ avr_create_property_section (void) sec = bfd_make_section (stdoutput, section_name); if (sec == NULL) as_fatal (_("Failed to create property section `%s'\n"), section_name); - bfd_set_section_flags (stdoutput, sec, flags); + bfd_set_section_flags (sec, flags); sec->output_section = sec; return sec; } @@ -2069,8 +2288,7 @@ create_record_for_frag (segT sec, fragS *fragP) { struct avr_property_record_link *prop_rec_link; - prop_rec_link = xmalloc (sizeof (struct avr_property_record_link)); - memset (prop_rec_link, 0, sizeof (*prop_rec_link)); + prop_rec_link = XCNEW (struct avr_property_record_link); gas_assert (fragP->fr_next != NULL); if (fragP->tc_frag_data.is_org) @@ -2186,7 +2404,7 @@ avr_create_and_fill_property_section (void) return; prop_sec = avr_create_property_section (); - bfd_set_section_size (stdoutput, prop_sec, sec_size); + bfd_set_section_size (prop_sec, sec_size); subseg_set (prop_sec, 0); frag_base = frag_more (sec_size); @@ -2209,3 +2427,384 @@ 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. */ + + S_SET_VALUE (sy, 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 = where - fr->fr_literal; + gas_assert (fr->fr_fix <= (valueT) 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); +}