jacobly0 / llvm-project

This fork of the canonical git mirror of the LLVM subversion repository adds (e)Z80 targets. Please refer to the wiki for important build instructions.
https://github.com/jacobly0/llvm-project/wiki
123 stars 15 forks source link

VReg has no regclass after selection #18

Closed codebje closed 3 years ago

codebje commented 3 years ago

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.

https://github.com/jacobly0/llvm-project/blob/170be88120e3aa88c20eea5615ba76b8f1d6c647/llvm/lib/CodeGen/GlobalISel/InstructionSelect.cpp#L185

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 COPYs need to be merged to eliminate %71 and %65, or perhaps the earlier rewrites of G_ZEXT and G_EXTRACT shouldn't use COPYs - I'm beyond my understanding of LLVM to know what's appropriate.

jacobly0 commented 3 years ago

Fixed.