Short version: If the integrated assembler accepted assembly strings
as input, more targets could take advantage of integrated assembly.
The longer version:
For a given assembly statement, my out-of-tree target has complex
instruction selection logic -- more so than the in-tree targets. This
target uses variable length instructions and a laborious hierarchy of
tblgen AsmOperands to do the job. Assembly and disassembly with
llvm-mc and llvm-objdump work fine.
As a simplification, the compiler deals almost exclusively in pseudo
instructions. By x86 analogy, using pseudos to unfold a TEST32rm into
MOV32rm + TEST32rr means I can skip the complex operand fitting effort
needed to pick specific machine instructions. There are many such
examples where handling real instructions would become a gross
overload.
One drawback of this approach is that the integrated assembler
receives only unexpanded pseudos as input, which fails. So, my target
must generate .s files and assemble as a separate step.
If a target could pass an assembly string to the integrated assembler,
the pseudo problem goes away. The string passed to the integrated
assembler could come wrapped in a pseudo MCInst or whatever. Support
for asm string parsing already exists in every target, so this doesn't
seem like much of a stretch.
As a simplification, the compiler deals almost exclusively in pseudo
instructions. By x86 analogy, using pseudos to unfold a TEST32rm into
MOV32rm + TEST32rr means I can skip the complex operand fitting effort
needed to pick specific machine instructions. There are many such
examples where handling real instructions would become a gross
overload.
One drawback of this approach is that the integrated assembler
receives only unexpanded pseudos as input, which fails. So, my target
must generate .s files and assemble as a separate step.
It sounds like you're doing the expansion directly in the InstPrinter.
Most targets using fused pseudo-instructions like this do it either in
a separate pass (e.g. AArch64ExpandPseudoInsts.cpp) or during the
lowering from MachineInstr to MCInst(s).
Skipping this for a stringly typed "yeah, whatever" interface sounds
really hacky to me. Though you might be able to get something working
by following what inline-asm calls do (I've no idea where they're
lowered off the top of my head, but then I've no idea what your code
is doing in the specifics either so it probably cancels out).
You can use various forms of aliases and pseudo instructions for pure
assembler use too. Consider the simplified immediate load syntax of ARM,
which handles the constant pool by itself. See test/MC/ARM/ltorg.s.
It sounds like you're doing the expansion directly in the InstPrinter.
Most targets using fused pseudo-instructions like this do it either in
a separate pass (e.g. AArch64ExpandPseudoInsts.cpp) or during the
lowering from MachineInstr to MCInst(s).
Another option that occurred is enhancing the PseudoInstExpansion
handling to cover multiple instructions. Currently it can only produce
1 output, but I don't think there's any fundamental reason it the
output couldn't be a list of instructions instead.
I probably used the term 'expansion' incorrectly. Pseudos go 1:1 into
.s files, then MCTargetAsmParser does its job. This class nicely
consolidates tblgen's auto-generated operand fitting logic, which for
me is quite a blob of code. Should use of the integrated assembler
require targets to pick all machine instructions some other way? If
the answer should be no, then handling pseudos via their AsmString
feels like a tidy answer.
Please stop talking abouot "AsmString", that really makes no sense. It
is really hard to help you if you go back to an approach that is
difficult to understand and from what we can understand, completely
wrong. It also doesn't help that the questions so far are pretty much
without meat. E.g. no example of what your "high-level" assembler
mnemonic looks like and how the resulting instructions look like.
To try to rephrase, these are the transformations to and from MC in LLVM:
assembly → MCInsts
MachineInstrs → MCInsts
MCInsts → object file
MCInsts → assembly
It sounds you’re having trouble translating from MachineInstrs to MCInsts, but printing assembly strings from MachineInstrs is easy for some reason.
I don’t really see why we’d want to go back to the old way of printing assembly strings from the compiler, and then reparsing them, even if it were in-process. We did a lot of work to avoid that whole re-parsing step to save on compile time.
I don’t fully understand the challenges your target presents, but I’m not convinced that they can’t be overcome with some good engineering. Printing asm strings and building MCInsts seem like equivalently difficult problems that would leverage the same underlying logic.
OK. AsmString is the tblgen variable holding the string
representation of the instruction. This doesn't exist in an MCInst
and I conflated the two. Sorry about that.
We can use X86 for a meaty example that is close to my target. In
X86InstrInfo::optimizeCompareInstr() we see code like this:
case X86::SUB64ri32: NewOpcode = X86::CMP64ri32; break;
case X86::SUB64ri8: NewOpcode = X86::CMP64ri8; break;
case X86::SUB32ri: NewOpcode = X86::CMP32ri; break;
case X86::SUB32ri8: NewOpcode = X86::CMP32ri8; break;
case X86::SUB16ri: NewOpcode = X86::CMP16ri; break;
case X86::SUB16ri8: NewOpcode = X86::CMP16ri8; break;
case X86::SUB8ri: NewOpcode = X86::CMP8ri; break;
Here, the compiler must distinguish SUB "ri" from the more compact,
but logically redundant SUB "ri8". That, multiplied by the number of
operand widths and that again multiplied by the number of compare-like
instructions.
My target has *many* more choices than just "ri" and "ri8" and checks
like this switch statement would explode. At the time of
optimizeCompareInstr(), nitpicking all the various ways to encode an
immediate value for an instruction would be very painful. It's enough
to know an immediate form exists as represented by a pseudo. I get to
use something like this:
case FOO::SUB64ri: NewOpcode = FOO::CMP64ri; break;
case FOO::SUB32ri: NewOpcode = FOO::CMP32ri; break;
case FOO::SUB16ri: NewOpcode = FOO::CMP16ri; break;
case FOO::SUB8ri: NewOpcode = FOO::CMP8ri; break;
Into the assembly output file, the pseudo emits something like: "cmpl
$0x80,%eax" with no clue of encoding.
Given the .s file, only the assembly parser worries if $0x80 is best
represented as an unsigned imm8, a signed imm16, imm32, power-of-2,
shifted nibble, special choices for given register destinations, and
so on.
Hopefully this is this more clear? I will back off from suggesting
how this use of pseudos could be accommodated with the integrated
assembler.
Thanks, that's probably another way to put it. The "for some reason"
is because the task of physical instruction selection is especially
complex in my target. The tblgen'd code in MCTargetAsmParser was a
significant investment and the only way I have to pick an instruction
with a real encoding.
We can use X86 for a meaty example that is close to my target. In
X86InstrInfo::optimizeCompareInstr() we see code like this:
case X86::SUB64ri32: NewOpcode = X86::CMP64ri32; break;
case X86::SUB64ri8: NewOpcode = X86::CMP64ri8; break;
case X86::SUB32ri: NewOpcode = X86::CMP32ri; break;
case X86::SUB32ri8: NewOpcode = X86::CMP32ri8; break;
case X86::SUB16ri: NewOpcode = X86::CMP16ri; break;
case X86::SUB16ri8: NewOpcode = X86::CMP16ri8; break;
case X86::SUB8ri: NewOpcode = X86::CMP8ri; break;
Note that as the name suggests, this is a peep hole optimisation for the
compiler, not for the assembler. So again, what are you worried about?
Parsing assembler or generating code?
There are a lot of optimisations for making it easier to express peep
hole optimisations in tblgen, but depending on the backend working on
such infrastructure is just not worth the time.
My target has *many* more choices than just "ri" and "ri8" and checks
like this switch statement would explode. At the time of
optimizeCompareInstr(), nitpicking all the various ways to encode an
immediate value for an instruction would be very painful. It's enough
to know an immediate form exists as represented by a pseudo. I get to
use something like this:
So use tblgen to create appropiate pattern tables.
Into the assembly output file, the pseudo emits something like: "cmpl
$0x80,%eax" with no clue of encoding.
For code generation it is often quite important to know the encoding as
different immediates have different costs associated with them. For many
of the RISC backends, it changes the number of instructions. That has
direct impact on other optimisations. As I mentioned, i.e. ARM has a
pseudo syntax for loading constants via "constant islands". PPC has
pseudo instructions for creating a 64bit immediate in a register via
sequence of shifted immediate and ors. I still don't know how your
target differs to offer more specific hints.