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

compiler: Refactor control flow graph data structures #582

Merged
merged 10 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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/build_cfg.jou → compiler/build_cf_graph.jou
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "stdlib/str.jou"
import "stdlib/mem.jou"

import "./cf_graph.jou"
import "./structs.jou"
import "./evaluate.jou"
import "./types.jou"
Expand Down
330 changes: 330 additions & 0 deletions compiler/cf_graph.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
# Control Flow Graph.
# Struct names not prefixed with Cfg because it looks too much like "config" to me

import "stdlib/mem.jou"
import "stdlib/io.jou"

import "./errors_and_warnings.jou"
import "./structs.jou"
import "./print.jou"
import "./free.jou"
import "./types.jou"


def very_short_number_type_description(t: Type*) -> byte*:
if t->kind == TypeKind::FloatingPoint:
return "floating"
if t->kind == TypeKind::SignedInteger:
return "signed"
if t->kind == TypeKind::UnsignedInteger:
return "unsigned"
assert False


class CfStringArray:
str: byte*
len: int

enum CfInstructionKind:
Constant
SpecialConstant # e.g. "WINDOWS", unlike CF_Constant this doesn't trigger "this code will never run" warnings
StringArray
Call # function or method call, depending on whether self_type is NULL (see below)
AddressOfLocalVar
AddressOfGlobalVar
SizeOf
PtrMemsetToZero # takes one operand, a pointer: memset(ptr, 0, sizeof(*ptr))
PtrStore # *op1 = op2 (does not use destvar, takes 2 operands)
PtrLoad # aka dereference
PtrToInt64
Int64ToPtr
PtrClassField # takes 1 operand (pointer), sets destvar to &op->fieldname
PtrCast
PtrAddInt
# Left and right side of number operations must be of the same type (except CfInstructionKind::NumCast).
NumAdd
NumSub
NumMul
NumDiv
NumMod
NumEq
NumLt
NumCast
EnumToInt32
Int32ToEnum
BoolNegate # TODO: get rid of this?
VarCpy # similar to assignment statements: var1 = var2

class CfInstruction:
location: Location
kind: CfInstructionKind
union:
constant: Constant # CfInstructionKind::Constant
strarray: CfStringArray # CfInstructionKind::StringArray
signature: Signature # CfInstructionKind::Call
fieldname: byte[100] # CfInstructionKind::PtrClassField
globalname: byte[100] # CfInstructionKind::AddressOfGlobalVar
scname: byte[100] # CfInstructionKind::SpecialConstant
type: Type* # CfInstructionKind::SizeOf

operands: LocalVariable** # e.g. numbers to add, function arguments
noperands: int
destvar: LocalVariable* # NULL when it doesn't make sense, e.g. functions that return void
hide_unreachable_warning: bool # usually false, can be set to true to avoid unreachable warning false positives

def print(self) -> None:
printf(" line %-4d ", self->location.lineno)

if self->destvar != NULL:
self->destvar->print()
printf(" = ")

if self->kind == CfInstructionKind::AddressOfLocalVar:
printf("address of ")
self->operands[0]->print()
printf(" (local variable)")
elif self->kind == CfInstructionKind::AddressOfGlobalVar:
printf("address of %s (global variable)", self->globalname)
elif self->kind == CfInstructionKind::SizeOf:
printf("sizeof %s", self->type->name)
elif self->kind == CfInstructionKind::BoolNegate:
printf("boolean negation of ")
self->operands[0]->print()
elif self->kind == CfInstructionKind::Call:
if get_self_class(&self->signature) != NULL:
printf("call method %s.", get_self_class(&self->signature)->name)
else:
printf("call function ")
printf("%s(", self->signature.name)
for i = 0; i < self->noperands; i++:
if i != 0:
printf(", ")
self->operands[i]->print()
printf(")")
elif self->kind == CfInstructionKind::NumCast:
printf("number cast ")
self->operands[0]->print()
printf(
" (%d-bit %s --> %d-bit %s)",
self->operands[0]->type->size_in_bits,
very_short_number_type_description(self->operands[0]->type),
self->destvar->type->size_in_bits,
very_short_number_type_description(self->destvar->type),
)
elif self->kind == CfInstructionKind::EnumToInt32:
printf("cast ")
self->operands[0]->print()
printf(" from enum to 32-bit signed int")
elif self->kind == CfInstructionKind::Int32ToEnum:
printf("cast ")
self->operands[0]->print()
printf(" from 32-bit signed int to enum")
elif self->kind == CfInstructionKind::PtrToInt64:
printf("cast ")
self->operands[0]->print()
printf(" to 64-bit integer")
elif self->kind == CfInstructionKind::Int64ToPtr:
printf("cast ")
self->operands[0]->print()
printf(" from 64-bit integer to pointer")
elif self->kind == CfInstructionKind::Constant:
print_constant(&self->constant)
elif self->kind == CfInstructionKind::SpecialConstant:
printf("special constant \"%s\"", self->scname)
elif self->kind == CfInstructionKind::StringArray:
printf("string array ")
print_string(self->strarray.str, self->strarray.len)
elif (
self->kind == CfInstructionKind::NumAdd
or self->kind == CfInstructionKind::NumSub
or self->kind == CfInstructionKind::NumMul
or self->kind == CfInstructionKind::NumDiv
or self->kind == CfInstructionKind::NumMod
or self->kind == CfInstructionKind::NumEq
or self->kind == CfInstructionKind::NumLt
):
if self->kind == CfInstructionKind::NumAdd:
printf("num add ")
elif self->kind == CfInstructionKind::NumSub:
printf("num sub ")
elif self->kind == CfInstructionKind::NumMul:
printf("num mul ")
elif self->kind == CfInstructionKind::NumDiv:
printf("num div ")
elif self->kind == CfInstructionKind::NumMod:
printf("num mod ")
elif self->kind == CfInstructionKind::NumEq:
printf("num eq ")
elif self->kind == CfInstructionKind::NumLt:
printf("num lt ")
else:
assert False
self->operands[0]->print()
printf(", ")
self->operands[1]->print()
elif self->kind == CfInstructionKind::PtrLoad:
# Extra parentheses to make these stand out a bit.
printf("*(")
self->operands[0]->print()
printf(")")
elif self->kind == CfInstructionKind::PtrStore:
printf("*(")
self->operands[0]->print()
printf(") = ")
self->operands[1]->print()
elif self->kind == CfInstructionKind::PtrAddInt:
printf("ptr ")
self->operands[0]->print()
printf(" + integer ")
self->operands[1]->print()
elif self->kind == CfInstructionKind::PtrClassField:
self->operands[0]->print()
printf(" + offset of field \"%s\"", self->fieldname)
elif self->kind == CfInstructionKind::PtrCast:
printf("pointer cast ")
self->operands[0]->print()
elif self->kind == CfInstructionKind::PtrMemsetToZero:
printf("set value of pointer ")
self->operands[0]->print()
printf(" to zero bytes")
elif self->kind == CfInstructionKind::VarCpy:
self->operands[0]->print()
else:
assert False
printf("\n")

def free(self) -> None:
if self->kind == CfInstructionKind::Constant:
free_constant(&self->constant)
if self->kind == CfInstructionKind::StringArray:
free(self->strarray.str)
if self->kind == CfInstructionKind::Call:
free_signature(&self->signature)
free(self->operands)

# operands should be NULL-terminated array, or NULL for empty
# TODO: does it ever need to be NULL?
# TODO: do we need this method at all?
Akuli marked this conversation as resolved.
Show resolved Hide resolved
def set_operands(self, operands: LocalVariable**) -> None:
self->noperands = 0
while operands != NULL and operands[self->noperands] != NULL:
self->noperands++

nbytes = sizeof(self->operands[0]) * self->noperands
self->operands = malloc(nbytes)
assert self->operands != NULL
memcpy(self->operands, operands, nbytes)


class CfBlock:
instructions: CfInstruction*
ninstructions: int
branchvar: LocalVariable* # boolean value used to decide where to jump next

# iftrue and iffalse are NULL for special end block and after calling a noreturn function.
# When iftrue and iffalse are the same, the branchvar is not used and may be NULL.
iftrue: CfBlock*
iffalse: CfBlock*

def free(self) -> None:
for ins = self->instructions; ins < &self->instructions[self->ninstructions]; ins++:
ins->free()
free(self->instructions)


class CfGraph:
signature: Signature
start_block: CfBlock # First block
end_block: CfBlock # Always empty. Return statement jumps here.
all_blocks: CfBlock**
n_all_blocks: int
locals: LocalVariable** # First n variables are the function arguments
nlocals: int

def print(self) -> None:
printed_varnames_idx = 0

sigstr: byte* = signature_to_string(&self->signature, True, True)
printf("Function %s\n", sigstr)
free(sigstr)

printf(" Variables:\n")
for var = self->locals; var < &self->locals[self->nlocals]; var++:
printf(" ")
(*var)->print()
# Pad variable names with spaces to align them.
for i = (*var)->print_width(); i < 20; i++:
putchar(' ')
# If variable name is very long, put two spaces even in that case.
printf(" %s\n", (*var)->type->name)

for blockidx = 0; blockidx < self->n_all_blocks; blockidx++:
b = self->all_blocks[blockidx]

printf(" Block %d", blockidx)
#printf(" at %p", b)

if b == &self->start_block:
printf(" (start block)")
if b == &self->end_block:
assert b->ninstructions == 0
printf(" is the end block.\n")
continue

printf(":\n")

for ins = b->instructions; ins < &b->instructions[b->ninstructions]; ins++:
ins->print()

if b == &self->end_block:
assert b->iftrue == NULL
assert b->iffalse == NULL
elif b->iftrue == NULL and b->iffalse == NULL:
printf(" Execution stops here. We have called a noreturn function.\n")
else:
trueidx = -1
falseidx = -1
for i = 0; i < self->n_all_blocks; i++:
if self->all_blocks[i] == b->iftrue:
trueidx = i
if self->all_blocks[i]==b->iffalse:
falseidx = i
assert trueidx != -1
assert falseidx != -1
if trueidx == falseidx:
printf(" Jump to block %d.\n", trueidx)
else:
assert b->branchvar != NULL
printf(" If ")
b->branchvar->print()
printf(" is True jump to block %d, otherwise block %d.\n", trueidx, falseidx)

printf("\n")

def free(self) -> None:
free_signature(&self->signature)
for b = self->all_blocks; b < &self->all_blocks[self->n_all_blocks]; b++:
(*b)->free()
if *b != &self->start_block and *b != &self->end_block:
free(*b)
for v = self->locals; v < &self->locals[self->nlocals]; v++:
free(*v)
free(self->all_blocks)
free(self->locals)


class CfGraphFile:
filename: byte*
graphs: CfGraph** # only for defined functions
ngraphs: int

def print(self) -> None:
printf("===== Control Flow Graphs for file \"%s\" =====\n", self->filename)
for cfg = self->graphs; cfg < &self->graphs[self->ngraphs]; cfg++:
(*cfg)->print()

def free(self) -> None:
for cfg = self->graphs; cfg < &self->graphs[self->ngraphs]; cfg++:
(*cfg)->free()
free(*cfg)
free(self->graphs)
1 change: 1 addition & 0 deletions compiler/codegen.jou
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "stdlib/mem.jou"
import "stdlib/str.jou"

import "./evaluate.jou"
import "./cf_graph.jou"
import "./llvm.jou"
import "./target.jou"
import "./types.jou"
Expand Down
30 changes: 0 additions & 30 deletions compiler/free.jou
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,3 @@ def free_file_types(ft: FileTypes*) -> None:
free(ft->owned_types)
free(ft->functions)
free(ft->fomtypes)

def free_control_flow_graph_block(cfg: CfGraph*, b: CfBlock*) -> None:
for ins = b->instructions; ins < &b->instructions[b->ninstructions]; ins++:
if ins->kind == CfInstructionKind::Constant:
free_constant(&ins->constant)
if ins->kind == CfInstructionKind::StringArray:
free(ins->strarray.str)
if ins->kind == CfInstructionKind::Call:
free_signature(&ins->signature)
free(ins->operands)
free(b->instructions)
if b != &cfg->start_block and b != &cfg->end_block:
free(b)

def free_cfg(cfg: CfGraph*) -> None:
free_signature(&cfg->signature)

for b = cfg->all_blocks; b < &cfg->all_blocks[cfg->n_all_blocks]; b++:
free_control_flow_graph_block(cfg, *b)
for v = cfg->locals; v < &cfg->locals[cfg->nlocals]; v++:
free(*v)

free(cfg->all_blocks)
free(cfg->locals)
free(cfg)

def free_control_flow_graphs(cfgfile: CfGraphFile*) -> None:
for cfg = cfgfile->graphs; cfg < &cfgfile->graphs[cfgfile->ngraphs]; cfg++:
free_cfg(*cfg)
free(cfgfile->graphs)
Loading
Loading