Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DNM] [wip] x86: use liveness info in register allocation #109

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ add_library("sd-base"
x86_code_gen/cg_x86_basic_block.c
x86_code_gen/cg_x86_single_function.c
x86_code_gen/cg_x86_single_tac.c
x86_code_gen/allocate_registers_x86.c
x86_code_gen/compile_ir/compile_tac.h
x86_code_gen/compile_ir/compile_tac_binary_op.c
x86_code_gen/compile_ir/compile_tac_call.c
Expand Down
3 changes: 1 addition & 2 deletions compiler/main/avr_code_gen/cg_avr.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ bool compile_and_write_avr(struct AST* ast, struct Ctx* ctx) {

struct IBuffer* ibu = ibu_ctor();

//struct RAT* rat = rat_ctor(ctx_tables(ctx));
struct RAT* rat = rat_ctor(RAT_ARCH_AVR);
struct RAT* rat = rat_ctor(RAT_ARCH_AVR, 10);

struct TAC* t = makeTACSetupSP();
emit_asm_avr_single_tac(rat, t, ctx, ibu);
Expand Down
7 changes: 6 additions & 1 deletion compiler/main/avr_code_gen/cg_avr_basic_block.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <stdio.h>

#include "cli/flags/flags.h"
#include "liveness/liveness.h"
#include "tables/symtable/symtable.h"

#include "tac/tac.h"
Expand All @@ -28,8 +29,10 @@ void emit_asm_avr_basic_block(struct BasicBlock* block, struct Ctx* ctx, struct

block->visited_emit_asm = true;

struct Liveness* live = liveness_calc_tacbuffer(block->buffer);

//create register allocation table for the basic block.
struct RAT* rat = rat_ctor(RAT_ARCH_AVR);
struct RAT* rat = rat_ctor(RAT_ARCH_AVR, liveness_ntemps(live));

//simplest naive approach (first iteration):
//simply get a new register for each temporary
Expand All @@ -49,6 +52,8 @@ void emit_asm_avr_basic_block(struct BasicBlock* block, struct Ctx* ctx, struct

rat_dtor(rat);

liveness_dtor(live);

//false/default branch gets emitted first,
//because there is no label for it in a lot of cases
//this way we can avoid an extra jump that's really
Expand Down
4 changes: 4 additions & 0 deletions compiler/main/liveness/liveness.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ void liveness_print(struct Liveness* l) {
map_print(l->map_out, l->nstmts, l->ntemps, "out[n]");
}

size_t liveness_ntemps(struct Liveness* l) {
return l->ntemps;
}

static bool liveness_get(struct Liveness* l, size_t stmt_index, size_t tmp_index) {
return l->map_out[stmt_index][tmp_index] || l->map_in[stmt_index][tmp_index];
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/main/liveness/liveness.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ struct Liveness* liveness_calc_tacbuffer(struct TACBuffer* buf);
// print tables to stdout for debug
void liveness_print(struct Liveness* l);

// @returns number of temporaries that
// the liveness info keeps track of
size_t liveness_ntemps(struct Liveness* l);

// This can be used in register allocation.
// If a temporary does not have overlapping liveness with
// the other temporaries already occupying a register,
Expand Down
93 changes: 93 additions & 0 deletions compiler/main/x86_code_gen/allocate_registers_x86.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "liveness/liveness.h"
#include "tables/symtable/symtable.h"

#include "tac/tac.h"
#include "tac/tacbuffer.h"
#include "basic_block/basicblock.h"
#include "rat/rat.h"

#include "allocate_registers_x86.h"

static void allocate_registers_single_tac(struct TAC* t, struct RAT* rat, struct ST* st, struct Liveness* live);

void allocate_registers_basicblocks(struct BasicBlock** graph, size_t nblocks, struct RAT* rat, struct ST* st, struct Liveness* live) {

for (size_t i = 0; i < nblocks; i++) {
allocate_registers(graph[i]->buffer, rat, st, live);
}
}

void allocate_registers(struct TACBuffer* b, struct RAT* rat, struct ST* st, struct Liveness* live) {

assert(live != NULL);

for (size_t i = 0; i < tacbuffer_count(b); i++) {
struct TAC* t = tacbuffer_get(b, i);
allocate_registers_single_tac(t, rat, st, live);
}
}

static uint32_t rat_ensure_register_x86(struct RAT* rat, uint32_t tmp, struct Liveness* live) {
// on x86 we do not have to use register pairs.
// shoo shoo, the register will be large enough :)

if (rat_has_register(rat, tmp)) {
return rat_get_register(rat, tmp);
}

// check for overlap with liveness
const size_t ntemps = liveness_ntemps(live);

//TODO: overlap also with temporaries which are
//never used anywhere (and therefore have no liveness).
//These need a register too but they do not overlap.
//
//So that's a bug.

bool* live_overlap = calloc(ntemps, sizeof(bool));
for (size_t t = 0; t < ntemps; t++) {
if (t == tmp) {
continue;
}
live_overlap[t] = liveness_overlaps(live, tmp, t);
}

const int32_t reg = rat_find_reg_no_overlap(rat, live_overlap, ntemps);

assert(reg >= 0);

free(live_overlap);

rat_occupy(rat, reg, tmp, false);

return reg;
}

static void allocate_registers_single_tac(struct TAC* t, struct RAT* rat, struct ST* st, struct Liveness* live) {

struct LVST* lvst = st->lvst;
struct SST* sst = st->sst;

switch (t->kind) {

case TAC_CONST_VALUE:
case TAC_BINARY_OP_IMMEDIATE:
case TAC_LOAD_LOCAL_ADDR:
case TAC_COPY:
case TAC_BINARY_OP:
case TAC_UNARY_OP:
case TAC_LOAD_CONST_ADDR:
case TAC_LOAD:
case TAC_LOAD_LOCAL:
case TAC_CALL:
rat_ensure_register_x86(rat, t->dest, live);
break;

default: break;
}
}
14 changes: 14 additions & 0 deletions compiler/main/x86_code_gen/allocate_registers_x86.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <stdbool.h>

#include "util/ctx.h"
#include "rat/rat.h"
#include "basic_block/basicblock.h"

struct IBuffer;
struct Liveness;

void allocate_registers_basicblocks(struct BasicBlock** graph, size_t nblocks, struct RAT* rat, struct ST* st, struct Liveness* live);

void allocate_registers(struct TACBuffer* b, struct RAT* rat, struct ST* st, struct Liveness* live);
74 changes: 0 additions & 74 deletions compiler/main/x86_code_gen/cg_x86_basic_block.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,77 +49,3 @@ void emit_asm_x86_basic_block(struct BasicBlock* block, struct Ctx* ctx, struct
emit_asm_x86_basic_block(block->branch_2, ctx, ibu, rat);
emit_asm_x86_basic_block(block->branch_1, ctx, ibu, rat);
}

void allocate_registers(struct TACBuffer* b, struct RAT* rat, struct ST* st) {

for (size_t i = 0; i < tacbuffer_count(b); i++) {
struct TAC* t = tacbuffer_get(b, i);
allocate_registers_single_tac(t, rat, st);
}
}

static void allocate_registers_single_tac(struct TAC* t, struct RAT* rat, struct ST* st) {

struct LVST* lvst = st->lvst;
struct SST* sst = st->sst;

// on x86 we do not have to use register pairs.
// shoo shoo, the register will be large enough :)
const bool iswide = false;

switch (t->kind) {

case TAC_CONST_VALUE:
rat_ensure_register(rat, t->dest, true, iswide);
break;

case TAC_BINARY_OP_IMMEDIATE:
rat_ensure_register(rat, t->dest, true, iswide);
break;

case TAC_LOAD_LOCAL_ADDR:
//address always needs 2 registers
rat_ensure_register(rat, t->dest, true, true);
break;

case TAC_CALL: {

if (sst_size(sst) > t->arg1) {
//in TAC tests the SST might not be fully initialized
struct Type* return_type = sst_at(sst, t->arg1)->return_type;
}

rat_ensure_register(rat, t->dest, false, iswide);
} break;

case TAC_COPY:
rat_ensure_register(rat, t->dest, false, iswide);
break;

case TAC_BINARY_OP:
rat_ensure_register(rat, t->dest, false, iswide);
break;

case TAC_UNARY_OP:
rat_ensure_register(rat, t->dest, false, iswide);
break;

case TAC_LOAD_LOCAL: {
//look at the LVST to see the width of the local var
struct Type* local_type = lvst_at(lvst, t->arg1)->type;
rat_ensure_register(rat, t->dest, false, iswide);
} break;

case TAC_LOAD_CONST_ADDR:
rat_ensure_register(rat, t->dest, false, true);
break;

case TAC_LOAD:
//sadly we do not know what is all going to be added/subtracted
//from what we load there, could be a pointer, so it must be wide
rat_ensure_register(rat, t->dest, false, true);
break;

default: break;
}
}
2 changes: 0 additions & 2 deletions compiler/main/x86_code_gen/cg_x86_basic_block.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@
struct IBuffer;

void emit_asm_x86_basic_block(struct BasicBlock* block, struct Ctx* ctx, struct IBuffer* ibu, struct RAT* rat);

void allocate_registers(struct TACBuffer* b, struct RAT* rat, struct ST* st);
11 changes: 7 additions & 4 deletions compiler/main/x86_code_gen/cg_x86_single_function.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

#include "tables/symtable/symtable.h"
#include "cg_x86_single_function.h"
#include "allocate_registers_x86.h"

#include "cli/flags/flags.h"
#include "liveness/liveness.h"

void compile_and_write_x86_single_function(struct Method* m, struct Ctx* ctx, struct IBuffer* ibu) {

Expand Down Expand Up @@ -46,10 +48,11 @@ void compile_and_write_x86_single_function(struct Method* m, struct Ctx* ctx, st
struct BasicBlock** graph = basicblock_create_graph(buffer, m->decl->name, &nblocks, ctx);
struct BasicBlock* root = graph[0];

struct RAT* rat = rat_ctor(RAT_ARCH_X86);
for (int i = 0; i < nblocks; i++) {
allocate_registers(graph[i]->buffer, rat, st);
}
struct Liveness* live = liveness_calc(graph, nblocks);

struct RAT* rat = rat_ctor(RAT_ARCH_X86, liveness_ntemps(live));

allocate_registers_basicblocks(graph, nblocks, rat, st, live);

emit_asm_x86_basic_block(root, ctx, ibu, rat);

Expand Down
3 changes: 2 additions & 1 deletion compiler/test/testcases.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "parser/test/commandline/test.h"
#include "token/test/test.h"
#include "tac/test/test.h"
#include "rat/test.h"
#include "rat/test/test.h"
#include "ast/test/test_str_ast.h"

void (*tests_ast[])() = {
Expand Down Expand Up @@ -55,6 +55,7 @@ void (*tests_rat[])() = {
test_rat_occupant_pair,
test_rat_alloc_n_regs,
test_rat_is_wide,
test_rat_multiple_temps_in_register,
NULL,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ static void test_param(uint64_t fixed_value, bool debug) {
tacbuffer_append(b, makeTACSetupStackframe(stackframe_size_bytes));
tacbuffer_append(b, makeTACConst(0, fixed_value));
tacbuffer_append(b, makeTACConst(1, 0));

tacbuffer_append(b, makeTACCopy(1, 1));

tacbuffer_append(b, makeTACParam(0, false));

struct sd_uc_engine* system = sd_uc_engine_from_tacbuffer_v3(b, debug, true, stackframe_size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ static void test_fixed_value(uint32_t value, bool debug) {
tacbuffer_append(b, makeTACConst(0, value + 1));
tacbuffer_append(b, makeTACConst(1, value));
tacbuffer_append(b, makeTACConst(2, value - 1));

tacbuffer_append(b, makeTACCopy(2, 0));
tacbuffer_append(b, makeTACCopy(0, 2));
tacbuffer_append(b, makeTACReturn(1));

struct sd_uc_engine* system = sd_uc_engine_from_tacbuffer_v3(b, debug, true, stackframe_size);
Expand Down
14 changes: 10 additions & 4 deletions compiler/test/x86_code_gen/test_x86_code_gen_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "x86_code_gen/cg_x86.h"
#include "x86_code_gen/cg_x86_single_function.h"
#include "x86_code_gen/cg_x86_basic_block.h"
#include "x86_code_gen/allocate_registers_x86.h"

#include "tac/tacbuffer.h"
#include "basic_block/basicblock.h"
Expand All @@ -21,6 +22,7 @@

#include "cli/flags/flags.h"
#include "fake_lvst.h"
#include "liveness/liveness.h"

struct sd_uc_engine {
// wrapper struct
Expand Down Expand Up @@ -276,13 +278,17 @@ static void gen_from_tacbuffer(struct TACBuffer* buffer, FILE* fout, struct Ctx*
exit(EXIT_FAILURE);
}

struct Liveness* live = liveness_calc(graph, nblocks);

if (flags_debug(ctx_flags(ctx))) {
liveness_print(live);
}

struct IBuffer* ibu = ibu_ctor();

struct RAT* rat = rat_ctor(RAT_ARCH_X86);
struct RAT* rat = rat_ctor(RAT_ARCH_X86, liveness_ntemps(live));

for (int i = 0; i < nblocks; i++) {
allocate_registers(graph[i]->buffer, rat, ctx_tables(ctx));
}
allocate_registers_basicblocks(graph, nblocks, rat, ctx_tables(ctx), live);

emit_asm_x86_basic_block(root, ctx, ibu, rat);

Expand Down
Loading