[llvm-dev] The Trouble with Triples

Renato Golin via llvm-dev
Thu, 17 Sep 2015 10:00:28 -0000

On 16 September 2015 at 23:52, Eric Christopher <echristo at gmail.com>
wrote:
> TargetTuple appears to be related to the TargetParser as you bring up in
> this mail. They're two separate parts of similar problems - people
trying to
> both serialize command line options and communication from the front end to
> the backend with respect to target information.

Yes, though the relation between TargetTuple and TargetParser is
lighter than I made seem. Tuple doesn't need the parser, but for
complicated architectures, the parser simplifies the Tuple
considerably. For now, this is just ARM. MIPS problems seem to be more
related to how the values relate to each other, not how the string is
parsed, so it's quite likely that the TargetParser is *only* relevant
to ARM. But for ARM, it is crucial. See below.


> This leads me to a question: What can't be done to TargetMachine to
avoid
> this serialization?

The misconception here is that we *want* to do the serialization, or
that was the design we came up with to tackle complexity. In the ARM
world, there are *two* big architectural problems:

1. ARM licenses every part of the chip and allows licencees to
mix&match whatever they want.

This is key to ARM's success, but it's a nightmare for tools, that
have to cope with the combination of virtually every option possible.

This complexity can (and should) be tackled at the TargetMachine
level, generated from ARM.td, with the options that we do recognise
and can make a difference.

2. Legacy in how to describe a target for decades.

Target triples and compiler options have been abused for decades to
tackle the complexity above. But since they were done one at a time,
no one thought it was a good idea, or even remotely relevant to try
and tackle the all combinations in a non-exponential way.

If we use a target config file, or a target database, and construct
the TargetMachine (& al) from that, there is no need at all for
parsing strings. But for all other legacy ways to describe a targets,
namely triples and compiler options, we do.

Things like Krait as a different CPU+extensions is one example, but
others like xscale, or iwmmxt, which can be arch name, cpu name or
extension name. The only "complex" parsing is to reduce the arch name
into sub-arch name+ISA (armv7a -> arm + v7a), which also has to deal
with big-endian, etc. Everything else needs to be parser exactly as
defined by the legacy options.

But all this parsing should be done *once*, then filling TargetMachine
or TargetTuple, then using that. Today it doesn't because we're in the
process of making it.

The status is...

Before: every function would parse the same strings themselves via
StringSwitch and complex rules.

During: All complex rules are moved into one place, but all users
still call them all the time. This makes it look stupid, but it's not
by design.

After: All users stop calling the parsing routines and call the
Tuple/Machine classes to infer about the target. Parsing is only done
once at startup.

The second step is not worse than "before", and it helps show where
the inefficiencies were. Once we know all the users of the triple
parsing, we can reduce all to enums and switches, and make it almost
zero-cost.

But for that, we need the TargetTuple or some other mechanism (that is
*not* the Triple) to hold unambiguous information about the target.


> And a followup question: What can't be serialized at the function level
in
> the IR to make certain things clear that aren't global? We already do
this
> for a lot of command line options.

These options will change the triple in the IR. Examples:

$ clang -target arm-linux-gnueabi -emit-llvm -S foo.c -o -
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv4t--linux-gnueabi"

$ clang -target arm-linux-gnueabihf -emit-llvm -S foo.c -o -
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6k--linux-gnueabihf"

$ clang -target arm-linux-gnueabi -mcpu=cortex-a8 -emit-llvm -S foo.c -o -
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7--linux-gnueabi"

$ clang -target armv7-linux-gnueabi -mcpu=arm7tdmi -emit-llvm -S foo.c -o -
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv4t--linux-gnueabi"

This has the benefit of reducing the number of flags we need in IR,
but also has the problem that the mapping between front-end and
back-end target description is not 1-to-1, and the transformation from
options to triple to target machine is not always injective.

Things work today because we test them and "fix" the known issues by
adding more string parsing or more enum values to cope with the
legacy. There is no design so far, and that's what we're trying to
change.


> And one more: What global options do we need to consider here?

I'm not sure what you mean, here.



> Targets like Mips and ARM were
> unfortunately designed to change things on the fly during assembly and need
> to collate or at least change defaults as we're processing code.

Yes. That's the unfortunate example of the .fpu / .arch_extension /
.arch / .cpu tags in asm code.


> but it seems to have stabilized at an ARM specific set of
> code with no defined interface. I can't even figure out how I'd use
it in
> lib/Basic right now for any target other than ARM.

This is a fair assessment, and a result that only ARM folks have
worked on it. But to be honest, not many people outside of the ARM
community were interested in it, so I took the decision to let it run
its course, and tighten the interface later. That's why Chandler's
changes to the parser were so intrusive, because he solidified our
current state, that was far from any design we would have wanted. Now,
it's going to be harder to refactor into a proper interface, but not
impossible.

But the fact that this is ARM only makes it really hard for us to
identify what is common and what is not. When I proposed the parser
earlier this year, I expected others to move their targets to it. I
would have done myself if this wasn't such a target-specific sensitive
thing. I don't want to assume I know about other architectures'
problems more than their own engineers, and I don't like when people
assume that they know enough about ours to completely refactor our
code.


> This isn't a condemnation
> of TargetParser, but I think it's something that needs to be thought
through
> a bit more.

Our plan was simple:

1. Move all parsing to a single location, share between front and back end.
2. See what's common between targets, keep that into a lightweight
dynamic class structure, where {ARCH}TargetParser derives from
TargetParser.
3. Makes TargetParser a part of TargetTuple/Machine/Whatever using
LLVM's form of RTTI to pick the right one depending on the arch.
4. Keep all the internal complexity in a target specific
table-generated file, which has all the strings to be parsed.
5. Move all the users of the parser to enquire on the Tuple, thus
using enum/switch instead of string parsing

We got as far as finish the first task. Task 2 was (understandably)
ignored by everyone else, so invalidating task 3 in the process. Task
4 would be our next step.

Task 5 need some form of unambiguous representation, which Daniel was
tackling and I let him to it. But finishing our 4th task would still
mean that we're parsing a lot of strings unnecessarily, and we have
only achieved one goal: to reduce code duplication.

>From the parser's point of view, it makes no difference which class
holds the target representation, as long as I can change *all* parsing
into enum-switch lookups. So, while I sympathised with Daniel's
dilemma, I don't have such a strong position on either merits.


> Right now I see TargetTuple as trying to take over all of the various
> arguments to TargetMachine and encapsulate them into a single thing. I also
> don't see this is bad, but I also don't see it taking all of them
right now
> and I'm not sure how it solves some of the existing problems with data
> sharing that we've got which is where the push back you're both
getting is
> coming from here.

>From my understanding, there are two separate worlds: the precise and
unambiguous world of target descriptions, and the chaotic world of
command line options.

Command line options are often ambiguous, they interact with each
other and they can change according to the host-target relationship.
Parsing all this knowledge and disambiguating it is not a simple task.
It requires a lot of code, a lot of parsing, a lot of switches and
mappings between enums. But once this is done, a low-level unique
representation can be achieved.

On the other hand, the class that is tasked to hold all the
information of all targets in an orderly and unique fashion has to be
precise and correct above all else. There's no space for doubt or
guess work in such a class, or the compiler will start taking random
decisions and it'd be a nightmare to understand it.

So it is clear to me that these two concepts should never cross wires.
In the same way that the Tuple shouldn't be parsing target-specific
stuff on its own, I don't think that a precise machine representation
should be cross-referencing user options to define its properties.

I see a clear succession of ownership:

TargetMachine(
  TargetTuple(
    TargetParser( Triple, arch options ),
    host info,
    target defaults,
    other options ),
  other target machine parameters ... )

But that's just my uninformed opinion. I'll let you and Daniel to
discuss the merits of each other.

cheers,
--renato