+
+ 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);