[LLVMdev] Enhancing TableGen

David A. Greene greened at obbligato.org
Fri Oct 7 08:05:00 PDT 2011


Che-Liang Chiou <clchiou at gmail.com> writes:

> My purpose is to eliminate copy-paste style of programming in td files
> as much as possible, but only to a point that the new language
> constructs do not create too much overhead/readability-downgrade.

Yes!

> In other words, I am targeting those low-hanging fruit of copy-paste
> programmings in td files that are eliminated by a simple for-loop
> syntax. The repetitive patterns I observed in PTX backend (and
> probably some other backends) are:
> * Members of a multiclass, such as those in PTXInstrInfo.td.
> * Consecutive similar defs, such as register declarations.

Yep.

> [Why for-loop?]
> * All I need is a simple iteration language construct. I believe there
> is no simpler constructs (in terms of readability) than a for-loop,
> but I am happy to be convinced otherwise.
> * It is sufficient to eliminate the most common copy-paste programming
> that I observed.
> * It is simple enough to understand and maintain, at least I believe so.

I mostly agree with these.  One other thing I've found useful is the
ability to abstract out the type information.  For example, in x86 land,
an SSE/AVX add is always:

(set (type regclass:reg), (type (add (type regclass:reg), (type regclass:reg))))

Similarly, a sub is:

(set (type regclass:reg), (type (sub (type regclass:reg), (type regclass:reg))))

In fact most binary operations are:

(set (type regclass:reg), (type (op (type regclass:reg), (type regclass:reg))))

So why write hundreds of patterns to express this?  Using the for-loop
syntax:

// WARNING: Pseudo-code, many details elided for presentation purposes.

multiclass binop<opcode> : sse_binop<opcode>, avx_binop<opcode>;

multiclass sse_binop<opcode> {
  for type = [f32, f64, v4f32, v2f64]
      regclass = [FP32, FP64, VR128, VR128]
      suffix = [ss, sd, ps, pd] {

    def !toupper(suffix)#rr : Instr<
      [(set (type regclass:$dst), (type (opcode (type regclass:$src1),
                                                (type regclass:$src2))))]>;
    def !toupper(suffix)#rm : Instr<
      [(set (type regclass:$dst), (type (opcode (type regclass:$src1),
                                                (type addr:$src2))))]>;
  }
}

multiclass avx_binop<opcode> {
  for type = [f32, f64, v4f32, v2f64, v8f32, v4f64]
      regclass = [FP32, FP64, VR128, VR128, VR256, VR256]
      prefix = [x, x, x, x, y, y]
      suffix = [ss, sd, ps, pd] {

    def V#prefix#NAME#!toupper(suffix)#rr : Instr<
      [(set (type regclass:$dst), (type (opcode (type regclass:$src1),
                                                (type regclass:$src2))))]>;
    def V#prefix#NAME#!toupper(suffix)#rm : Instr<
      [(set (type regclass:$dst), (type (opcode (type regclass:$src1),
                                                (type addr:$src2))))]>;
  }
}

def ADD : binop<add>;
def SUB : binop<add>;
def MUL : binop<add>;
def DIV : binop<add>;
[...]

Here I am treating "#" as an infix !strconcat.  This makes things much
easier to read than both !strconcat() and a double-# notation, IMHO.

Now each binary pattern is only specified twice and even that
duplication can be eliminated with a little more abstraction.  Perhaps
that's not worth it, however.  I can live with this level of
duplication.

I would also like to replace #NAME# with a "real" Record field that is
automatically added to def-type Records.  This removes a lot of the
hackiness of #NAME#.

> [Why preprocessor?]
>
> The TGParser.cpp as its current form parses and emits the results in
> one-pass. That means it would emit the for-loop body even before we
> are done parsing the entire for-loop.

It doesn't have to.  I'm working on a for loop that is not preprocessor
based.  It uses the same technique as the multiclass: remember the most
inner for loop seen and instantiate everything after the entire for loop
body is parsed.

> So I believe a non-preprocessor approach would require 2 passes. The
> first pass parses the input and generates a simple syntax tree, and
> the second pass evaluate the syntax tree and emits output records (In
> fact, this is how I implemented the current preprocessor). And I
> believe that changing TGParser.cpp to accommodate 2 passes is quite a
> lot, and so I chose a preprocessor.

In the long run, a two-pass parser would probably be more maintainable
but I agree it's a big job.  That's why I went with a compromise of
treating for like a sort of multiclass, at least in the instantiation
mechanics.  I'm still working on it so I haven't got all the details
together yet.

> But if you think we should really rewrite TGParser.cpp to parse and
> evaluate for-loops correctly, I am glad that we could get away with a
> preprocessor.

I think we can do it without completely rewriting the parser.

> [Why NOT while-loop?]
> * A while-loop requires evaluating an loop-condition expression; this
> is complexity that I would like to avoid.

I agree.  It's not needed.  Simple iteration over a list is enoguh.

> [Why NO if-else?]
> * It requires at least evaluating a Boolean expression, too.
> * If a piece of td codes is complicated enough that we need an if-else
> to eliminate its duplication, I think it is worthy of the duplication.

Perhaps.  I'm sort of on the fence on this.  I don't like the !if stuff
I introduced for readability reasons.  An imperitive-style if might make
things easier but I think I could live with the duplication.

> [Why NO abstractions (like `define foo(a, b, c)`)?]
> * Abstractions is probably worthy of, but I am not sure yet. I think
> we could wait until it is clear that we really need abstractions.

I'm not sure what you mean by this abstraction.  Can you elaborate?

> [string vs token]
>
> The preprocessor (as its current form) has tokens by default, and it
> only converts a series of tokens and white spaces into a string if
> explicitly required (by a special escape #"#, see example below).

On further thought, I think this is correct.  My plan with the
parser-integrated for is to allow the user to declare the type of the
iterator.  It solves a number of problems, most importantly TableGen's
declaration before use requirement.

> ----------------------------------------
> #for i = sequence(0, 127)
> def P#i# : PTXReg<#"#p#i##"#>;
> #end
> ----------------------------------------
> * Anything between #"# are quoted, including white spaces and
> non-tokens. E.g., #"#hello  world#"# --> "hello  world"

As mentioned above, I've been thinking about making "#" an infix
equivalent to !strconcat().  This would require the parser to implicitly
cast values to string if necessary but that's not hard to do.

> * Macro variable needs a '#' character at both front and back. This
> looks like the multiclass's #NAME# substitution, and so I think is
> more consistent than prepending a single '#' at front.

To handle existing #NAME# constructs, I was planing to define a trailing
# as a !strconcat(string, "").  This keeps consistency and provides more
flexibility to the # operator.

> What do you think? Which one is more readable to you? !case<> or #"# or ... ?

I like an infix #.  It's consistent with at least some other languages.

> [Can the for-loop proopsal be a preprocessing phase?]
>
> I guess the example Dave gave (see below) cannot be handled in a (even
> extended) preprocessor. I am not keen on implementing for-loop in a
> preprocessor. I chose a preprocessor because I think it would cause
> least impact to the codebase and, to be honest, I didn't address of
> the pattern that Dave gave in his example in my design. I was trying
> to avoid variable-length lists because I think that is too complicated
> to users. But I could be wrong.

I think it could be if not done carefully.  I am already getting
feedback that some of my proposals are too complicated.  I am really
taking that to heart and trying to find a good balance among
maintainability, clarity and redundancy.  I think things can be better
than they are now and better than what I've proposed so far.  No one
gets the right answer in a vacuum.  :)

                              -Dave



More information about the llvm-dev mailing list