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.
This commit is contained in:
Georg-Johann Lay 2017-06-30 16:37:39 +01:00 committed by Nick Clifton
parent 33f466961c
commit 32f76c6773
12 changed files with 1060 additions and 7 deletions

View File

@ -1,3 +1,39 @@
2017-06-30 Georg-Johann Lay <avr@gjlay.de>
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) <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.
2017-06-30 Maciej W. Rozycki <macro@imgtec.com>
* config/tc-mips.c (match_float_constant): Update description.

View File

@ -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, &regno1);
++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, &regno2);
}
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, &reg);
}
*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);
}

View File

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

View File

@ -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 <var>
a: 83 95 inc r24
c: 80 93 60 00 sts 0x0060, r24 ; 0x800060 <var>
10: 8f 91 pop r24
12: 8f bf out 0x3f, r24
14: 8f 91 pop r24
16: 18 95 reti
@end example

View File

@ -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 ; \?\?\?\?

View File

@ -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

View File

@ -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 ; \?\?\?\?

View File

@ -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

View File

@ -0,0 +1,4 @@
#name: __gcc_isr pseudo instruction, test gccisr-03
#as:
#error: pseudo instruction `__gcc_isr' not supported
#target: avr-*-*

View File

@ -0,0 +1,6 @@
.text
;;;
__start1:
__gcc_isr 1

View File

@ -1,3 +1,8 @@
2017-06-30 Georg-Johann Lay <avr@gjlay.de>
PR gas/21683
* opcode/avr.h (AVR_INSN): Add one for __gcc_isr.
2017-06-30 Maciej W. Rozycki <macro@imgtec.com>
Andrew Bennett <andrew.bennett@imgtec.com>

View File

@ -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)