298 lines
11 KiB
C
298 lines
11 KiB
C
/*
|
|
* Copyright (c) 2019 Lima Project
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sub license,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the
|
|
* next paragraph) shall be included in all copies or substantial portions
|
|
* of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "ppir.h"
|
|
|
|
/* Propagates liveness from a liveness set to another by performing the
|
|
* union between sets. */
|
|
static void
|
|
ppir_liveness_propagate(ppir_compiler *comp,
|
|
BITSET_WORD *dest_set, BITSET_WORD *src_set,
|
|
uint8_t *dest_mask, uint8_t *src_mask)
|
|
{
|
|
for (int i = 0; i < BITSET_WORDS(comp->reg_num); i++)
|
|
dest_set[i] |= src_set[i];
|
|
|
|
for (int i = 0; i < reg_mask_size(comp->reg_num); i++)
|
|
dest_mask[i] |= src_mask[i];
|
|
}
|
|
|
|
/* Check whether two liveness sets are equal. */
|
|
static bool
|
|
ppir_liveness_set_equal(ppir_compiler *comp,
|
|
BITSET_WORD *set1, BITSET_WORD *set2,
|
|
uint8_t *mask1, uint8_t *mask2)
|
|
{
|
|
for (int i = 0; i < BITSET_WORDS(comp->reg_num); i++)
|
|
if (set1[i] != set2[i])
|
|
return false;
|
|
|
|
for (int i = 0; i < reg_mask_size(comp->reg_num); i++)
|
|
if (mask1[i] != mask2[i])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Update the liveness information of the instruction by adding its srcs
|
|
* as live registers to the live_in set. */
|
|
static void
|
|
ppir_liveness_instr_srcs(ppir_compiler *comp, ppir_instr *instr)
|
|
{
|
|
for (int i = PPIR_INSTR_SLOT_NUM-1; i >= 0; i--) {
|
|
ppir_node *node = instr->slots[i];
|
|
if (!node)
|
|
continue;
|
|
|
|
switch(node->op) {
|
|
case ppir_op_const:
|
|
case ppir_op_undef:
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < ppir_node_get_src_num(node); i++) {
|
|
ppir_src *src = ppir_node_get_src(node, i);
|
|
if (!src || src->type == ppir_target_pipeline)
|
|
continue;
|
|
|
|
ppir_reg *reg = ppir_src_get_reg(src);
|
|
if (!reg || reg->undef)
|
|
continue;
|
|
|
|
unsigned int index = reg->regalloc_index;
|
|
|
|
/* if some other op on this same instruction is writing,
|
|
* we just need to reserve a register for this particular
|
|
* instruction. */
|
|
if (src->node && src->node->instr == instr) {
|
|
BITSET_SET(instr->live_internal, index);
|
|
continue;
|
|
}
|
|
|
|
bool live = BITSET_TEST(instr->live_set, index);
|
|
if (src->type == ppir_target_ssa) {
|
|
/* reg is read, needs to be live before instr */
|
|
if (live)
|
|
continue;
|
|
|
|
BITSET_SET(instr->live_set, index);
|
|
}
|
|
else {
|
|
unsigned int mask = ppir_src_get_mask(src);
|
|
uint8_t live_mask = get_reg_mask(instr->live_mask, index);
|
|
|
|
/* read reg is type register, need to check if this sets
|
|
* any additional bits in the current mask */
|
|
if (live && (live_mask == (live_mask | mask)))
|
|
continue;
|
|
|
|
/* some new components */
|
|
set_reg_mask(instr->live_mask, index, (live_mask | mask));
|
|
BITSET_SET(instr->live_set, index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Update the liveness information of the instruction by removing its
|
|
* dests from the live_in set. */
|
|
static void
|
|
ppir_liveness_instr_dest(ppir_compiler *comp, ppir_instr *instr, ppir_instr *last)
|
|
{
|
|
for (int i = PPIR_INSTR_SLOT_NUM-1; i >= 0; i--) {
|
|
ppir_node *node = instr->slots[i];
|
|
if (!node)
|
|
continue;
|
|
|
|
switch(node->op) {
|
|
case ppir_op_const:
|
|
case ppir_op_undef:
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ppir_dest *dest = ppir_node_get_dest(node);
|
|
if (!dest || dest->type == ppir_target_pipeline)
|
|
continue;
|
|
ppir_reg *reg = ppir_dest_get_reg(dest);
|
|
if (!reg || reg->undef)
|
|
continue;
|
|
|
|
unsigned int index = reg->regalloc_index;
|
|
bool live = BITSET_TEST(instr->live_set, index);
|
|
|
|
/* If it's an out reg, it's alive till the end of the block, so add it
|
|
* to live_set of the last instruction */
|
|
if (!live && reg->out_reg && (instr != last)) {
|
|
BITSET_SET(last->live_set, index);
|
|
BITSET_CLEAR(instr->live_set, index);
|
|
continue;
|
|
}
|
|
|
|
/* If a register is written but wasn't read in a later instruction, it is
|
|
* either an output register in last instruction, dead code or a bug.
|
|
* For now, assign an interference to it to ensure it doesn't get assigned
|
|
* a live register and overwrites it. */
|
|
if (!live) {
|
|
BITSET_SET(instr->live_internal, index);
|
|
continue;
|
|
}
|
|
|
|
if (dest->type == ppir_target_ssa) {
|
|
/* reg is written and ssa, is not live before instr */
|
|
BITSET_CLEAR(instr->live_set, index);
|
|
}
|
|
else {
|
|
unsigned int mask = dest->write_mask;
|
|
uint8_t live_mask = get_reg_mask(instr->live_mask, index);
|
|
/* written reg is type register, need to check if this clears
|
|
* the remaining mask to remove it from the live set */
|
|
if (live_mask == (live_mask & ~mask))
|
|
continue;
|
|
|
|
set_reg_mask(instr->live_mask, index, (live_mask & ~mask));
|
|
/* unset reg if all remaining bits were cleared */
|
|
if ((live_mask & ~mask) == 0) {
|
|
BITSET_CLEAR(instr->live_set, index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Main loop, iterate blocks/instructions/ops backwards, propagate
|
|
* livenss and update liveness of each instruction. */
|
|
static bool
|
|
ppir_liveness_compute_live_sets(ppir_compiler *comp)
|
|
{
|
|
uint8_t temp_live_mask[reg_mask_size(comp->reg_num)];
|
|
BITSET_DECLARE(temp_live_set, comp->reg_num);
|
|
bool cont = false;
|
|
list_for_each_entry_rev(ppir_block, block, &comp->block_list, list) {
|
|
if (list_is_empty(&block->instr_list))
|
|
continue;
|
|
|
|
ppir_instr *last = list_last_entry(&block->instr_list, ppir_instr, list);
|
|
assert(last);
|
|
|
|
list_for_each_entry_rev(ppir_instr, instr, &block->instr_list, list) {
|
|
/* initial copy to check for changes */
|
|
memset(temp_live_mask, 0, sizeof(temp_live_mask));
|
|
memset(temp_live_set, 0, sizeof(temp_live_set));
|
|
|
|
ppir_liveness_propagate(comp,
|
|
temp_live_set, instr->live_set,
|
|
temp_live_mask, instr->live_mask);
|
|
|
|
/* inherit (or-) live variables from next instr or block */
|
|
if (instr == last) {
|
|
ppir_instr *next_instr;
|
|
/* inherit liveness from the first instruction in the next blocks */
|
|
for (int i = 0; i < 2; i++) {
|
|
ppir_block *succ = block->successors[i];
|
|
if (!succ)
|
|
continue;
|
|
|
|
/* if the block is empty, go for the next-next until a non-empty
|
|
* one is found */
|
|
while (list_is_empty(&succ->instr_list)) {
|
|
assert(succ->successors[0] && !succ->successors[1]);
|
|
succ = succ->successors[0];
|
|
}
|
|
|
|
next_instr = list_first_entry(&succ->instr_list, ppir_instr, list);
|
|
assert(next_instr);
|
|
|
|
ppir_liveness_propagate(comp,
|
|
instr->live_set, next_instr->live_set,
|
|
instr->live_mask, next_instr->live_mask);
|
|
}
|
|
}
|
|
else {
|
|
ppir_instr *next_instr = list_entry(instr->list.next, ppir_instr, list);
|
|
ppir_liveness_propagate(comp,
|
|
instr->live_set, next_instr->live_set,
|
|
instr->live_mask, next_instr->live_mask);
|
|
}
|
|
|
|
ppir_liveness_instr_dest(comp, instr, last);
|
|
ppir_liveness_instr_srcs(comp, instr);
|
|
|
|
cont |= !ppir_liveness_set_equal(comp,
|
|
temp_live_set, instr->live_set,
|
|
temp_live_mask, instr->live_mask);
|
|
}
|
|
}
|
|
|
|
return cont;
|
|
}
|
|
|
|
/*
|
|
* Liveness analysis is based on https://en.wikipedia.org/wiki/Live_variable_analysis
|
|
* This implementation calculates liveness for each instruction.
|
|
* The liveness set in this implementation is defined as the set of
|
|
* registers live before the instruction executes.
|
|
* Blocks/instructions/ops are iterated backwards so register reads are
|
|
* propagated up to the instruction that writes it.
|
|
*
|
|
* 1) Before computing liveness for an instruction, propagate liveness
|
|
* from the next instruction. If it is the last instruction in a
|
|
* block, propagate liveness from all possible next instructions in
|
|
* the successor blocks.
|
|
* 2) Calculate the live set for the instruction. The initial live set
|
|
* is a propagated set of the live set from the next instructions.
|
|
* - Registers which aren't touched by this instruction are kept
|
|
* intact.
|
|
* - If a register is written by this instruction, it no longer needs
|
|
* to be live before the instruction, so it is removed from the live
|
|
* set of that instruction.
|
|
* - If a register is read by this instruction, it needs to be live
|
|
* before its execution, so add it to its live set.
|
|
* - Non-ssa registers are a special case. For this, the algorithm
|
|
* keeps and updates the mask of live components following the same
|
|
* logic as above. The register is only removed from the live set of
|
|
* the instruction when no live components are left.
|
|
* - If a non-ssa register is written and read in the same
|
|
* instruction, it stays in the live set.
|
|
* - Another special case is when a register is only written and read
|
|
* within a single instruciton. In this case a register needs to be
|
|
* reserved but not propagated. The algorithm adds it to the
|
|
* live_internal set so that the register allocator properly assigns
|
|
* an interference for it.
|
|
* 3) The algorithm must run over the entire program until it converges,
|
|
* i.e. a full run happens without changes. This is because blocks
|
|
* are updated sequentially and updates in a block may need to be
|
|
* propagated to parent blocks that were already calculated in the
|
|
* current run.
|
|
*/
|
|
void
|
|
ppir_liveness_analysis(ppir_compiler *comp)
|
|
{
|
|
while (ppir_liveness_compute_live_sets(comp))
|
|
;
|
|
}
|