diff options
author | Josef Zlomek <zlomekj@suse.cz> | 2004-05-27 06:28:12 +0200 |
---|---|---|
committer | Josef Zlomek <zlomek@gcc.gnu.org> | 2004-05-27 04:28:12 +0000 |
commit | e314a036a8942fe4ce5d9fd586f0a8bac90f6df3 (patch) | |
tree | 495f11c36b83e1b4a4adfe71e9dc03141b117804 /gcc/emit-rtl.c | |
parent | 2140214f631a6ec8a4cd9c45e1001a5a40fef5e7 (diff) | |
download | gcc-e314a036a8942fe4ce5d9fd586f0a8bac90f6df3.zip gcc-e314a036a8942fe4ce5d9fd586f0a8bac90f6df3.tar.gz gcc-e314a036a8942fe4ce5d9fd586f0a8bac90f6df3.tar.bz2 |
re PR middle-end/14084 (Reg allocator incorrectly changes REG_OFFSET)
PR middle-end/14084
* emit-rtl.c (gen_rtx_REG_offset): Adjust the offset according
to size of decl.
From-SVN: r82313
Diffstat (limited to 'gcc/emit-rtl.c')
-rw-r--r-- | gcc/emit-rtl.c | 87 |
1 files changed, 85 insertions, 2 deletions
diff --git a/gcc/emit-rtl.c b/gcc/emit-rtl.c index 9211c08..bc23bb7 100644 --- a/gcc/emit-rtl.c +++ b/gcc/emit-rtl.c @@ -746,13 +746,96 @@ gen_reg_rtx (enum machine_mode mode) return val; } -/* Generate a register with same attributes as REG, - but offsetted by OFFSET. */ +/* Generate a register with same attributes as REG, but offsetted by OFFSET. + Do the big endian correction if needed. */ rtx gen_rtx_REG_offset (rtx reg, enum machine_mode mode, unsigned int regno, int offset) { rtx new = gen_rtx_REG (mode, regno); + tree decl; + HOST_WIDE_INT var_size; + + /* PR middle-end/14084 + The problem appears when a variable is stored in a larger register + and later it is used in the original mode or some mode in between + or some part of variable is accessed. + + On little endian machines there is no problem because + the REG_OFFSET of the start of the variable is the same when + accessed in any mode (it is 0). + + However, this is not true on big endian machines. + The offset of the start of the variable is different when accessed + in different modes. + When we are taking a part of the REG we have to change the OFFSET + from offset WRT size of mode of REG to offset WRT size of variable. + + If we would not do the big endian correction the resulting REG_OFFSET + would be larger than the size of the DECL. + + Examples of correction, for BYTES_BIG_ENDIAN WORDS_BIG_ENDIAN machine: + + REG.mode MODE DECL size old offset new offset description + DI SI 4 4 0 int32 in SImode + DI SI 1 4 0 char in SImode + DI QI 1 7 0 char in QImode + DI QI 4 5 1 1st element in QImode + of char[4] + DI HI 4 6 2 1st element in HImode + of int16[2] + + If the size of DECL is equal or greater than the size of REG + we can't do this correction because the register holds the + whole variable or a part of the variable and thus the REG_OFFSET + is already correct. */ + + decl = REG_EXPR (reg); + if ((BYTES_BIG_ENDIAN || WORDS_BIG_ENDIAN) + && decl != NULL + && offset > 0 + && GET_MODE_SIZE (GET_MODE (reg)) > GET_MODE_SIZE (mode) + && ((var_size = int_size_in_bytes (TREE_TYPE (decl))) > 0 + && var_size < GET_MODE_SIZE (GET_MODE (reg)))) + { + int offset_le; + + /* Convert machine endian to little endian WRT size of mode of REG. */ + if (WORDS_BIG_ENDIAN) + offset_le = ((GET_MODE_SIZE (GET_MODE (reg)) - 1 - offset) + / UNITS_PER_WORD) * UNITS_PER_WORD; + else + offset_le = (offset / UNITS_PER_WORD) * UNITS_PER_WORD; + + if (BYTES_BIG_ENDIAN) + offset_le += ((GET_MODE_SIZE (GET_MODE (reg)) - 1 - offset) + % UNITS_PER_WORD); + else + offset_le += offset % UNITS_PER_WORD; + + if (offset_le >= var_size) + { + /* MODE is wider than the variable so the new reg will cover + the whole variable so the resulting OFFSET should be 0. */ + offset = 0; + } + else + { + /* Convert little endian to machine endian WRT size of variable. */ + if (WORDS_BIG_ENDIAN) + offset = ((var_size - 1 - offset_le) + / UNITS_PER_WORD) * UNITS_PER_WORD; + else + offset = (offset_le / UNITS_PER_WORD) * UNITS_PER_WORD; + + if (BYTES_BIG_ENDIAN) + offset += ((var_size - 1 - offset_le) + % UNITS_PER_WORD); + else + offset += offset_le % UNITS_PER_WORD; + } + } + REG_ATTRS (new) = get_reg_attrs (REG_EXPR (reg), REG_OFFSET (reg) + offset); return new; |