Description
I've encountered a segfault in code generation; the smallest repeatable case I have (with -Oz) is:
# 1 "<built-in>"
# 1 "foo.c"
static const char *g_default_sigstr[2] = { "", "" };
char *strsignal(int signum)
{
if (signum > 31) return (char *)"no";
switch (signum)
{
case 1: return (char *)"SIGUSR1";
case 2: return (char *)"SIGUSR2";
case 3: return (char *)"SIGALRM";
case 6: return (char *)"SIGSTOP";
case 7: return (char *)"SIGTSTP";
case 8: return (char *)"SIGCONT";
case 17: return (char *)"SIGWORK";
default: break;
}
return (char *)g_default_sigstr[signum];
}
Many changes to this code will result in a successful compile: removing any of the cases, changing case 1 to case 0, changing the size of g_default_sigstr
to 1, changing the argument type to unsigned char.
A debug build gives the perhaps more helpful message:
fatal error: error in backend: VReg has no regclass after selection: $uhl = COPY %5:gpr(s24) (in function: strsignal)
I've traced through in llvm a little, trying to work out what's going wrong. I'm not at all familiar with LLVM's internals, so I might be way off the mark with this, sorry.
The machine block in the generated LLVM IR is:
if.end: ; preds = %entry
%switch.tableidx = add i24 %signum, -1
%0 = icmp ult i24 %switch.tableidx, 17
br i1 %0, label %switch.hole_check, label %sw.epilog
After some processing, the following instructions are in the program code (amongst, of course, many others):
/* A chain of COPYs ending in a G_TRUNC is created over time */
%0:r24(s24) = G_LOAD %1:gpr(p0) :: (invariant load 3 from %fixed-stack.0, align 1) /* signum */
%4:_(s24) = G_CONSTANT i24 -1
%5:_(s24) = G_ADD %0:_, %4:_ /* %5 is %switch.tableidx, signum - 1 */
%65:_(s24) = COPY %5:_(s24) /* part of a rewrite of %8:_(s32) = G_ZEXT %5:_(s24) */
%71:_(s24) = COPY %65:_(s24) /* Rewrite from %71:_(s24) = G_EXTRACT %8:_(s32), 0 */
%73:_(s8) = G_TRUNC %71:_(s24)
/* these three show %73 being used as s8 */
%50:r8(s8) = COPY %73:r8(s8)
$l = COPY %50:r8(s8)
CALL24 &_lshru, ...
/* These show %5 still being used */
%7:gpr(s1) = G_ICMP intpred(ult), %5:gpr(s24), %6:gpr
G_BRCOND %7:gpr(s1), %bb.4
Eventually the combiner spots the COPY
chain and CombinerHelper::applyNarrowOp
kicks in. This removes the G_TRUNC
and changes the G_ADD
to pre-truncate:
Try combining %65:_(s24) = COPY %5:_(s24)
Try combining %71:_(s24) = COPY %65:_(s24)
Try combining %73:_(s8) = G_TRUNC %71:_(s24)
Applying legalizer ruleset to: 39, Tys={s8, s8, }, Opcode=39, MMOs={}
.. match
.. .. Legal, 0, LLT_invalid
Erasing: %73:_(s8) = G_TRUNC %71:_(s24)
Erasing: %73:_(s8) = G_TRUNC %71:_(s24)
Changing: %5:_(s24) = G_ADD %0:_, %4:_
Creating: G_TRUNC
Creating: G_TRUNC
Changed: %73:_(s8) = G_ADD %79:_, %80:_
Created: %79:_(s8) = G_TRUNC %0:_(s24)
Created: %80:_(s8) = G_TRUNC %4:_(s24)
I think when this happens %5 is no longer set by any instruction. Eventually the G_BRCOND
folds the comparison away and produces:
%81:o24(s24) = LD24ri 17
$uhl = COPY %5:gpr(s24)
SUB24ao %81:o24(s24), implicit-def $uhl, implicit-def $f, implicit $uhl
JQCC %bb.4, 3, implicit $f
Because the %5:_(s24) = G_ADD %0:_, %4:_
isn't there any more, %5 has no register class. In a debug build all registers are checked for a valid register class while in a release build the compilation carries on for a short while before segfaulting.
If I disable the Z80PostLegalizerCombinerHelper
rule for narrow_op
with -mllvm --z80postlegalizercombinerhelper-disable-rule -mllvm narrow_op
the compilation succeeds.
I think the root cause is that CombinerHelper::matchNarrowOp()
checks the register of operand 1 of the G_TRUNC
for multiple uses, but register %71 only has one: to be safe, registers %71, %65, and %5 all need to have only one use, or the COPY
s need to be merged to eliminate %71 and %65, or perhaps the earlier rewrites of G_ZEXT
and G_EXTRACT
shouldn't use COPY
s - I'm beyond my understanding of LLVM to know what's appropriate.