Skip to content

Commit

Permalink
Refactor IR representation: basic blocks and CFG
Browse files Browse the repository at this point in the history
  • Loading branch information
LesleyLai committed Feb 20, 2025
1 parent ffeabed commit 05b7932
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 166 deletions.
29 changes: 25 additions & 4 deletions include/mcc/ir.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ typedef struct IRProgram {
} IRProgram;

typedef struct IRFunctionDef {
StringView name;
uint32_t instruction_count;

uint32_t param_count;

struct IRInstruction* instructions;
StringView* params;

uint32_t block_count;
struct IRBasicBlock** blocks;
} IRFunctionDef;

// The name of a function is just the name of its fjrst basic block
StringView get_ir_function_name(const IRFunctionDef* function);

typedef enum IRValueType {
IR_VALUE_TYPE_CONSTANT,
IR_VALUE_TYPE_VARIABLE,
Expand All @@ -46,9 +50,26 @@ typedef struct IRValue {
};
} IRValue;

typedef enum IRBasicBlockTailInstructionKind {
IR_BLOCK_TAIL_INVALID,
IR_BLOCK_TAIL_RETURN,
} IRBasicBlockTailInstructionKind;

typedef struct IRBasicBlock {
StringView name;
uint32_t instruction_count;
struct IRInstruction* instructions;

struct {
IRBasicBlockTailInstructionKind kind;
union {
IRValue return_val;
};
} tail;
} IRBasicBlock;

typedef enum IRInstructionType {
IR_INVALID = 0,
IR_RETURN, // return val

// unary
IR_COPY, // dest = src
Expand Down
75 changes: 54 additions & 21 deletions src/ir/ir_generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
case BINARY_OP_SHIFT_LEFT_EQUAL: \
case BINARY_OP_SHIFT_RIGHT_EQUAL

StringView get_ir_function_name(const IRFunctionDef* function)
{
MCC_ASSERT(function->block_count > 0);
return function->blocks[0]->name;
}

/*
* =============================================================================
* Convenient "constructors"
Expand All @@ -35,12 +41,6 @@ static IRValue ir_variable(StringView name)
return (IRValue){.typ = IR_VALUE_TYPE_VARIABLE, .variable = name};
}

static IRInstruction ir_single_operand_instr(IRInstructionType typ,
IRValue operand)
{
return (IRInstruction){.typ = typ, .operand1 = operand};
}

static IRInstruction ir_unary_instr(IRInstructionType typ, IRValue dst,
IRValue src)
{
Expand Down Expand Up @@ -112,6 +112,12 @@ typedef struct IRInstructions {
uint32_t capacity;
} IRInstructions;

typedef struct IRBasicBlocks {
IRBasicBlock** data;
uint32_t length;
uint32_t capacity;
} IRBasicBlocks;

struct ErrorVec {
size_t length;
size_t capacity;
Expand All @@ -129,7 +135,8 @@ typedef struct IRGenTUContext {
// context only for the a single function
typedef struct IRGenProceduralContext {
IRGenTUContext* tu_context;
IRInstructions instructions;
IRInstructions instructions; // instructions for the current block
IRBasicBlocks blocks;
int fresh_variable_counter;
int fresh_label_counter;
} IRGenProceduralContext;
Expand Down Expand Up @@ -497,10 +504,32 @@ static void emit_ir_instructions_from_stmt(const Stmt* stmt,
emit_ir_instructions_from_expr(stmt->expr, context);
} break;
case STMT_RETURN: {
IRBasicBlock* block =
ARENA_ALLOC_OBJECT(context->tu_context->permanent_arena, IRBasicBlock);

IRInstruction* instructions = nullptr;
if (context->instructions.data != nullptr) {
instructions =
ARENA_ALLOC_ARRAY(context->tu_context->permanent_arena, IRInstruction,
context->instructions.length);
memcpy(instructions, context->instructions.data,
context->instructions.length * sizeof(IRInstruction));
}

const IRValue return_value =
emit_ir_instructions_from_expr(stmt->ret.expr, context);

push_instruction(context, ir_single_operand_instr(IR_RETURN, return_value));
*block = (IRBasicBlock){
.name = str("todo"),
.instruction_count = context->instructions.length,
.instructions = instructions,
.tail = {.kind = IR_BLOCK_TAIL_RETURN, .return_val = return_value}};

DYNARRAY_PUSH_BACK(&context->blocks, IRBasicBlock*,
context->tu_context->scratch_arena, block);

// clear current instructions
context->instructions = (IRInstructions){};
} break;
case STMT_COMPOUND: {
for (size_t i = 0; i < stmt->compound.child_count; ++i) {
Expand Down Expand Up @@ -696,15 +725,16 @@ static IRFunctionDef generate_ir_function_def(const FunctionDecl* decl,

// return 0 for main if there is no return statement at the end
// TODO: should only do that for the main function
if (context.instructions.length == 0 ||
context.instructions.data[context.instructions.length - 1].typ !=
IR_RETURN) {
push_instruction(&context,
ir_single_operand_instr(IR_RETURN, ir_constant(0)));
}

// allocate and copy instructions to permanent arena
IRInstruction* instructions = nullptr;
// TODO: reenable this
// if (context.instructions.length == 0 ||
// context.instructions.data[context.instructions.length - 1].typ !=
// IR_RETURN) {
// push_instruction(&context,
// ir_single_operand_instr(IR_RETURN, ir_constant(0)));
// }

// allocate and copy blocks to permanent arena
IRBasicBlock** blocks = nullptr;
if (context.instructions.data != nullptr) {
instructions = ARENA_ALLOC_ARRAY(tu_context->permanent_arena, IRInstruction,

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, ON)

use of undeclared identifier 'instructions'

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, OFF)

use of undeclared identifier 'instructions'

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, ON)

use of undeclared identifier 'instructions'

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, OFF)

use of undeclared identifier 'instructions'

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, ON)

‘instructions’ undeclared (first use in this function); did you mean ‘IRInstructions’?

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, OFF)

‘instructions’ undeclared (first use in this function); did you mean ‘IRInstructions’?

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, ON)

‘instructions’ undeclared (first use in this function); did you mean ‘IRInstructions’?

Check failure on line 739 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, OFF)

‘instructions’ undeclared (first use in this function); did you mean ‘IRInstructions’?
context.instructions.length);
Expand All @@ -719,11 +749,14 @@ static IRFunctionDef generate_ir_function_def(const FunctionDecl* decl,
parameters[i] = decl->params.data[i]->rewrote_name;
}

return (IRFunctionDef){.name = decl->name->name,
.param_count = param_count,
IRBasicBlock** blocks =

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, ON)

redefinition of 'blocks'

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, OFF)

redefinition of 'blocks'

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, ON)

redefinition of 'blocks'

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, OFF)

redefinition of 'blocks'

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, ON)

redefinition of ‘blocks’

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, OFF)

redefinition of ‘blocks’

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, ON)

redefinition of ‘blocks’

Check failure on line 752 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, OFF)

redefinition of ‘blocks’
ARENA_ALLOC_ARRAY(tu_context->permanent_arena, IRBasicBlock*, 1);
blocks[0] = start;

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, ON)

use of undeclared identifier 'start'

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Release, OFF)

use of undeclared identifier 'start'

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, ON)

use of undeclared identifier 'start'

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, llvm-18.0.0, Ninja, Debug, OFF)

use of undeclared identifier 'start'

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, ON)

‘start’ undeclared (first use in this function); did you mean ‘str’?

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Release, OFF)

‘start’ undeclared (first use in this function); did you mean ‘str’?

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, ON)

‘start’ undeclared (first use in this function); did you mean ‘str’?

Check failure on line 754 in src/ir/ir_generator.c

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, gcc-13, Ninja, Debug, OFF)

‘start’ undeclared (first use in this function); did you mean ‘str’?

return (IRFunctionDef){.param_count = param_count,
.params = parameters,
.instruction_count = context.instructions.length,
.instructions = instructions};
.blocks = blocks,
.block_count = 1};
}

typedef struct IRFunctionVec {
Expand Down
127 changes: 67 additions & 60 deletions src/ir/ir_printer.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,75 @@ static void print_binary_op(IRInstruction instruction, const char* op_name)
printf("\n");
}

static void print_basic_block(const IRBasicBlock* block)
{
for (size_t j = 0; j < block->instruction_count; j++) {
const IRInstruction instruction = block->instructions[j];
switch (instruction.typ) {
case IR_INVALID: MCC_UNREACHABLE(); break;
case IR_COPY: print_unary_op(instruction, "copy"); break;
case IR_NEG: print_unary_op(instruction, "neg"); break;
case IR_COMPLEMENT: print_unary_op(instruction, "complement"); break;
case IR_NOT: print_unary_op(instruction, "not"); break;
case IR_ADD: print_binary_op(instruction, "add"); break;
case IR_SUB: print_binary_op(instruction, "sub"); break;
case IR_MUL: print_binary_op(instruction, "mul"); break;
case IR_DIV: print_binary_op(instruction, "div"); break;
case IR_MOD: print_binary_op(instruction, "mod"); break;
case IR_BITWISE_AND: print_binary_op(instruction, "bitand"); break;
case IR_BITWISE_OR: print_binary_op(instruction, "bitor"); break;
case IR_BITWISE_XOR: print_binary_op(instruction, "xor"); break;
case IR_SHIFT_LEFT: print_binary_op(instruction, "shl"); break;
case IR_SHIFT_RIGHT_ARITHMETIC: print_binary_op(instruction, "ashr"); break;
case IR_SHIFT_RIGHT_LOGICAL: print_binary_op(instruction, "lshr"); break;
case IR_EQUAL: print_binary_op(instruction, "eq"); break;
case IR_NOT_EQUAL: print_binary_op(instruction, "ne"); break;
case IR_LESS: print_binary_op(instruction, "lt"); break;
case IR_LESS_EQUAL: print_binary_op(instruction, "le"); break;
case IR_GREATER: print_binary_op(instruction, "gt"); break;
case IR_GREATER_EQUAL: print_binary_op(instruction, "ge"); break;
case IR_JMP: {
printf(" jmp ");
printf(".%.*s\n", (int)instruction.label.size, instruction.label.start);
} break;
case IR_BR: {
printf(" br ");
print_ir_value(instruction.cond);
printf(" .%.*s .%.*s\n", //
(int)instruction.if_label.size, instruction.if_label.start, //
(int)instruction.else_label.size, instruction.else_label.start);
} break;
case IR_LABEL: {
printf(".%.*s:\n", (int)instruction.label.size, instruction.label.start);
} break;
case IR_CALL: {
printf(" ");
print_ir_value(instruction.call.dest);
printf(" = call %.*s(", (int)instruction.call.func_name.size,
instruction.call.func_name.start);
for (uint32_t k = 0; k < instruction.call.arg_count; ++k) {
if (k != 0) { printf(", "); }
print_ir_value(instruction.call.args[k]);
}
printf(")\n");
} break;
}
}
switch (block->tail.kind) {
case IR_BLOCK_TAIL_INVALID: MCC_UNREACHABLE();
case IR_BLOCK_TAIL_RETURN: {
printf(" return ");
print_ir_value(block->tail.return_val);
printf("\n");
} break;
}
}

void print_ir(const IRProgram* ir)
{
for (size_t i = 0; i < ir->function_count; i++) {
const IRFunctionDef ir_function = ir->functions[i];
const StringView name = ir_function.name;
const StringView name = get_ir_function_name(&ir_function);
printf("func %.*s(", (int)name.size, name.start);
for (uint32_t j = 0; j < ir_function.param_count; ++j) {
if (j != 0) { printf(", "); }
Expand All @@ -43,65 +107,8 @@ void print_ir(const IRProgram* ir)
}
printf("):\n");

for (size_t j = 0; j < ir_function.instruction_count; j++) {
const IRInstruction instruction = ir_function.instructions[j];
switch (instruction.typ) {
case IR_INVALID: MCC_UNREACHABLE(); break;
case IR_RETURN: {
printf(" return ");
print_ir_value(instruction.operand1);
printf("\n");
} break;
case IR_COPY: print_unary_op(instruction, "copy"); break;
case IR_NEG: print_unary_op(instruction, "neg"); break;
case IR_COMPLEMENT: print_unary_op(instruction, "complement"); break;
case IR_NOT: print_unary_op(instruction, "not"); break;
case IR_ADD: print_binary_op(instruction, "add"); break;
case IR_SUB: print_binary_op(instruction, "sub"); break;
case IR_MUL: print_binary_op(instruction, "mul"); break;
case IR_DIV: print_binary_op(instruction, "div"); break;
case IR_MOD: print_binary_op(instruction, "mod"); break;
case IR_BITWISE_AND: print_binary_op(instruction, "bitand"); break;
case IR_BITWISE_OR: print_binary_op(instruction, "bitor"); break;
case IR_BITWISE_XOR: print_binary_op(instruction, "xor"); break;
case IR_SHIFT_LEFT: print_binary_op(instruction, "shl"); break;
case IR_SHIFT_RIGHT_ARITHMETIC:
print_binary_op(instruction, "ashr");
break;
case IR_SHIFT_RIGHT_LOGICAL: print_binary_op(instruction, "lshr"); break;
case IR_EQUAL: print_binary_op(instruction, "eq"); break;
case IR_NOT_EQUAL: print_binary_op(instruction, "ne"); break;
case IR_LESS: print_binary_op(instruction, "lt"); break;
case IR_LESS_EQUAL: print_binary_op(instruction, "le"); break;
case IR_GREATER: print_binary_op(instruction, "gt"); break;
case IR_GREATER_EQUAL: print_binary_op(instruction, "ge"); break;
case IR_JMP: {
printf(" jmp ");
printf(".%.*s\n", (int)instruction.label.size, instruction.label.start);
} break;
case IR_BR: {
printf(" br ");
print_ir_value(instruction.cond);
printf(" .%.*s .%.*s\n", //
(int)instruction.if_label.size, instruction.if_label.start, //
(int)instruction.else_label.size, instruction.else_label.start);
} break;
case IR_LABEL: {
printf(".%.*s:\n", (int)instruction.label.size,
instruction.label.start);
} break;
case IR_CALL: {
printf(" ");
print_ir_value(instruction.call.dest);
printf(" = call %.*s(", (int)instruction.call.func_name.size,
instruction.call.func_name.start);
for (uint32_t k = 0; k < instruction.call.arg_count; ++k) {
if (k != 0) { printf(", "); }
print_ir_value(instruction.call.args[k]);
}
printf(")\n");
} break;
}
for (uint32_t j = 0; j < ir_function.block_count; ++j) {
print_basic_block(ir_function.blocks[j]);
}
}
}
2 changes: 1 addition & 1 deletion src/x86/x86.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static X86FunctionDef x86_generate_function(const IRFunctionDef* ir_function,
context->scratch_arena = old_scratch_arena;

return (X86FunctionDef){
.name = ir_function->name,
.name = get_ir_function_name(ir_function),
.instructions = instruction_buffer,
.instruction_count = fixed_instructions.length,
};
Expand Down
Loading

0 comments on commit 05b7932

Please sign in to comment.