Skip to content

Commit

Permalink
Refactor how methods work (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Mar 3, 2023
1 parent 5b0fa1e commit 35ca984
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 149 deletions.
4 changes: 4 additions & 0 deletions self_hosted/parses_wrong.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,7 @@ tests/other_errors/method_on_ptr_called_on_class.jou
tests/syntax_error/self_outside_class.jou
tests/should_succeed/int_literals.jou
tests/other_errors/using_void_method.jou
tests/already_exists_error/class_field.jou
tests/already_exists_error/method.jou
tests/should_succeed/imported/point_factory.jou
tests/should_succeed/indirect_method_import.jou
4 changes: 4 additions & 0 deletions self_hosted/runs_wrong.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,7 @@ tests/wrong_type/var_assignment.jou
tests/wrong_type/void_main.jou
tests/wrong_type/while.jou
tests/other_errors/using_void_method.jou
tests/already_exists_error/class_field.jou
tests/already_exists_error/method.jou
tests/should_succeed/imported/point_factory.jou
tests/should_succeed/indirect_method_import.jou
46 changes: 29 additions & 17 deletions src/build_cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,12 @@ static const LocalVariable *build_class_field_pointer(
assert(instance->type->data.valuetype->kind == TYPE_CLASS);
const Type *classtype = instance->type->data.valuetype;

for (int i = 0; i < classtype->data.classfields.count; i++) {
char name[100];
safe_strcpy(name, classtype->data.classfields.names[i]);
const Type *type = classtype->data.classfields.types[i];

if (!strcmp(name, fieldname)) {
for (struct ClassField *f = classtype->data.classdata.fields.ptr; f < End(classtype->data.classdata.fields); f++) {
if (!strcmp(f->name, fieldname)) {
union CfInstructionData dat;
safe_strcpy(dat.fieldname, name);
safe_strcpy(dat.fieldname, f->name);

LocalVariable* result = add_local_var(st, get_pointer_type(type));
LocalVariable* result = add_local_var(st, get_pointer_type(f->type));
add_instruction(st, location, CF_PTR_CLASS_FIELD, &dat, (const LocalVariable*[]){instance,NULL}, result);
return result;
}
Expand Down Expand Up @@ -435,11 +431,29 @@ static const LocalVariable *build_function_or_method_call(struct State *st, cons
else
return_value = NULL;

union CfInstructionData data;
if (self)
snprintf(data.funcname, sizeof data.funcname, "%s.%s", self->type->data.valuetype->name, call->calledname);
else
safe_strcpy(data.funcname, call->calledname);
const Signature *sig = NULL;
if(self) {
assert(self->type->kind == TYPE_POINTER);
const Type *selfclass = self->type->data.valuetype;
assert(selfclass->kind == TYPE_CLASS);
for (const Signature *s = selfclass->data.classdata.methods.ptr; s < End(selfclass->data.classdata.methods); s++) {
assert(get_self_class(s) == selfclass);
if (!strcmp(s->name, call->calledname)) {
sig = s;
break;
}
}
} else {
for (const struct TypeContextFunction *f = st->typectx->functions.ptr; f < End(st->typectx->functions); f++) {
if (!strcmp(f->signature.name, call->calledname)) {
sig = &f->signature;
break;
}
}
}
assert(sig);

union CfInstructionData data = { .signature = copy_signature(sig) };
add_instruction(st, location, CF_CALL, &data, args, return_value);

free(args);
Expand Down Expand Up @@ -876,7 +890,7 @@ CfGraphFile build_control_flow_graphs(AstToplevelNode *ast, TypeContext *typectx

while (ast->kind != AST_TOPLEVEL_END_OF_FILE) {
if(ast->kind == AST_TOPLEVEL_DEFINE_FUNCTION) {
const Signature *sig = typecheck_function_body(typectx, ast->data.funcdef.signature.funcname, &ast->data.funcdef.body);
const Signature *sig = typecheck_function_or_method_body(typectx, NULL, &ast->data.funcdef);
CfGraph *g = build_function(&st, &ast->data.funcdef.body);
g->signature = copy_signature(sig);
Append(&result.graphs, g);
Expand All @@ -892,9 +906,7 @@ CfGraphFile build_control_flow_graphs(AstToplevelNode *ast, TypeContext *typectx
assert(classtype);

for (AstFunctionDef *m = ast->data.classdef.methods.ptr; m < End(ast->data.classdef.methods); m++) {
char name[200];
snprintf(name, sizeof name, "%s.%s", classtype->name, m->signature.funcname);
const Signature *sig = typecheck_function_body(typectx, name, &m->body);
const Signature *sig = typecheck_function_or_method_body(typectx, classtype, m);
CfGraph *g = build_function(&st, &m->body);
g->signature = copy_signature(sig);
Append(&result.graphs, g);
Expand Down
60 changes: 36 additions & 24 deletions src/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ static LLVMTypeRef codegen_type(const Type *type)
assert(0);
case TYPE_CLASS:
{
int n = type->data.classfields.count;
int n = type->data.classdata.fields.len;
LLVMTypeRef *elems = malloc(sizeof(elems[0]) * n); // NOLINT
for (int i = 0; i < n; i++) {
// Treat all pointers inside structs as if they were void*.
// This allows structs to contain pointers to themselves.
if (type->data.classfields.types[i]->kind == TYPE_POINTER)
if (type->data.classdata.fields.ptr[i].type->kind == TYPE_POINTER)
elems[i] = codegen_type(voidPtrType);
else
elems[i] = codegen_type(type->data.classfields.types[i]);
elems[i] = codegen_type(type->data.classdata.fields.ptr[i].type);
}
LLVMTypeRef result = LLVMStructType(elems, type->data.classfields.count, false);
LLVMTypeRef result = LLVMStructType(elems, n, false);
free(elems);
return result;
}
Expand Down Expand Up @@ -102,8 +102,19 @@ static void set_local_var(const struct State *st, const LocalVariable *cfvar, LL
assert(0);
}

static LLVMValueRef codegen_function_decl(const struct State *st, const Signature *sig)
static LLVMValueRef codegen_function_or_method_decl(const struct State *st, const Signature *sig)
{
char fullname[200];
if (get_self_class(sig))
snprintf(fullname, sizeof fullname, "%s.%s", get_self_class(sig)->name, sig->name);
else
safe_strcpy(fullname, sig->name);

// Make it so that this can be called many times without issue
LLVMValueRef func = LLVMGetNamedFunction(st->module, fullname);
if (func)
return func;

LLVMTypeRef *argtypes = malloc(sig->nargs * sizeof(argtypes[0])); // NOLINT
for (int i = 0; i < sig->nargs; i++)
argtypes[i] = codegen_type(sig->argtypes[i]);
Expand All @@ -117,7 +128,7 @@ static LLVMValueRef codegen_function_decl(const struct State *st, const Signatur
LLVMTypeRef functype = LLVMFunctionType(returntype, argtypes, sig->nargs, sig->takes_varargs);
free(argtypes);

LLVMValueRef func = LLVMAddFunction(st->module, sig->funcname, functype);
func = LLVMAddFunction(st->module, fullname, functype);

// Terrible hack: if declaring an OS function that doesn't exist on current platform,
// make it a definition instead of a declaration so that there are no linker errors.
Expand All @@ -128,26 +139,28 @@ static LLVMValueRef codegen_function_decl(const struct State *st, const Signatur
#else
doesnt_exist = "GetModuleFileNameA";
#endif
if (!strcmp(sig->funcname, doesnt_exist)) {
if (!strcmp(fullname, doesnt_exist)) {
LLVMBasicBlockRef block = LLVMAppendBasicBlock(func, "my_block");
LLVMPositionBuilderAtEnd(st->builder, block);
LLVMBuildUnreachable(st->builder);
LLVMBuilderRef b = LLVMCreateBuilder();
LLVMPositionBuilderAtEnd(b, block);
LLVMBuildUnreachable(b);
LLVMDisposeBuilder(b);
}

return func;
}

static LLVMValueRef codegen_call(const struct State *st, const char *funcname, LLVMValueRef *args, int nargs)
static LLVMValueRef codegen_call(const struct State *st, const Signature *sig, LLVMValueRef *args, int nargs)
{
LLVMValueRef function = LLVMGetNamedFunction(st->module, funcname);
LLVMValueRef function = codegen_function_or_method_decl(st, sig);
assert(function);
assert(LLVMGetTypeKind(LLVMTypeOf(function)) == LLVMPointerTypeKind);
LLVMTypeRef function_type = LLVMGetElementType(LLVMTypeOf(function));
assert(LLVMGetTypeKind(function_type) == LLVMFunctionTypeKind);

char debug_name[100] = {0};
if (LLVMGetTypeKind(LLVMGetReturnType(function_type)) != LLVMVoidTypeKind)
snprintf(debug_name, sizeof debug_name, "%s_return_value", funcname);
snprintf(debug_name, sizeof debug_name, "%s_return_value", sig->name);

return LLVMBuildCall2(st->builder, function_type, function, args, nargs, debug_name);
}
Expand Down Expand Up @@ -242,7 +255,7 @@ static void codegen_instruction(const struct State *st, const CfInstruction *ins
LLVMValueRef *args = malloc(ins->noperands * sizeof(args[0])); // NOLINT
for (int i = 0; i < ins->noperands; i++)
args[i] = getop(i);
LLVMValueRef return_value = codegen_call(st, ins->data.funcname, args, ins->noperands);
LLVMValueRef return_value = codegen_call(st, &ins->data.signature, args, ins->noperands);
if (ins->destvar)
setdest(return_value);
free(args);
Expand All @@ -264,15 +277,17 @@ static void codegen_instruction(const struct State *st, const CfInstruction *ins
case CF_PTR_CLASS_FIELD:
{
const Type *classtype = ins->operands[0]->type->data.valuetype;
const struct ClassField *f = classtype->data.classdata.fields.ptr;
int i = 0;
while (strcmp(classtype->data.classfields.names[i], ins->data.fieldname))
while (strcmp(f->name, ins->data.fieldname)) {
f++;
i++;
}

LLVMValueRef val = LLVMBuildStructGEP2(st->builder, codegen_type(classtype), getop(0), i, ins->data.fieldname);
const Type *fieldtype = classtype->data.classfields.types[i];
if (fieldtype->kind == TYPE_POINTER) {
if (f->type->kind == TYPE_POINTER) {
// We lied to LLVM that the struct member is i8*, so that we can do self-referencing types
val = LLVMBuildBitCast(st->builder, val, LLVMPointerType(codegen_type(fieldtype),0), "struct_member_i8_hack");
val = LLVMBuildBitCast(st->builder, val, LLVMPointerType(codegen_type(f->type),0), "struct_member_i8_hack");
}
setdest(val);
}
Expand Down Expand Up @@ -388,14 +403,13 @@ static void codegen_call_to_the_special_startup_function(const struct State *st)
}
#endif

static void codegen_function_def(struct State *st, const CfGraph *cfg)
static void codegen_function_or_method_def(struct State *st, const CfGraph *cfg)
{
st->cfvars = cfg->locals.ptr;
st->cfvars_end = End(cfg->locals);
st->llvm_locals = malloc(sizeof(st->llvm_locals[0]) * cfg->locals.len); // NOLINT

LLVMValueRef llvm_func = LLVMGetNamedFunction(st->module, cfg->signature.funcname);
assert(llvm_func);
LLVMValueRef llvm_func = codegen_function_or_method_decl(st, &cfg->signature);

LLVMBasicBlockRef *blocks = malloc(sizeof(blocks[0]) * cfg->all_blocks.len); // NOLINT
for (int i = 0; i < cfg->all_blocks.len; i++) {
Expand All @@ -408,7 +422,7 @@ static void codegen_function_def(struct State *st, const CfGraph *cfg)
LLVMPositionBuilderAtEnd(st->builder, blocks[0]);

#ifdef _WIN32
if (!strcmp(cfg->signature.funcname, "main"))
if (!get_self_class(&cfg->signature) && !strcmp(cfg->signature.name, "main"))
codegen_call_to_the_special_startup_function(st);
#endif

Expand Down Expand Up @@ -475,10 +489,8 @@ LLVMModuleRef codegen(const CfGraphFile *cfgfile, const TypeContext *typectx)
LLVMSetInitializer(globalptr, LLVMGetUndef(t));
}

for (struct TypeContextFunction *f = typectx->functions.ptr; f < End(typectx->functions); f++)
codegen_function_decl(&st, &f->signature);
for (CfGraph **g = cfgfile->graphs.ptr; g < End(cfgfile->graphs); g++)
codegen_function_def(&st, *g);
codegen_function_or_method_def(&st, *g);

LLVMDisposeBuilder(st.builder);
return st.module;
Expand Down
4 changes: 3 additions & 1 deletion src/free.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ void free_ast(AstToplevelNode *topnodelist)
}


static void free_signature(const Signature *sig)
void free_signature(const Signature *sig)
{
free(sig->argnames);
free(sig->argtypes);
Expand Down Expand Up @@ -258,6 +258,8 @@ void free_control_flow_graph_block(const CfGraph *cfg, CfBlock *b)
for (const CfInstruction *ins = b->instructions.ptr; ins < End(b->instructions); ins++) {
if (ins->kind == CF_CONSTANT)
free_constant(&ins->data.constant);
if (ins->kind == CF_CALL)
free_signature(&ins->data.signature);
free(ins->operands);
}
free(b->instructions.ptr);
Expand Down
26 changes: 14 additions & 12 deletions src/jou_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ struct AstType {
};

struct AstSignature {
Location funcname_location;
char funcname[100];
Location name_location;
char name[100];
List(AstNameTypeValue) args;
bool takes_varargs; // true for functions like printf()
AstType returntype; // can represent void
Expand Down Expand Up @@ -339,6 +339,11 @@ struct AstToplevelNode {
};


struct ClassData {
List(struct ClassField { char name[100]; const Type *type; }) fields;
List(Signature) methods;
};

struct Type {
char name[500]; // All types have a name for error messages and debugging.
enum TypeKind {
Expand All @@ -356,8 +361,8 @@ struct Type {
union {
int width_in_bits; // TYPE_SIGNED_INTEGER, TYPE_UNSIGNED_INTEGER, TYPE_FLOATING_POINT
const Type *valuetype; // TYPE_POINTER
struct ClassData classdata; // TYPE_CLASS
struct { const Type *membertype; int len; } array; // TYPE_ARRAY
struct { int count; char (*names)[100]; const Type **types; } classfields; // TYPE_CLASS
struct { int count; char (*names)[100]; } enummembers;
} data;
};
Expand Down Expand Up @@ -390,19 +395,14 @@ const Type *get_array_type(const Type *t, int len); // result lives as long as
const Type *type_of_constant(const Constant *c);
Type *create_opaque_struct(const char *name);
Type *create_enum(const char *name, int membercount, char (*membernames)[100]);
void set_class_fields(
Type *classtype, // must be opaque class, becomes non-opaque
int fieldcount,
char (*fieldnames)[100], // will be free()d eventually
const Type **fieldtypes); // will be free()d eventually
void free_type(Type *type);

bool is_integer_type(const Type *t); // includes signed and unsigned
bool is_number_type(const Type *t); // integers, floats, doubles
bool is_pointer_type(const Type *t); // includes void pointers

struct Signature {
char funcname[200]; // For methods this is "ClassName.methodname"
char name[100]; // Function or method name. For methods it does not include the name of the class.
int nargs;
const Type **argtypes;
char (*argnames)[100];
Expand All @@ -411,6 +411,8 @@ struct Signature {
Location returntype_location; // meaningful even if returntype is NULL
};

void free_signature(const Signature *sig);
const Type *get_self_class(const Signature *sig); // NULL for functions, a class for methods
char *signature_to_string(const Signature *sig, bool include_return_type);
Signature copy_signature(const Signature *sig);

Expand Down Expand Up @@ -479,7 +481,7 @@ Step 3 is interleaved with build_cfg (see the reset_type_context() function).
*/
ExportSymbol *typecheck_step1_create_types(TypeContext *ctx, const AstToplevelNode *ast);
ExportSymbol *typecheck_step2_signatures_globals_structbodies(TypeContext *ctx, const AstToplevelNode *ast);
const Signature *typecheck_function_body(TypeContext *ctx, const char *name, const AstBody *body);
const Signature *typecheck_function_or_method_body(TypeContext *ctx, const Type *classtype, const AstFunctionDef *ast);

/*
Wipes all function-specific data, making the type context suitable
Expand All @@ -498,7 +500,7 @@ struct CfInstruction {
Location location;
enum CfInstructionKind {
CF_CONSTANT,
CF_CALL,
CF_CALL, // function or method call, depending on whether self_type is NULL (see below)
CF_ADDRESS_OF_LOCAL_VAR,
CF_ADDRESS_OF_GLOBAL_VAR,
CF_SIZEOF,
Expand All @@ -525,7 +527,7 @@ struct CfInstruction {
} kind;
union CfInstructionData {
Constant constant; // CF_CONSTANT
char funcname[200]; // CF_CALL
Signature signature; // CF_CALL
char fieldname[100]; // CF_PTR_CLASS_FIELD
char globalname[100]; // CF_ADDRESS_OF_GLOBAL_VAR
const Type *type; // CF_SIZEOF
Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ static bool astnode_conflicts_with_an_import(const AstToplevelNode *astnode, con
switch(astnode->kind) {
case AST_TOPLEVEL_DECLARE_FUNCTION:
case AST_TOPLEVEL_DEFINE_FUNCTION:
return import->kind == EXPSYM_FUNCTION && !strcmp(import->name, astnode->data.funcdef.signature.funcname);
return import->kind == EXPSYM_FUNCTION && !strcmp(import->name, astnode->data.funcdef.signature.name);
case AST_TOPLEVEL_DECLARE_GLOBAL_VARIABLE:
case AST_TOPLEVEL_DEFINE_GLOBAL_VARIABLE:
return import->kind == EXPSYM_GLOBAL_VAR && !strcmp(import->name, astnode->data.globalvar.name);
Expand Down
4 changes: 2 additions & 2 deletions src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ static AstSignature parse_function_signature(const Token **tokens, bool accept_s

if ((*tokens)->type != TOKEN_NAME)
fail_with_parse_error(*tokens, "a function name");
result.funcname_location = (*tokens)->location;
safe_strcpy(result.funcname, (*tokens)->data.name);
result.name_location = (*tokens)->location;
safe_strcpy(result.name, (*tokens)->data.name);
++*tokens;

if (!is_operator(*tokens, "("))
Expand Down
8 changes: 6 additions & 2 deletions src/print.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ static void print_ast_type(const struct AstType *t)

static void print_ast_function_signature(const AstSignature *sig)
{
printf("%s(", sig->funcname);
printf("%s(", sig->name);
for (const AstNameTypeValue *ntv = sig->args.ptr; ntv < End(sig->args); ntv++) {
if (ntv > sig->args.ptr) printf(", ");
printf("%s: ", ntv->name);
Expand Down Expand Up @@ -496,7 +496,11 @@ static void print_cf_instruction(const CfInstruction *ins)
printf("boolean negation of %s", varname(ins->operands[0]));
break;
case CF_CALL:
printf("call %s(", ins->data.funcname);
if (get_self_class(&ins->data.signature))
printf("call method %s.", get_self_class(&ins->data.signature)->name);
else
printf("call function ");
printf("%s(", ins->data.signature.name);
for (int i = 0; i < ins->noperands; i++) {
if(i) printf(", ");
printf("%s", varname(ins->operands[i]));
Expand Down
Loading

0 comments on commit 35ca984

Please sign in to comment.