diff --git a/.clang-format-include b/.clang-format-include new file mode 100644 index 0000000..eae9b61 --- /dev/null +++ b/.clang-format-include @@ -0,0 +1,3 @@ +src/**/* +lib/env_args.cpp +lib/env_args.h diff --git a/README.md b/README.md index a03d7c6..bd596c5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ General programming language for low-level performant program. - `src/kapuc/{lex,parse}.{c,h}` for syntax/parser - (TODO) `src/kapu/` for the main package manager/build system - Before making pr, make sure - - You run `clang-format` with all of the files you modified (or simpler, ```git clang-format --staged``` for staged changes) + - You run `clang-format` with all of the files you modified (or simpler, ```git clang-format --staged``` for staged changes or ```ninja -C build clang-format``` for full project) - You make sure the build is successful - (TODO) check clang-tidy with `clang-tidy --warnings-as-errors=* ./src/kapuc/*.{c,h} ./lib/env_args.{cpp,h}` diff --git a/compile_commands.json b/compile_commands.json index 25eb4b2..affbd32 120000 --- a/compile_commands.json +++ b/compile_commands.json @@ -1 +1 @@ -build/compile_commands.json \ No newline at end of file +./build/compile_commands.json \ No newline at end of file diff --git a/meson.build b/meson.build index e367855..e6028a3 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ log_headers = files('lib/log.h') env_args_sources = files('lib/env_args.cpp') env_args_headers = files('lib/env_args.h') -kapuc_sources = files('src/kapuc/helper.h', 'src/kapuc/lex.c', 'src/kapuc/lex.h', 'src/kapuc/main.c', 'src/kapuc/parse.c', 'src/kapuc/parse.h') +kapuc_sources = files('src/kapuc/helper.h', 'src/kapuc/lex.c', 'src/kapuc/lex.h', 'src/kapuc/main.c', 'src/kapuc/parse.c', 'src/kapuc/parse.h', 'src/kapuc/analyzer/analysis.c', 'src/kapuc/analyzer/analysis.h', 'src/kapuc/analyzer/scope_analysis.h', 'src/kapuc/PIR/generator.c', 'src/kapuc/PIR/generator.h', 'src/kapuc/PIR/compiler.c', 'src/kapuc/PIR/compiler.h', 'src/kapuc/PIR/PIR.h') # Define the libraries sds_lib = static_library('sds', sds_sources, include_directories: include_directories('.')) diff --git a/shell.nix b/shell.nix index bd54dd4..20faa39 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,4 @@ {pkgs ? import {} }: pkgs.mkShell { - nativeBuildInputs = with pkgs.buildPackages; [meson llvmPackages_17.libllvm ninja]; + nativeBuildInputs = with pkgs; [meson llvmPackages_17.libllvm clang-tools_17 ninja]; } diff --git a/src/kapuc/PIR/PIR.h b/src/kapuc/PIR/PIR.h new file mode 100644 index 0000000..c781df3 --- /dev/null +++ b/src/kapuc/PIR/PIR.h @@ -0,0 +1,176 @@ +#include "lib/sds.h" + +#include +#include + +#ifndef PIR_H +#define PIR_H + +enum expr_type +{ + // operation (all are non-checked operation on LLVM) + // TODO: add flag to change this + Add, + Mul, + Del, + Div, + Func_val, + Val, +}; + +typedef struct +{ + bool is_default_type; + bool is_ptr; + union + { + uint8_t + default_type; // 0: int8, 1: int16, 2: int32, 3: int64, 4: bool (int1) + // FIXME: add type trail for custom type + }; +} typing; + +typedef struct +{ + typing t; + union + { + int int__val; + // FIXME: add value for custom type (struct, etc) + }; +} val; + +typedef struct expr_ +{ + enum expr_type t; + union + { + struct + { + struct expr_* lhs; + struct expr_* rhs; + } b; + val v; + size_t func_val; + }; +} expr; + +#define T expr +void +expr_free(T* v); +T +expr_copy(T* v); +#include "lib/ctl/vec.h" +#undef T + +struct assignment +{ + int id; + expr e; +}; + +typedef enum +{ + assignment, + ret, + call, + ic, +} stmt_type; + +typedef struct +{ + size_t call_ids; // the main_blocks index + vec_expr value; +} call_expr; + +typedef struct +{ + int jmp_case; + int jmp_result; +} int_case; + +#define T int_case +void +int_case_free(T*); +T +int_case_copy(T*); +#include "lib/ctl/vec.h" + +typedef struct +{ + expr to_switch; + vec_int_case ics; +} int_switch; + +typedef struct +{ + stmt_type t; + union + { + struct assignment assignment; + expr ret_val; + call_expr call_ca; + int_switch ics; + }; +} stmt; +#define T stmt +void +stmt_free(T*); +T +stmt_copy(T*); +#include "lib/ctl/vec.h" + +typedef typing FUNC_VAR; +#define T FUNC_VAR +void +FUNC_VAR_free(T*); +T +FUNC_VAR_copy(T*); +#include "lib/ctl/vec.h" + +typedef vec_stmt BLOCK; +#define T BLOCK +void +BLOCK_free(T*); +T +BLOCK_copy(T*); +#include "lib/ctl/vec.h" + +struct FUNC +{ + sds name; + vec_FUNC_VAR vv; + vec_BLOCK bs; + typing t; + bool is_external; + bool is_variadic; +}; + +typedef enum +{ + func +} main_type; + +typedef struct +{ + main_type type; + union + { + struct FUNC func; + }; +} MAIN_BLOCK; + +#define T MAIN_BLOCK +void +MAIN_BLOCK_free(T*); +T +MAIN_BLOCK_copy(T*); +#include "lib/ctl/vec.h" + +// entire PIR for the file generation +struct PIR +{ + vec_MAIN_BLOCK main_blocks; +}; + +#endif diff --git a/src/kapuc/PIR/compiler.c b/src/kapuc/PIR/compiler.c new file mode 100644 index 0000000..09660b8 --- /dev/null +++ b/src/kapuc/PIR/compiler.c @@ -0,0 +1,389 @@ +#include "compiler.h" + +#include "lib/log.h" + +#include "llvm-c/Core.h" +#include "llvm-c/TargetMachine.h" +#include "llvm-c/Types.h" + +#include +#include +#include + +static LLVMBuilderRef +add_default__start_func(LLVMModuleRef module, LLVMValueRef main, LLVMTypeRef t) +{ + LLVMTypeRef exit_arg_types[] = { LLVMInt32Type() }; + LLVMTypeRef exit_type = + LLVMFunctionType(LLVMVoidType(), exit_arg_types, 1, 0); + LLVMValueRef exit_func = LLVMAddFunction(module, "exit", exit_type); + + LLVMBuilderRef builder2 = LLVMCreateBuilder(); + LLVMValueRef _start_func = LLVMAddFunction( + module, "_start", LLVMFunctionType(LLVMVoidType(), NULL, 0, 0)); + LLVMBasicBlockRef _start_block = + LLVMAppendBasicBlock(_start_func, "_start_real_entry"); + LLVMPositionBuilderAtEnd(builder2, _start_block); + LLVMValueRef main_call = LLVMBuildCall2(builder2, t, main, NULL, 0, "main"); + LLVMValueRef arg[] = { main_call }; + LLVMBuildCall2(builder2, exit_type, exit_func, arg, 1, ""); + LLVMBuildUnreachable(builder2); + return builder2; +} + +#define TYSWITCH(a, b, c) \ + case a: { \ + c; \ + break; \ + } + +#define ALL_TYPE(M) \ + M(0, LLVMInt8Type()) \ + M(1, LLVMInt16Type()) \ + M(2, LLVMInt32Type()) M(3, LLVMInt64Type()) M(4, LLVMInt1Type()) + +static inline LLVMValueRef +_resolve_static_val(val* v) +{ + log_debug("resolving static value of type %d", v->t); + assert(v->t.is_default_type); + switch (v->t.default_type) { +#define INTTYVAL(a, b) \ + TYSWITCH(a, b, return LLVMConstInt(b, v->int__val, false)) + ALL_TYPE(INTTYVAL) + default: + return NULL; +#undef INTTYVAL + } +} + +static LLVMValueRef +resolve_static_val(val* v, LLVMBuilderRef b) +{ + if (v->t.is_ptr) + return LLVMBuildIntToPtr(b, _resolve_static_val(v), LLVMInt8Type(), ""); + return _resolve_static_val(v); +} + +// for func_val <-> LLVMValueRef +// we can use vec for this since func_var is supposed to be linear anyways +typedef struct +{ + bool isAlloca_ed; + LLVMValueRef v; +} FuncVarReg; +#define T FuncVarReg +void +FuncVarReg_free(T*) +{ + // we do nothing since we can just dispose entire module for that +} +T +FuncVarReg_copy(T* V) +{ + FuncVarReg* V2 = + malloc(sizeof(FuncVarReg)); // I mean it's just pointer?? also this + // function shouldn't be called anyways? + memcpy(V2, V, sizeof(FuncVarReg)); + return *V2; +} +#include "lib/ctl/vec.h" +#undef T + +// for func <-> LLVMValueRef +typedef struct +{ + bool is_external; + LLVMValueRef v; +} Func; +#define T Func +void +Func_free(T*) +{ + // we do nothing since we can just dispose entire module for that +} +T +Func_copy(T* V) +{ + Func* V2 = malloc(sizeof(Func)); // I mean it's just pointer?? also this + // function shouldn't be called anyways? + memcpy(V2, V, sizeof(Func)); + return *V2; +} +#include "lib/ctl/vec.h" +#undef T + +static inline LLVMValueRef +resolve_val(expr* e, vec_FuncVarReg* v, LLVMBuilderRef b) +{ + switch (e->t) { + case Val: + return resolve_static_val(&e->v, b); + case Func_val: { + FuncVarReg* smol_v = vec_FuncVarReg_at(v, e->func_val); + if (smol_v->isAlloca_ed) + return LLVMBuildLoad2(b, LLVMInt8Type(), smol_v->v, ""); + return smol_v->v; + } + default: + return NULL; + } +} +#define T LLVMValueRef +void +LLVMValueRef_free(T*) +{ + // it's a ref anyways? +} +T +LLVMValueRef_copy(T* v) +{ + return *v; +} +#include "lib/ctl/vec.h" +#undef T + +#define T LLVMBuilderRef +void +LLVMBuilderRef_free(T* v) +{ + LLVMDisposeBuilder(*v); +} +T +LLVMBuilderRef_copy(T* v) +{ + return *v; +} +#include "lib/ctl/vec.h" +#undef T + +// we don't have to use vec.h because this is just runtime static static stuff +static inline LLVMBasicBlockRef* +give_me_n_blocks(int n) +{ + return calloc(n, sizeof(LLVMBasicBlockRef)); +} + +LLVMModuleRef +generate_LLVM_IR(struct PIR* p, char* module_name) +{ + LLVMModuleRef module = LLVMModuleCreateWithName(module_name); + vec_Func fs = vec_Func_init(); + vec_LLVMBuilderRef b_ref = vec_LLVMBuilderRef_init(); + foreach (vec_MAIN_BLOCK, &p->main_blocks, iter) { + switch (iter.ref->type) { + case func: { + // check typing + assert(iter.ref->func.t.is_default_type); + LLVMTypeRef ret_type = NULL; + LLVMBuilderRef builder = LLVMCreateBuilder(); + switch (iter.ref->func.t.default_type) { +#define FUNCTY(a, b) TYSWITCH(a, b, ret_type = b); + ALL_TYPE(FUNCTY) +#undef FUNCTY + } + ret_type = + LLVMFunctionType(ret_type, NULL, 0, iter.ref->func.is_variadic); + LLVMValueRef f = + LLVMAddFunction(module, iter.ref->func.name, ret_type); + char* type_string = LLVMPrintTypeToString(ret_type); + log_debug("type: %s", type_string); + LLVMDisposeMessage(type_string); + Func* fi = malloc(sizeof(Func)); + fi->is_external = iter.ref->func.is_external; + fi->v = f; + vec_Func_push_back(&fs, *fi); + if (!iter.ref->func.is_external) { + char val[15]; // TODO: figure out why 15 + size_t current_pos = 0; + vec_FuncVarReg v = vec_FuncVarReg_init(); + LLVMBasicBlockRef* bbs = + give_me_n_blocks(iter.ref->func.bs.size); + int current_block = 0; + foreach (vec_BLOCK, &iter.ref->func.bs, iter2) { + sprintf(val, + "$%zu", + current_pos); // TODO: add some debug info here on + // debug build? Incase the compiler + // f-up we could check the IR + bbs[current_block] = LLVMAppendBasicBlock( + f, + val); // TODO: add vec_LLVMBasicBlockRef and get_block(..) + // for the ic and stuff where we need to jump + LLVMPositionBuilderAtEnd(builder, bbs[current_block]); + foreach (vec_stmt, iter2.ref, iter3) { + log_debug("checking"); + switch (iter3.ref->t) { + case assignment: { + FuncVarReg* f = malloc(sizeof(FuncVarReg)); + log_debug("found assignment to _%d as %d", + iter3.ref->assignment.id, + iter3.ref->assignment.e.t); + switch (iter3.ref->assignment.e.t) { + case Func_val: + case Val: { + log_debug("Val!"); +#define ASSIGNTY(a, b) TYSWITCH(a, b, lhs = LLVMBuildAlloca(builder, b, "")); + if (iter3.ref->assignment.e.t == Val) + assert(iter3.ref->assignment.e.v.t + .is_default_type); + LLVMValueRef lhs; + switch ( + iter3.ref->assignment.e.v.t.default_type) { + ALL_TYPE(ASSIGNTY) + } + LLVMBuildStore( + builder, + resolve_val( + &iter3.ref->assignment.e, &v, builder), + lhs); + f->isAlloca_ed = true; + f->v = lhs; + vec_FuncVarReg_push_back(&v, *f); + break; + } + default: { + // Add and stuff + log_debug("what %d", iter3.ref->assignment.e.t); + LLVMValueRef val; +#define GENLLVMBUILDE(E, E2) \ + case E: { \ + val = LLVMBuild##E2( \ + builder, \ + resolve_val(iter3.ref->assignment.e.b.lhs, &v, builder), \ + resolve_val(iter3.ref->assignment.e.b.rhs, &v, builder), \ + ""); \ + break; \ + } + switch (iter3.ref->assignment.e.t) { + GENLLVMBUILDE(Add, Add) + GENLLVMBUILDE(Mul, Mul) + GENLLVMBUILDE(Div, SDiv) + GENLLVMBUILDE(Del, Sub) + default: + assert(false); + } + f->isAlloca_ed = false; + f->v = val; + vec_FuncVarReg_push_back(&v, *f); + break; + } + } + continue; + } + case ret: { + log_debug("found ret!"); + LLVMBuildRet( + builder, + resolve_val(&iter3.ref->ret_val, &v, builder)); + continue; + } + case ic: { + log_debug("found int case!"); + LLVMValueRef x = LLVMBuildSwitch( + builder, + resolve_val( + &iter3.ref->ics.to_switch, &v, builder), + NULL, + 1); // TODO: we are not terminating shit, we + // should terminate shit for optimization I + // mean techincally LLVM would do that for us + // with the LowerSwitch but we could optimize + // the PIR before doing that?? Also since we + // jump after this we should just like, be + // terminated??? + assert(false); + LLVMAddCase(x, NULL, NULL); + } + case call: { + log_debug("found call!"); + Func* fi = + vec_Func_at(&fs, iter3.ref->call_ca.call_ids); + if (fi == NULL) { + log_error("Failed to resolve function, is it " + "created yet?"); + log_debug("Continuing from error"); + continue; + } + vec_LLVMValueRef wtf = vec_LLVMValueRef_init(); + foreach ( + vec_expr, &iter3.ref->call_ca.value, iter4) { + vec_LLVMValueRef_push_back( + &wtf, resolve_val(iter4.ref, &v, builder)); + } + LLVMBuildCall2(builder, + LLVMGlobalGetValueType(fi->v), + fi->v, + wtf.value, + wtf.size, + ""); // this is always global anyways + vec_LLVMValueRef_free(&wtf); // wtf + continue; + } + default: { + log_debug("found weird shit at pos %zu", + current_pos); + break; + } + } + } + current_pos++; + } + vec_FuncVarReg_free(&v); + if (strcmp(iter.ref->func.name, "main") == 0) { + printf("found main!\n"); + vec_LLVMBuilderRef_push_back( + &b_ref, + add_default__start_func( + module, + f, + ret_type)); // the PIR should have only 1 main anyways + } + } + vec_LLVMBuilderRef_push_back(&b_ref, builder); // list for dispose + continue; + } + } + } + vec_LLVMBuilderRef_free(&b_ref); + vec_Func_free(&fs); + log_debug("successfully generate LLVM Module"); + return module; +} + +#ifndef DEFAULT_TARGET +#define AUTO_TARGET +#define DEFAULT_TARGET LLVMGetDefaultTargetTriple() +#endif + +void +compile_module(LLVMModuleRef module, char* name, char* target_val) +{ + char* triple; + if (target_val == NULL) + triple = DEFAULT_TARGET; + else + triple = target_val; + LLVMTargetRef t = NULL; + char* error = NULL; + LLVMBool l = LLVMGetTargetFromTriple(triple, &t, &error); + if (t == NULL || l != 0) { + log_error("failed to get target: %s", error); + LLVMDisposeMessage(error); + exit(1); + } + LLVMTargetMachineRef machine = + LLVMCreateTargetMachine(t, + triple, + "generic", + "", + LLVMCodeGenLevelAggressive, + LLVMRelocDynamicNoPic, + LLVMCodeModelMedium); + // TODO: link here + LLVMTargetMachineEmitToFile(machine, module, name, LLVMObjectFile, NULL); +#ifdef AUTO_TARGET + LLVMDisposeMessage(triple); +#endif + LLVMDisposeTargetMachine(machine); +} diff --git a/src/kapuc/PIR/compiler.h b/src/kapuc/PIR/compiler.h new file mode 100644 index 0000000..0dcf0b7 --- /dev/null +++ b/src/kapuc/PIR/compiler.h @@ -0,0 +1,11 @@ +// for compiling the PIR +#include "PIR.h" + +#include "llvm-c/Types.h" + +#include + +LLVMModuleRef +generate_LLVM_IR(struct PIR* p, char* module_name); +void +compile_module(LLVMModuleRef module, char* name, char* target_val); diff --git a/src/kapuc/PIR/generator.c b/src/kapuc/PIR/generator.c new file mode 100644 index 0000000..f9f033c --- /dev/null +++ b/src/kapuc/PIR/generator.c @@ -0,0 +1,391 @@ +#include "generator.h" + +#include "lib/ctl/ctl.h" + +#include +#include +#include + +struct PIR* +create_PIR() +{ + struct PIR* p = malloc(sizeof(struct PIR)); + p->main_blocks = vec_MAIN_BLOCK_init(); + if (p == NULL) + return false; + return p; +} + +void +expr_free(expr* v) +{ + switch (v->t) { + case Add: + case Mul: + case Del: + case Div: + expr_free(v->b.lhs); + expr_free(v->b.rhs); + break; + case Func_val: + case Val: + break; + } +} +expr +expr_copy(expr* v) +{ + expr* v2 = malloc(sizeof(expr)); + memcpy(v2, v, sizeof(expr)); + return *v2; +} + +void +MAIN_BLOCK_free(MAIN_BLOCK* b) +{ + switch (b->type) { + case func: { + sdsfree(b->func.name); + if (!b->func.is_external) { + vec_FUNC_VAR_free(&b->func.vv); + vec_BLOCK_free(&b->func.bs); + } + } + } + // free(b); +} +MAIN_BLOCK +MAIN_BLOCK_copy(MAIN_BLOCK* b) +{ + MAIN_BLOCK* b2 = malloc(sizeof(MAIN_BLOCK)); + memcpy(b2, b, sizeof(MAIN_BLOCK)); + return *b2; +} + +void +BLOCK_free(BLOCK* b) +{ + vec_stmt_free(b); + // free(b); +} +BLOCK +BLOCK_copy(BLOCK* b) +{ + BLOCK* b2 = malloc(sizeof(BLOCK)); + memcpy(b2, b, sizeof(BLOCK)); + return *b2; +} + +void +int_case_free(int_case*) +{ +} +int_case +int_case_copy(int_case* i) +{ + int_case* i2 = malloc(sizeof(int_case)); + memcpy(i2, i, sizeof(int_case)); + return *i2; +} + +void +stmt_free(stmt* s) +{ + switch (s->t) { + case call: { + vec_expr_free(&s->call_ca.value); + break; + } + case assignment: + expr_free(&s->assignment.e); + break; + case ic: + vec_int_case_free(&s->ics.ics); + break; + case ret: + expr_free(&s->ret_val); + break; + } +} +stmt +stmt_copy(stmt* s) +{ + stmt* s2 = malloc(sizeof(stmt)); + memcpy(s2, s, sizeof(stmt)); + return *s2; +} + +void +FUNC_VAR_free(FUNC_VAR* f) +{ + // free(f); +} +FUNC_VAR +FUNC_VAR_copy(FUNC_VAR* f) +{ + FUNC_VAR* b2 = malloc(sizeof(FUNC_VAR)); + memcpy(b2, f, sizeof(FUNC_VAR)); + return *b2; +} + +static void +print_expr(expr* e) +{ + if (e == NULL) + return; + switch (e->t) { + case Add: + fputs("add ", stdout); + print_expr(e->b.lhs); + fputs(", ", stdout); + print_expr(e->b.rhs); + return; + case Mul: + fputs("mul ", stdout); + print_expr(e->b.lhs); + fputs(", ", stdout); + print_expr(e->b.rhs); + return; + case Del: + fputs("del ", stdout); + print_expr(e->b.lhs); + fputs(", ", stdout); + print_expr(e->b.rhs); + return; + case Div: + fputs("div ", stdout); + print_expr(e->b.lhs); + fputs(", ", stdout); + print_expr(e->b.rhs); + return; + case Func_val: + printf("%%%zu", e->func_val); + return; + case Val: + assert(e->v.t.is_default_type); + printf("%d", e->v.int__val); + break; + } +} + +bool +print_PIR(struct PIR* p) +{ + if (p == NULL) + return false; + size_t current_id = 0; + foreach (vec_MAIN_BLOCK, &p->main_blocks, iter) { + switch (iter.ref->type) { + case func: { + printf("FUNC %s (id: %zu, is_external: %s) \n", + iter.ref->func.name, + current_id, + iter.ref->func.is_external ? "true" : "false"); + current_id++; + if (!iter.ref->func.is_external) { + // loop over all block + size_t cur_block = 0; + foreach (vec_BLOCK, &iter.ref->func.bs, block) { + printf("\tblock id %zu\n", cur_block); + cur_block++; + foreach (vec_stmt, block.ref, stmt) { + fputs("\t\t -> ", stdout); + switch (stmt.ref->t) { + case assignment: + printf("assignment to %%%d, e = ", + stmt.ref->assignment.id); + print_expr(&stmt.ref->assignment.e); + fputc('\n', stdout); + break; + case ret: + fputs("ret ", stdout); + print_expr(&stmt.ref->ret_val); + fputc('\n', stdout); + break; + case ic: + break; + case call: + printf("call to %zu (", stmt.ref->call_ca.call_ids); + foreach ( + vec_expr, &stmt.ref->call_ca.value, call_val) { + print_expr(call_val.ref); + fputs(", ", stdout); + } + fputs(")\n", stdout); + break; + } + } + } + } + continue; + } + default: { + printf("unknown?? %d\n", iter.ref->type); + continue; + } + } + } + return true; +} + +size_t +add_main_block_to_PIR(struct PIR* p, MAIN_BLOCK* b) +{ + if (b != NULL && p != NULL) { + vec_MAIN_BLOCK_push_back(&p->main_blocks, *b); + return p->main_blocks.size - 1; + } + return -1; +} + +size_t +add_function_to_PIR(struct PIR* p, + sds function_name, + typing* t, + bool is_external, + bool is_variadic) +{ + MAIN_BLOCK* b = malloc(sizeof(MAIN_BLOCK)); + if (!is_external) + assert(!is_variadic); + b->type = func; + b->func.name = function_name; + if (is_external) { + b->func.is_external = true; + b->func.is_variadic = is_variadic; + } else { + b->func.is_external = false; + b->func.is_variadic = false; + b->func.bs = vec_BLOCK_init(); + b->func.vv = vec_FUNC_VAR_init(); + } + b->func.t = *t; + return add_main_block_to_PIR(p, b); +} + +size_t +add_block_to_function(struct PIR* p, int func_index) +{ + MAIN_BLOCK* big_b = vec_MAIN_BLOCK_at(&p->main_blocks, func_index); + if (big_b != NULL && big_b->type == func) { + BLOCK b = vec_stmt_init(); + vec_BLOCK_push_back(&big_b->func.bs, b); + return big_b->func.bs.size - 1; + } + return -1; +} + +size_t +add_stmt_to_block(struct PIR* p, + size_t func_index, + size_t block_index, + stmt* stmt) +{ + if (func_index < p->main_blocks.size) { + MAIN_BLOCK* big_b = vec_MAIN_BLOCK_at(&p->main_blocks, func_index); + assert(big_b->type == func); + if (block_index < big_b->func.bs.size) { + BLOCK* smol_b = vec_BLOCK_at(&big_b->func.bs, block_index); + vec_stmt_push_back(smol_b, *stmt); + return smol_b->size - 1; + } + } + return -1; +} + +size_t +add_var_to_func(struct PIR* p, int func_index, FUNC_VAR v) +{ + MAIN_BLOCK* big_b = vec_MAIN_BLOCK_at(&p->main_blocks, func_index); + if (big_b != NULL && big_b->type == func) { + vec_FUNC_VAR_push_back(&big_b->func.vv, v); + return big_b->func.vv.size - 1; + } + return -1; +} + +size_t +add_Expr_to_block(struct PIR* p, + int func_index, + int block_index, + expr* value, + FUNC_VAR v) +{ + stmt* s = malloc(sizeof(stmt)); + s->t = assignment; + s->assignment.id = add_var_to_func(p, func_index, v); + s->assignment.e = *value; + add_stmt_to_block(p, func_index, block_index, s); + return s->assignment.id; +} + +size_t +add_Ret_to_block(struct PIR* p, int func_index, int block_index, expr* value) +{ + stmt* s = malloc(sizeof(stmt)); + s->t = ret; + s->ret_val = *value; + return add_stmt_to_block(p, func_index, block_index, s); +} + +size_t +add_Call_to_block(struct PIR* p, + int func_index, + int block_index, + int call_index, + vec_expr args) +{ + stmt* s = malloc(sizeof(stmt)); + s->t = call; + s->call_ca.call_ids = call_index; + s->call_ca.value = args; + return add_stmt_to_block(p, func_index, block_index, s); +} + +size_t +add_intjmp_to_block(struct PIR* p, + int func_index, + int block_index, + expr* to_switch) +{ + stmt* s = malloc(sizeof(stmt)); + s->t = ic; + s->ics.ics = vec_int_case_init(); + s->ics.to_switch = *to_switch; + return add_stmt_to_block(p, func_index, block_index, s); +} + +size_t +add_ic_to_intjmp(struct PIR* p, + size_t func_index, + size_t block_index, + size_t stmt_index, + int jmp_case, + size_t jmp_result) +{ + if (func_index < p->main_blocks.size) { + MAIN_BLOCK* big_b = vec_MAIN_BLOCK_at(&p->main_blocks, func_index); + assert(big_b->type == func); + if (block_index < big_b->func.bs.size) { + BLOCK* smol_b = vec_BLOCK_at(&big_b->func.bs, block_index); + if (stmt_index < smol_b->size) { + stmt* s = vec_stmt_at(smol_b, stmt_index); + assert(s->t == ic); + int_case* c = malloc(sizeof(int_case)); + c->jmp_case = jmp_case; + c->jmp_result = jmp_result; + vec_int_case_push_back(&s->ics.ics, *c); + return s->ics.ics.size - 1; + } + } + } + return -1; +} + +void +free_PIR(struct PIR* p) +{ + vec_MAIN_BLOCK_free(&p->main_blocks); + free(p); + // free(p); +} diff --git a/src/kapuc/PIR/generator.h b/src/kapuc/PIR/generator.h new file mode 100644 index 0000000..11bc24c --- /dev/null +++ b/src/kapuc/PIR/generator.h @@ -0,0 +1,61 @@ +// for generation of the PIR +#include "PIR.h" + +#include + +bool +print_PIR(struct PIR* p); + +struct PIR* +create_PIR(); // return 0 if failed +void +free_PIR(struct PIR* p); + +size_t +add_function_to_PIR( + struct PIR* p, + sds function_name, + typing* t, + bool is_external, + bool is_variadic); // return the index of the PIR, -1 if messed up +size_t +add_block_to_function( + struct PIR* p, + int func_index); // return the index of the block, -1 if messed up + +size_t +add_Expr_to_block(struct PIR* p, + int func_index, + int block_index, + expr* value, + FUNC_VAR v); +size_t +add_Ret_to_block(struct PIR* p, int func_index, int block_index, expr* value); +size_t +add_Call_to_block(struct PIR* p, + int func_index, + int block_index, + int call_index, + vec_expr args); + +size_t +add_intjmp_to_block(struct PIR* p, + int func_index, + int block_index, + expr* to_switch); + +size_t +add_ic_to_intjmp(struct PIR* p, + size_t func_index, + size_t block_index, + size_t stmt_index, + int jmp_case, + size_t jmp_result); +#define INT8_TYPING \ + { \ + .is_default_type = true, .default_type = 0 \ + } +#define INT16_TYPING \ + { \ + .is_default_type = true, .default_type = 1 \ + } diff --git a/src/kapuc/analyzer/NOTE.md b/src/kapuc/analyzer/NOTE.md new file mode 100644 index 0000000..521b782 --- /dev/null +++ b/src/kapuc/analyzer/NOTE.md @@ -0,0 +1,98 @@ +# Analyzer +- this is also supposed to generate something around the line of LLVM IR and the structure of the code (like Rust's MIR) +- at least after we do all the type check +- so we need to keep track of + - the code in current block (that will store the block under it) +- types imported resolves from top to bottom +- scope resolve orders are like this (files are supposed to be like, only represented in the codegen (as 0, to be exact), not usable in real code) + - types + (file::) types -> imported :: types -> std:: types + - function + (file::) function -> imported :: functions -> std:: functions + - variables + this just resolves from outer block to inner block. + (currently) there is no constant defined for entire file. + if it exist it would probably exist as the biggest file:: scope + +## The PIR (Poor man's IR) +- so we reduced down the syntatic sugar from + - Normal blocks to basic block (CFG) + - Variables are changed such that + - they have no name (are represented by the index on the scope_variables on debug) + - they are in static single assignment form (SSA) + - function calls are just jump to block +- some example + - assignment + ``` + { + let i8 x = 0; + x = 1; + } + ``` + is + ``` + { + _1 = 0; + _2 = 1; + } + ``` + + (the reason this should work is that we check for types and constant first before generating the PIR, if we don't do that it would fails) + (the first assignment will be marked useless) + + - using itself + ``` + { + let i8 x = 0; + x = x + 1; + } + ``` + is + ``` + $start { + _1 = 0; + _2 = _1 + 1; + } + ``` + + (the assignment should be merged by the optimizer since the first assignment is useless) + + - if-else/multiple block + ``` + { + const i8 test = 1; + if test == 1 return 0; + else return 1; + } + ``` + is + ``` + { + i8 _1; + $0 { + _1 = 1; + IC(_1, 1: $0, _: $1) + } + $1 { + RET(0); + } + $2 { + RET(1); + } + } + ``` + + (IC are int case, RET are for return, I am not so sure if you could just return without creating variable to return in LLVM?) + +- Definition of PIR (in some weird CFG-or-struct-like form?) + ``` + FUNC = VAR* BLOCK* // we will use $0 block as the start of function + VAR = {TYPE ID} + BLOCK = {ID STMTS} + STMTS = ASSIGNMENT | RET | IC + ASSIGNMENT = {ID VAL} + VAL = NUMBER | BOOL // would this need more data type? + RET = {VAL} + IC = {VAL COND* _COND} // default condition is _ + COND = {VAL JMP} + ``` \ No newline at end of file diff --git a/src/kapuc/analyzer/analysis.c b/src/kapuc/analyzer/analysis.c new file mode 100644 index 0000000..80da7b4 --- /dev/null +++ b/src/kapuc/analyzer/analysis.c @@ -0,0 +1,136 @@ +#include "../parse.h" +#include "./scope_analysis.h" +#include "lib/log.h" + +#include +#include + +// NOTE: To the unlucky one (probably me) who will read this, welcome to the +// territory where insanity met spaghetti, where dumb fucker try to write +// something, and dumb fucker try to review its own shit +// Actual useful NOTE: the design of this analyzer is like this. +// clang-format off +// write & +// access datas generated from each passes +// +----------------------------------+ +// | | +// | | +// | | +// | | +// +----------v----------+ (in order from top to bottom) +// |block analyzer +---> scope passes ---^---> type passes (easy) +// |(contains scope | | +// |variables, the | +---> generate ssa form pass (hard) +// |previous scope, | | +// |and the code | +---> generational reference pass (med) +// |inside the block | | +// |NOTE: technically | +---> dead code elimination (med) +// |the code isn't in | +// |the block, but passed| +// |as argument to | +// |the block passes) | +// +---------------------+ +// clang-format on + +// utility function for the block_variables +#define T block_variables +void +block_variables_free(T* v) +{ + // do nothing + // FIXME: do something +} +T +block_variables_copy(T* c) +{ + T* d = malloc(sizeof(T)); + memcpy(d, c, sizeof(T)); + log_debug("did it"); + return *d; +} +#undef T + +#define TYPE_COMPARE(x, c, d) \ + if (strcmp(c, d) == 0) { \ + x; \ + return true; \ + } + +// get type id from the default type +// TODO: generate avaliable types from the import statements in global trees and +// check against those if we have imported those. currently this approach only +// support default types +static bool +get_default_type(sds s, struct k_trail* t) +{ + TYPE_COMPARE(return true;, s, "i8") + return false; +} + +// get the type info from local scope +// if we don't have it in local scope, do get_type_info on the previous scope +// (if it exist) +static inline bool +get_typeinfo(sds s, struct k_trail* t) +{ + return get_default_type(s, t); +} + +static bool +check_dest_if_found() +{ + return false; +} + +// will return false if found repeated value in scope +static bool +add_typeinfo_to_scope(struct scope_datas* cur_scope) +{ + log_debug("redefinition of %s in same scope"); + return false; +} + +#define SWITCH_BLOCK_STMTS \ + switch (cur_tree->level_stmts_tree.statement->type) { \ + case STMT_ASSIGNMENT: { \ + log_debug("found assignment"); \ + block_variables* c = malloc(sizeof(block_variables)); \ + c->dest = sdsnew("test"); \ + c->assign_type = 0; \ + vec_block_variables_push_back(&blk_scope.scope_variables, *c); \ + } \ + default: { \ + log_error("what!!!"); \ + /*return false;*/ \ + } \ + } + +// previous_scopes can be NULL if we are at parent scope +bool +check_block(struct parse_tree* blk_tree, struct scope_datas* previous_scopes) +{ + assert(blk_tree->type == LVL_STMTS); + struct scope_datas blk_scope; + // add prev scope to the current + blk_scope.previous_scopes = previous_scopes; + // for each statement, add datas to the blk_scope. + // FIXME: fucking change the block tree structure from linked list to an + // vector?? maybe it will be faster?? slower?? since we never manipulate the + // old parse tree anyways?? at least it will be easier. This approach also + // won't work for block inside block (not ifs, just inline block) which is + // god awful. If we want inline blocks, we have to use something like blocks + // structure, which would be amazing. + struct parse_tree* cur_tree = blk_tree; + while (cur_tree->type == LVL_STMTS) { + blk_scope.scope_variables = vec_block_variables_init(); + SWITCH_BLOCK_STMTS; + log_debug("length: %d", blk_scope.scope_variables.size); + break; + } + foreach (vec_block_variables, &blk_scope.scope_variables, iter) + log_debug("%s\n", iter.ref->dest); + // one statement left + // SWITCH_BLOCK_STMTS; +#undef SWITCH_BLOCK_STMTS + return false; +} diff --git a/src/kapuc/analyzer/analysis.h b/src/kapuc/analyzer/analysis.h new file mode 100644 index 0000000..b334df1 --- /dev/null +++ b/src/kapuc/analyzer/analysis.h @@ -0,0 +1,4 @@ +#include + +bool +check_block(struct parse_tree* blk_tree, struct scope_datas* previous_scopes); diff --git a/src/kapuc/analyzer/scope_analysis.h b/src/kapuc/analyzer/scope_analysis.h new file mode 100644 index 0000000..3a891b7 --- /dev/null +++ b/src/kapuc/analyzer/scope_analysis.h @@ -0,0 +1,58 @@ +#include "lib/sds.h" +struct k_trail; + +typedef struct block_variables +{ + sds dest; + struct k_trail* type; + int8_t assign_type; // 0: number + union + {}; // value here (expression?) +} block_variables; + +// i love reverse linked list +// TODO: Figure out if k_trail should be linked list or reverse linked list? +// current choice is to have the biggest trail be std:: (standard libraries) and +// file:: also FIXME: move this to analyzer/types.h, that should be where the +// type trail exist. +// also FIXME: this shouldn't work?? +struct k_trail +{ + block_variables current; + block_variables* prev; + int8_t type; // from trail_type in TREE_TYPE_TRAIL, 0: atom.atom, 1: + // atom::atom, others are reserved +}; + +#define T block_variables +void +block_variables_free(T*); +T +block_variables_copy(T*); +#include "lib/ctl/vec.h" + +enum statement_type +{ + CREATE_VARIABLE, + ASSIGN_VARIABLE, +}; + +struct CREATE_VARIABLE_stmt +{ + sds name; + struct k_trail type; + // TODO: expression? +}; + +// pretty much block +struct scope_datas +{ + vec_block_variables scope_variables; + struct scope_datas* + previous_scopes; // we use previous scope because a scope can only have 1 + // previous scopes also with this we can get previous + // scope's variables to do type analysis + // also we don't care about the inner scope once the + // analysis are complete (unless we don't free it then + // fuck) +}; diff --git a/src/kapuc/analyzer/types.c b/src/kapuc/analyzer/types.c new file mode 100644 index 0000000..e69de29 diff --git a/src/kapuc/analyzer/types.h b/src/kapuc/analyzer/types.h new file mode 100644 index 0000000..e69de29 diff --git a/src/kapuc/main.c b/src/kapuc/main.c index b235c05..2afce9a 100644 --- a/src/kapuc/main.c +++ b/src/kapuc/main.c @@ -6,6 +6,9 @@ #define STB_DS_IMPLEMENTATION #define SHIT_IS_IN_TESTING +#include "PIR/compiler.h" +#include "PIR/generator.h" +#include "analyzer/analysis.h" #include "lex.h" #include "lib/env_args.h" #include "lib/stb_ds.h" @@ -176,6 +179,12 @@ main(const int argc, char** argv) } #endif + // initialize all + LLVMInitializeAllTargetInfos(); + LLVMInitializeAllTargets(); + LLVMInitializeAllTargetMCs(); + LLVMInitializeAllAsmParsers(); + LLVMInitializeAllAsmPrinters(); if (!no_parse) { log_debug("parsing started"); struct parser p = { tokens, 0 }; @@ -187,16 +196,96 @@ main(const int argc, char** argv) print_entire_expression(tree); putchar('\n'); log_debug("tree type: %d", tree->type); + // try checking block + if (tree->type == LVL_STMTS) { + log_debug("got lvl_stmts, try checking block"); + check_block(tree, NULL); + log_debug("ooh we survive segfault"); + } + log_debug("test creating PIR"); + struct PIR* p = create_PIR(); + if (p == false) + log_error("failed to create new PIR instance"); + else { + typing t = INT8_TYPING; + typing t2 = INT16_TYPING; + size_t putchar_index = + add_function_to_PIR(p, sdsnew("putchar"), &t, true, false); + val* v = malloc(sizeof(val)); + v->t = t; + v->int__val = 'c'; + + size_t function_index = + add_function_to_PIR(p, sdsnew("main"), &t, false, false); + if (function_index == -1) + log_error("failed to add function to PIR"); + else + log_debug("successfully add function to PIR, index: %d", + function_index); + size_t b_index = add_block_to_function(p, function_index); + expr* e_prev = malloc(sizeof(expr)); + e_prev->t = Val; + e_prev->v.t = t; + e_prev->v.int__val = 111; + size_t old_var_index = + add_Expr_to_block(p, function_index, b_index, e_prev, t); + log_debug("var_index: %d", old_var_index); + expr* e = malloc(sizeof(expr)); + e->t = Func_val; + e->func_val = old_var_index; + size_t new_var_index = + add_Expr_to_block(p, function_index, b_index, e, t); + log_debug("new var_index: %d", new_var_index); + + expr* e_lhs = malloc(sizeof(expr)); + e_lhs->t = Func_val; + e_lhs->func_val = old_var_index; + expr* e_rhs = malloc(sizeof(expr)); + e_rhs->t = Func_val; + e_rhs->func_val = old_var_index; + expr* e_add = malloc(sizeof(expr)); + e_add->t = Add; + e_add->b.lhs = e_lhs; + e_add->b.rhs = e_rhs; + size_t add_index = + add_Expr_to_block(p, function_index, b_index, e_add, t); + log_debug("add_index: %d", add_index); + + // add call + expr* e_lhs2 = malloc(sizeof(expr)); + e_lhs2->t = Func_val; + e_lhs2->func_val = old_var_index; + vec_expr vvvvv = vec_expr_init(); + vec_expr_push_back(&vvvvv, *e_lhs2); + size_t x = add_Call_to_block( + p, function_index, b_index, putchar_index, vvvvv); + log_debug("call index: %d", x); + + expr* e_ret = malloc(sizeof(expr)); + e_ret->t = Func_val; + e_ret->func_val = old_var_index; + size_t ret_index = + add_Ret_to_block(p, function_index, b_index, e_ret); + log_debug("ret_index: %d", ret_index); + print_PIR(p); + LLVMModuleRef m = generate_LLVM_IR(p, "test"); + LLVMDumpModule(m); + compile_module(m, "test_pir.o", NULL); + free_PIR(p); + free(v); + free(e_prev); + free(e); + free(e_add); + free(e_lhs); + free(e_rhs); + free(e_ret); + LLVMDisposeModule(m); + } free_parse_tree(tree); } } + log_debug("probably finished generating PIR"); log_debug("testing llvm"); - // initialize all - LLVMInitializeAllTargetInfos(); - LLVMInitializeAllTargets(); - LLVMInitializeAllTargetMCs(); - LLVMInitializeAllAsmParsers(); - LLVMInitializeAllAsmPrinters(); test_llvm_wasm(); test_llvm_native(); diff --git a/src/kapuc/parse.c b/src/kapuc/parse.c index 86c4a14..fe61d9e 100644 --- a/src/kapuc/parse.c +++ b/src/kapuc/parse.c @@ -2,7 +2,6 @@ #include "lex.h" #include "lib/log.h" -#include "lib/sds.h" #include "lib/stb_ds.h" #include @@ -14,7 +13,7 @@ type_free(tree_ptr* t){ tree_ptr type_copy(tree_ptr* t) { - return *t; // it is a pointer to pointer anyways? + return *t; // it is a pointer to pointer anyways? }; static bool diff --git a/test_analyze.kp b/test_analyze.kp new file mode 100644 index 0000000..47dc95b --- /dev/null +++ b/test_analyze.kp @@ -0,0 +1,4 @@ +{ + let i8 x = 0; + x = 1; +} diff --git a/test_stmt.kp b/test_stmt.kp index 74e8c96..892209f 100644 --- a/test_stmt.kp +++ b/test_stmt.kp @@ -1,5 +1,5 @@ { - let std::c_types::int8_t x = 0; + let int.i8 x = 0; if x == 1 return x == 2; for let int.i8 n = 0; n < 3; n += 1; n; diff --git a/wasm-test/index.html b/wasm-test/index.html index f92f238..bc1523b 100644 --- a/wasm-test/index.html +++ b/wasm-test/index.html @@ -1,5 +1,6 @@