A top-level register class is one that has no sub-classes. It is
possible to have multiple top-level register classes in the same
register bank. For example, ARM's FPR bank has both D and Q top-level
register classes.
Number register classes such that all top-level register classes appear
as a contiguous sequence starting from 0. This will be used by the
register allocator when counting used registers per top-level register
class.
Cretonne's encoding recipes need to have a fixed size so we can compute
accurate branch destination addresses. Intel's instruction encoding has
a lot of variance in the number of bytes needed to encode the opcode
which leads to a number of duplicated encoding recipes that only differ
in the opcode size.
Add an Intel-specific TailEnc Python class which represents an
abstraction over a set of recipes that are identical except for the
opcode encoding. The TailEnc can then generate specific encoding recipes
for each opcode format.
The opcode format is a prefix of the recipe name, so for example, the
'rr' TailEnc will generate the 'Op1rr', 'Op2rr', 'Mp2rr' etc recipes.
The TailEnc class provides a __call__ implementation that simply takes
the sequence of opcode bytes as arguments. It then looks up the right
prefix for the opcode bytes.
Cretonne's encoding recipes need to have a fixed size so we can compute
accurate branch destination addresses. Intel's instruction encoding has
a lot of variance in the number of bytes needed to encode the opcode
which leads to a number of duplicated encoding recipes that only differ
in the opcode size.
Add an Intel-specific TailEnc Python class which represents an
abstraction over a set of recipes that are identical except for the
opcode encoding. The TailEnc can then generate specific encoding recipes
for each opcode format.
The opcode format is a prefix of the recipe name, so for example, the
'rr' TailEnc will generate the 'Op1rr', 'Op2rr', 'Mp2rr' etc recipes.
The TailEnc class provides a __call__ implementation that simply takes
the sequence of opcode bytes as arguments. It then looks up the right
prefix for the opcode bytes.
We don't support the full set of Intel addressing modes yet. So far we
have:
- Register indirect, no displacement.
- Register indirect, 8-bit signed displacement.
- Register indirect, 32-bit signed displacement.
The SIB addressing modes will need new Cretonne instruction formats to
represent.
We don't support the full set of Intel addressing modes yet. So far we
have:
- Register indirect, no displacement.
- Register indirect, 8-bit signed displacement.
- Register indirect, 32-bit signed displacement.
The SIB addressing modes will need new Cretonne instruction formats to
represent.
We'll arrange encoding lists such that the first suitable encoding is
the best choice for the legalizer. This is the most intuitive way of
generating the encodings.
After register allocation, we may choose a different encoding, but that
will require looking at the whole list.
We'll arrange encoding lists such that the first suitable encoding is
the best choice for the legalizer. This is the most intuitive way of
generating the encodings.
After register allocation, we may choose a different encoding, but that
will require looking at the whole list.
After finding a register solution, it need to be executed as a sequence
of regmove instructions. This often requires a topological ordering of
the moves so they don't conflict.
When the solution contains cycles, try to grab an available scratch
register to implement the copies. Panic if that fails (later, we'll
implement emergency spilling in this case).
Make sure we handle odd aliasing in the arm32 floating point register
bank. Not everything is a simple cycle in that case, so make sure we
don't assume so.
After finding a register solution, it need to be executed as a sequence
of regmove instructions. This often requires a topological ordering of
the moves so they don't conflict.
When the solution contains cycles, try to grab an available scratch
register to implement the copies. Panic if that fails (later, we'll
implement emergency spilling in this case).
Make sure we handle odd aliasing in the arm32 floating point register
bank. Not everything is a simple cycle in that case, so make sure we
don't assume so.
The register constraint solver has two kinds of variables:
1. Live values that were already in a register, and
2. Values defined by the instruction.
Make a record of the original register holding the first kind of value.
The register constraint solver has two kinds of variables:
1. Live values that were already in a register, and
2. Values defined by the instruction.
Make a record of the original register holding the first kind of value.
It is not necessary to to a second pass over the live values to update
the set of available registers. The color_args() and color_entry_args()
functions can do that in a single pass.
It is not necessary to to a second pass over the live values to update
the set of available registers. The color_args() and color_entry_args()
functions can do that in a single pass.
The live value tracker expects them to be there.
We may eventually delete dead arguments from internal EBBs, but at least
the entry block needs to be able to handle dead function arguments.
The live value tracker expects them to be there.
We may eventually delete dead arguments from internal EBBs, but at least
the entry block needs to be able to handle dead function arguments.
Provide a drop_dead_args() function which deletes them instead.
We still need to assign a register to dead EBB arguments, so they can't
just be ignored.
Provide a drop_dead_args() function which deletes them instead.
We still need to assign a register to dead EBB arguments, so they can't
just be ignored.
The live value tracker goes through the trouble of looking up the live
range for each value it tracks. We can cache a few more interesting
properties from the live range in the LiveValue struct.
The live value tracker goes through the trouble of looking up the live
range for each value it tracks. We can cache a few more interesting
properties from the live range in the LiveValue struct.
Most of the time, register coloring is almost trivial: just pick
available registers for the values defined by the current instruction.
However, some instructions have register operand constraints, and it may
be necessary to move live registers around to satisfy the constraints.
Sometimes the instruction's own operands can interfere with each other
in a way that you can't just pick a register assignment for each output
in order.
This is complicated enough that it is worthwhile to represent as a
constraint satisfaction problem in a separate solver module. The
representation is chosen to be very fast in the common case where the
constraints are trivial to solve.
The current implementation is still incomplete, but as functional as the
code it's replacing. Missing features:
- Handle tied operand constraints.
- Handle ABI constraints on calls and return instructions.
- Execute a constraint solution by emitting regmove instructions.
- Handling register diversions before leaving the EBB.
Most of the time, register coloring is almost trivial: just pick
available registers for the values defined by the current instruction.
However, some instructions have register operand constraints, and it may
be necessary to move live registers around to satisfy the constraints.
Sometimes the instruction's own operands can interfere with each other
in a way that you can't just pick a register assignment for each output
in order.
This is complicated enough that it is worthwhile to represent as a
constraint satisfaction problem in a separate solver module. The
representation is chosen to be very fast in the common case where the
constraints are trivial to solve.
The current implementation is still incomplete, but as functional as the
code it's replacing. Missing features:
- Handle tied operand constraints.
- Handle ABI constraints on calls and return instructions.
- Execute a constraint solution by emitting regmove instructions.
- Handling register diversions before leaving the EBB.
These instructions have a fixed register constraint; the shift amount is
passed in CL.
Add meta language syntax so a fixed register can be specified as
"GPR.rcx".
These instructions have a fixed register constraint; the shift amount is
passed in CL.
Add meta language syntax so a fixed register can be specified as
"GPR.rcx".
Tabulate the Intel opcode representations and implement an OP() function
which computes the encoding bits.
Implement the single-byte opcode with a reg-reg ModR/M byte.
Tabulate the Intel opcode representations and implement an OP() function
which computes the encoding bits.
Implement the single-byte opcode with a reg-reg ModR/M byte.
Most instructions don't have any fixed register constraints. Add boolean
summaries that can be used to check if it is worthwhile to scan the
constraint lists when looking for a fixed register constraint.
Also add a tied_ops summary bool which indicates that the instruction
has tied operand constraints.
Most instructions don't have any fixed register constraints. Add boolean
summaries that can be used to check if it is worthwhile to scan the
constraint lists when looking for a fixed register constraint.
Also add a tied_ops summary bool which indicates that the instruction
has tied operand constraints.
The register constraint for an output operand can be specified as an
integer indicating the input operand number to tie. The tied operands
must use the same register.
Generate operand constraints using ConstraintKind::Tied(n) for both the
tied operands. The n index refers to the opposite array. The input
operand refers to the outs array and vice versa.
The register constraint for an output operand can be specified as an
integer indicating the input operand number to tie. The tied operands
must use the same register.
Generate operand constraints using ConstraintKind::Tied(n) for both the
tied operands. The n index refers to the opposite array. The input
operand refers to the outs array and vice versa.