In attempting to write an .md file for a homebrew
two-operand RISC, I've hit what appears to be an
lcc 4.2 register allocation bug affecting two-operand
targets having register locals.
I can reproduce the problem with a simple edit to the
existing <mips.md> target by changing the ADDI4 template
to use a two-operand rule.
I thought I'd check here on the chance that someone else
has already worked though this problem; see also [2a]
for another post describing this issue.
--------------------------
Problem Detail:
When targeting a two operand architecture, lcc 4.2 generates
code that overwrites the destination operand register before
it has been used as a source operand.
This happens whenever the destination is a register local
that also appears as the second source operand in the
lcc %c = OP(%0,%1) expression being matched.
Given:
:
: register int i,j;
:
: i = j + i;
:
Lcc emits the following code on a modified[3a-d] 4.2 MIPS
target having a hypothetical two-operand ADD/SUB.
Emitted code for i = j + i stomps on reg $30 before the add:
:
: move $30,$23
: add2u $30,$30
:
Note that the targets shipped with lcc 4.2 are all either
three-operand machines (Alpha/MIPS/SPARC), or two-operand
machines without register locals (x86), so it is plausible
that this escaped notice in the lcc 4.2 test cases.
(it is also plausible that I'm making a silly mistake somehere)
The code in ralloc() intended[4] to prevent this problem
never kicks in for register locals, which are already
allocated and assigned to an expression tree at the point
in a compilation when that ralloc() register masking code
is visited.
Adding[5] an assert to emitasm() to test "kids[1]" seems
to catch the bug in action.
--------------------------
Temporary debug patch
Hacking[6] <gen.c>'s rtarget() to always insert a temp
register load dodges the problem at the cost of extra
register moves everywhere:
code for i = j + i with rtarget hack:
:
: move $24,$30
: add2u $24,$23
: move $30,$24
:
I'm not sure that rtarget() is actually the best place in
the compiler to attempt a real patch for the problem.
It seems like swapping operands could avoid the overwrite
in most cases, especially on a two-operand target having
a Reverse Subtract instruction.
But I don't know enough about the lcc DAG/tree processing
to see where & how to do this properly without accidentally
introducing a heinous bug for other code sequences.
- Brian
References:
[1] F&H-95 "A retargetable C compiler: design and implementation", Fraser & Hanson , 1995
[2a] solitary 2008 comp.compilers.lcc post describing the same problem
http://groups.google.com/group/comp.compilers.lcc/msg/c9eb1c4c0b555988
[2b] obligatory xkcd reference for [2a]
http://xkcd.com/979/
[3] Changes to lcc 4.2 to create two operand ADDI4 and SUBI4 patterns:
[3a] start with the lcc 4.2 distribution :
https://github.com/drh/lcc/tree/v4_2
[3b] patch get() in <gram.c,y> to accept column 0 "!" as a line comment in .md template sections:
if (*bp == 0) {
bp = buf;
*bp = 0;
+
+ /* patch to accept "!" in column 0 as a line comment character in pattern section of .md files */
+ do
+ {
+ if (fgets(buf, sizeof buf, infp) == NULL) return EOF;
+ yylineno++;
+
+ } while (buf[0] == '!');
+
[3c] patch <mips.md> to change ADDI4 target instruction to a two operand template:
-reg: ADDI4(reg,rc) "addu $%c,$%0,%1\n" 1
+
+!
+! replace normal ADDI4 instruction, addu, with hypothetical add2u
+! in order to test two operand register allocation
+!
+!reg: ADDI4(reg,rc) "addu $%c,$%0,%1\n" 1
+reg: ADDI4(reg,rc) "?move $%c,$%0\nadd2u $%c,%1\n" 1
+
[3d] patch <mips.md> to change SUBI4 target instruction to a two operand template:
-reg: SUBI4(reg,rc) "subu $%c,$%0,%1\n" 1
+
+!
+! replace normal SUBI4 instruction, subu, with hypothetical sub2u
+! in order to test two operand register allocation
+!
+!reg: SUBI4(reg,rc) "subu $%c,$%0,%1\n" 1
+reg: SUBI4(reg,rc) "?move $%c,$%0\nsub2u $%c,%1\n" 1
+
[4] F&H-95, page 419, description of two operand register masking code in <gen.c>
"
" Such templates change the output register before reading
" all input registers, so they violate the rule above.
"
" To handle two operand instructions, we mark their code
" templates with a leading question mark. <snip>
"
" When ralloc sees such a rule, it edits mask to prevent
" reallocation of all input registers but the first
"
[5] <gen.c> add an assert to emitasm() to catch any destination register overwrites
if (*fmt == '?') {
+
+ //
+ // check for two-operand destination overwrite
+ //
+ assert( p->syms[RX] != p->x.kids[1]->syms[RX] );
+
[6] <gen.c> hack rtarget() to always assign a temporary register:
- if (r != q->syms[RX] && !q->syms[RX]->x.wildcard) {
+ //
+ // original code:
+ // if (r != q->syms[RX] && !q->syms[RX]->x.wildcard) {
+ //
+ // temporary debug hack, always allocating temp patches two-operand bug at the cost of temp loads everywhere
+ //
+ if ( 1 ) {
+
[7] complete test program to reproduce problem
//
// <bug_two_operand_md.c>
//
// Reproducer for LCC 4.2 register overwrite bug.
//
// This bug affects two operand machines using register locals.
//
// Two-operand instruction templates generate bad code
// whenever when %c == %1 in the tree being matched:
//
// %c = OP(%0,%1)
//
// Assembly snippets below were generated by a modifed lcc4.2 <mips.md>
// having ADDI4 and SUBI4 changed to use two-operand templates:
//
// reg: ADDI4(reg,rc) "?move $%c,$%0\nadd2u $%c,%1\n" 1
//
// reg: SUBI4(reg,rc) "?move $%c,$%0\nsub2u $%c,%1\n" 1
//
main()
{
register int i = 1;
register int j = 2;
register int k = 4;
//
// Register allocator assigns i to $30, j to $23, k to $22 :
//
// la $30,1
// la $23,2
// la $22,4
//
i = i + j; // emitted code OK
//
// destination and first operand match, so "?move %c,%0" is not emitted:
//
// add2u $30,$23
//
i = k + j; // emitted code OK
//
// overwrite of %c by move is harmless here because old value of i is not used:
//
// move $30,$22
// add2u $30,$23
//
i = j + i; // emitted code fails
//
// %1, aka destination, is overwritten by move before add :
//
// move $30,$23
// add2u $30,$30
//
i = j - i; // emitted code fails
//
// %1, aka destination, is overwritten by move before subtract :
//
// move $30,$23
// sub2u $30,$30
//
return(0);
}