From 5a286cfcd85d212c5b7aa6071e43393d3c583524 Mon Sep 17 00:00:00 2001 From: Chitsanupong Rongpan Date: Thu, 25 Apr 2024 13:24:55 +0700 Subject: [PATCH 01/15] add basic analysis process also add clang-utils to the shell.nix --- .clang-format-include | 3 ++ README.md | 2 +- meson.build | 2 +- shell.nix | 2 +- src/kapuc/analyzer/analysis.c | 67 +++++++++++++++++++++++++++++ src/kapuc/analyzer/analysis.h | 2 + src/kapuc/analyzer/scope_analysis.h | 38 ++++++++++++++++ src/kapuc/analyzer/types.c | 0 src/kapuc/analyzer/types.h | 0 src/kapuc/main.c | 7 +++ src/kapuc/parse.c | 2 +- test_stmt.kp | 2 +- 12 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 .clang-format-include create mode 100644 src/kapuc/analyzer/analysis.c create mode 100644 src/kapuc/analyzer/analysis.h create mode 100644 src/kapuc/analyzer/scope_analysis.h create mode 100644 src/kapuc/analyzer/types.c create mode 100644 src/kapuc/analyzer/types.h 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/meson.build b/meson.build index e367855..aa02f27 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') # Define the libraries sds_lib = static_library('sds', sds_sources, include_directories: include_directories('.')) diff --git a/shell.nix b/shell.nix index bd54dd4..cd8100e 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.buildPackages; [meson llvmPackages_17.libllvm clang-tools_17 ninja]; } diff --git a/src/kapuc/analyzer/analysis.c b/src/kapuc/analyzer/analysis.c new file mode 100644 index 0000000..1f1a508 --- /dev/null +++ b/src/kapuc/analyzer/analysis.c @@ -0,0 +1,67 @@ +#include "../parse.h" +#include "./scope_analysis.h" +#include "lib/log.h" + +#include + +// 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_type(); + +// 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 bool +get_typeinfo() +{ + return false; +} + +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; +} + +// 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 + struct parse_tree* cur_tree = blk_tree; + while (cur_tree->type == LVL_STMTS) { +#define SWITCH_BLOCK_STMTS \ + switch (cur_tree->level_stmts_tree.statement->type) { \ + case STMT_ASSIGNMENT: { \ + log_debug("found assignment"); \ + } \ + default: { \ + log_error("what!!!"); \ + return false; \ + } \ + } + SWITCH_BLOCK_STMTS; + } + // 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..7830d36 --- /dev/null +++ b/src/kapuc/analyzer/analysis.h @@ -0,0 +1,2 @@ +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..d0fb2ad --- /dev/null +++ b/src/kapuc/analyzer/scope_analysis.h @@ -0,0 +1,38 @@ +#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 +} block_variables; + +// i love reverse linked list +// TODO: Figure out if k_trail should be linked list or reverse linked list? +struct k_trail +{ + block_variables current; + block_variables* prev; +}; // type trail + +#define T block_variables +void +block_variables_free(T*); +T +block_variables_copy(T*); +#include "lib/ctl/vec.h" +// type of variables vector will be vec_block_variables + +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 +}; + +#undef T 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..e155e91 100644 --- a/src/kapuc/main.c +++ b/src/kapuc/main.c @@ -6,6 +6,7 @@ #define STB_DS_IMPLEMENTATION #define SHIT_IS_IN_TESTING +#include "analyzer/analysis.h" #include "lex.h" #include "lib/env_args.h" #include "lib/stb_ds.h" @@ -187,6 +188,12 @@ 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"); + } free_parse_tree(tree); } } diff --git a/src/kapuc/parse.c b/src/kapuc/parse.c index 86c4a14..ebc8590 100644 --- a/src/kapuc/parse.c +++ b/src/kapuc/parse.c @@ -14,7 +14,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_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; From 2bd27b91fe12ccc31aedd206be34036407ae85ef Mon Sep 17 00:00:00 2001 From: Chitsanupong Rongpan Date: Thu, 9 May 2024 19:59:07 +0700 Subject: [PATCH 02/15] One big change, Initial PIR. Able to generate alloca, store, and ret. --- meson.build | 2 +- shell.nix | 9 +- src/kapuc/PIR/PIR.h | 115 ++++++++++++++++++++ src/kapuc/PIR/compiler.c | 128 ++++++++++++++++++++++ src/kapuc/PIR/compiler.h | 8 ++ src/kapuc/PIR/generator.c | 161 ++++++++++++++++++++++++++++ src/kapuc/PIR/generator.h | 24 +++++ src/kapuc/analyzer/NOTE.md | 98 +++++++++++++++++ src/kapuc/analyzer/analysis.c | 103 +++++++++++++++--- src/kapuc/analyzer/analysis.h | 2 + src/kapuc/analyzer/scope_analysis.h | 30 +++++- src/kapuc/main.c | 38 +++++-- test_analyze.kp | 4 + wasm-test/index.html | 1 + 14 files changed, 692 insertions(+), 31 deletions(-) create mode 100644 src/kapuc/PIR/PIR.h create mode 100644 src/kapuc/PIR/compiler.c create mode 100644 src/kapuc/PIR/compiler.h create mode 100644 src/kapuc/PIR/generator.c create mode 100644 src/kapuc/PIR/generator.h create mode 100644 src/kapuc/analyzer/NOTE.md create mode 100644 test_analyze.kp diff --git a/meson.build b/meson.build index aa02f27..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', 'src/kapuc/analyzer/analysis.c', 'src/kapuc/analyzer/analysis.h', 'src/kapuc/analyzer/scope_analysis.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 cd8100e..b75039f 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,9 @@ -{pkgs ? import {} }: +{pkgs ? import {config.allowUnfree = true;} }: pkgs.mkShell { - nativeBuildInputs = with pkgs.buildPackages; [meson llvmPackages_17.libllvm clang-tools_17 ninja]; + nativeBuildInputs = with pkgs.buildPackages; [meson llvmPackages_17.libllvm clang-tools_17 ninja + (vscode-with-extensions.override { + vscodeExtensions = with vscode-extensions; [ + llvm-vs-code-extensions.vscode-clangd + ]; + })]; } diff --git a/src/kapuc/PIR/PIR.h b/src/kapuc/PIR/PIR.h new file mode 100644 index 0000000..105081e --- /dev/null +++ b/src/kapuc/PIR/PIR.h @@ -0,0 +1,115 @@ +#include +#include +#include "lib/sds.h" + +#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; + union { + uint8_t default_type; // 0: int8, 1: int16, 2: int32, 3: int64, 4: int128 + // 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 {val* lhs; val* rhs;} BinExpr; + +typedef struct { + enum expr_type t; + union { + BinExpr b; + val v; + size_t func_val; + }; +} expr; + +struct assignment { + int id; + expr e; +}; + +typedef enum { + assignment, + ret +} stmt_type; + +typedef struct { + stmt_type t; + union { + struct assignment assignment; + expr ret_val; + }; +} 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; +}; + +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 \ No newline at end of file diff --git a/src/kapuc/PIR/compiler.c b/src/kapuc/PIR/compiler.c new file mode 100644 index 0000000..8140fc7 --- /dev/null +++ b/src/kapuc/PIR/compiler.c @@ -0,0 +1,128 @@ +#include "compiler.h" +#include "lib/log.h" + +#include "llvm-c/Core.h" +#include "llvm-c/TargetMachine.h" +#include "llvm-c/Types.h" +#include +#include + +void 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); + LLVMBuildCall2(builder2, t, main, NULL, 0, "main"); + LLVMValueRef arg[] = { LLVMConstInt(LLVMInt32Type(), 0, 0) }; + LLVMBuildCall2(builder2, exit_type, exit_func, arg, 1, ""); + LLVMBuildUnreachable(builder2); +} + +#define FUNCTY(a,b) case a: {\ + ret_type = LLVMFunctionType(b, NULL, 0, 0);\ + break;\ +} + +LLVMModuleRef generate_LLVM_IR(struct PIR* p, char* module_name) { + LLVMModuleRef module = LLVMModuleCreateWithName(module_name); + foreach(vec_MAIN_BLOCK, &p->main_blocks, iter) { + switch(iter.ref->type) { + case func: { + // check typing + if (!iter.ref->func.t.is_default_type) exit(1); + LLVMTypeRef ret_type = NULL; + LLVMBuilderRef builder = LLVMCreateBuilder(); + switch(iter.ref->func.t.default_type) { + FUNCTY(0,LLVMInt8Type()); + FUNCTY(1,LLVMInt16Type()); + FUNCTY(2,LLVMInt16Type()); + } + LLVMValueRef f = LLVMAddFunction(module, iter.ref->func.name, ret_type); + char val[15]; // TODO: figure out why 15 + size_t current_pos = 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 + LLVMBasicBlockRef block = LLVMAppendBasicBlock(f, val); + LLVMPositionBuilderAtEnd(builder, block); + foreach(vec_stmt, iter2.ref, iter3) { + log_debug("checking"); + switch(iter3.ref->t) { + case assignment: { + log_debug("found assignment to _%d as %d", iter3.ref->assignment.id, iter3.ref->assignment.e.t); + LLVMValueRef lhs = LLVMBuildAlloca(builder, LLVMInt8Type(), "ee"); + switch(iter3.ref->assignment.e.t) { + case Val: { + log_debug("Val!"); + LLVMBuildStore(builder, LLVMConstInt(LLVMInt8Type(), 0, false), lhs); + break; + } + default: {log_debug("what %d", iter3.ref->assignment.e.t);} + } + continue; + } + case ret: { + log_debug("found ret!"); + // TODO: resolve value with a universal function + // something like LLVMValueRef resolve_value(...) + switch(iter3.ref->ret_val.t) { + case Val: { + log_debug("Val!"); + LLVMBuildRet(builder, LLVMConstInt(LLVMInt8Type(), 2, false)); + break; + } + default: {log_debug("what %d", iter3.ref->ret_val.t);} + } + continue; + } + default: { + log_debug("found weird shit at pos %zu", current_pos); + break; + } + } + } + current_pos++; + } + if (strcmp(iter.ref->func.name, "main") == 0) { + printf("found main!\n"); + add_default__start_func(module, f, ret_type); // the PIR should have only 1 main anyways + } + continue; + } + } + } + log_debug("successfully generate LLVM Module"); + return module; +} + +void compile_module(LLVMModuleRef module, char* name) { + char* triple = LLVMGetDefaultTargetTriple(); + 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); + LLVMDisposeMessage(triple); + exit(1); + } + LLVMTargetMachineRef machine = LLVMCreateTargetMachine( + t, + triple, + "generic", + "", + LLVMCodeGenLevelAggressive, + LLVMRelocDynamicNoPic, + LLVMCodeModelMedium); + // TODO: link here + LLVMTargetMachineEmitToFile( + machine, module, name, LLVMObjectFile, NULL); + LLVMDisposeMessage(triple); + LLVMDisposeTargetMachine(machine); +} \ No newline at end of file diff --git a/src/kapuc/PIR/compiler.h b/src/kapuc/PIR/compiler.h new file mode 100644 index 0000000..74cf054 --- /dev/null +++ b/src/kapuc/PIR/compiler.h @@ -0,0 +1,8 @@ +// for compiling the PIR +#include +#include "PIR.h" + +#include "llvm-c/Types.h" + +LLVMModuleRef generate_LLVM_IR(struct PIR* p, char* module_name); +void compile_module(LLVMModuleRef module, char* name); \ No newline at end of file diff --git a/src/kapuc/PIR/generator.c b/src/kapuc/PIR/generator.c new file mode 100644 index 0000000..d0cb3e3 --- /dev/null +++ b/src/kapuc/PIR/generator.c @@ -0,0 +1,161 @@ +#include "generator.h" +#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 +MAIN_BLOCK_free(MAIN_BLOCK* b) { + switch(b->type) { + case func: { + 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) { + // free(b); +} +BLOCK +BLOCK_copy(BLOCK* b) { + BLOCK* b2 = malloc(sizeof(BLOCK)); + memcpy(b2, b, sizeof(BLOCK)); + return *b2; +} + +void stmt_free(stmt* s) { + // free(s); +} +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; +} + +bool print_PIR(struct PIR* p) { + if (p == NULL) return false; + foreach(vec_MAIN_BLOCK, &p->main_blocks, iter) { + switch(iter.ref->type) { + case func: { + printf("FUNC %s\n", iter.ref->func.name); + continue; + } + default: { + printf("unknown?? %d\n", iter.ref->type); + return false; + } + } + } + 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) { + MAIN_BLOCK *b = malloc(sizeof(MAIN_BLOCK)); + b->type = func; + struct FUNC* f = malloc(sizeof(struct FUNC)); + f->name = function_name; + f->bs = vec_BLOCK_init(); + f->vv = vec_FUNC_VAR_init(); + f->t = *t; + b->func = *f; + 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, int func_index, int block_index, stmt* stmt) { + MAIN_BLOCK* big_b = vec_MAIN_BLOCK_at(&p->main_blocks, func_index); + if (big_b != NULL && big_b->type == func) { + BLOCK* smol_b = vec_BLOCK_at(&big_b->func.bs, block_index); + if (smol_b != NULL) { + 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; + add_stmt_to_block(p, func_index, block_index, s); + return s->assignment.id; +} + +void free_PIR(struct PIR *p) { + vec_MAIN_BLOCK_free(&p->main_blocks); + // free(p); +} \ No newline at end of file diff --git a/src/kapuc/PIR/generator.h b/src/kapuc/PIR/generator.h new file mode 100644 index 0000000..98b7bac --- /dev/null +++ b/src/kapuc/PIR/generator.h @@ -0,0 +1,24 @@ +// for generation of the PIR +#include +#include "PIR.h" + +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); // 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); +#define INT8_TYPING {.is_default_type = true, .default_type = 0} \ No newline at end of file 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 index 1f1a508..80da7b4 100644 --- a/src/kapuc/analyzer/analysis.c +++ b/src/kapuc/analyzer/analysis.c @@ -3,21 +3,77 @@ #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_type(); +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 bool -get_typeinfo() +static inline bool +get_typeinfo(sds s, struct k_trail* t) { - return false; + return get_default_type(s, t); } static bool @@ -34,6 +90,21 @@ add_typeinfo_to_scope(struct scope_datas* cur_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) @@ -45,23 +116,21 @@ check_block(struct parse_tree* blk_tree, struct scope_datas* 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 + // 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) { -#define SWITCH_BLOCK_STMTS \ - switch (cur_tree->level_stmts_tree.statement->type) { \ - case STMT_ASSIGNMENT: { \ - log_debug("found assignment"); \ - } \ - default: { \ - log_error("what!!!"); \ - return false; \ - } \ - } + blk_scope.scope_variables = vec_block_variables_init(); SWITCH_BLOCK_STMTS; + log_debug("length: %d", blk_scope.scope_variables.size); + break; } - // one statement left - SWITCH_BLOCK_STMTS; + 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 index 7830d36..b334df1 100644 --- a/src/kapuc/analyzer/analysis.h +++ b/src/kapuc/analyzer/analysis.h @@ -1,2 +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 index d0fb2ad..3a891b7 100644 --- a/src/kapuc/analyzer/scope_analysis.h +++ b/src/kapuc/analyzer/scope_analysis.h @@ -7,16 +7,22 @@ typedef struct block_variables struct k_trail* type; int8_t assign_type; // 0: number union - {}; // value here + {}; // 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; -}; // type trail + int8_t type; // from trail_type in TREE_TYPE_TRAIL, 0: atom.atom, 1: + // atom::atom, others are reserved +}; #define T block_variables void @@ -24,8 +30,21 @@ block_variables_free(T*); T block_variables_copy(T*); #include "lib/ctl/vec.h" -// type of variables vector will be vec_block_variables +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; @@ -33,6 +52,7 @@ 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) }; - -#undef T diff --git a/src/kapuc/main.c b/src/kapuc/main.c index e155e91..09fa592 100644 --- a/src/kapuc/main.c +++ b/src/kapuc/main.c @@ -11,6 +11,8 @@ #include "lib/env_args.h" #include "lib/stb_ds.h" #include "parse.h" +#include "PIR/generator.h" +#include "PIR/compiler.h" #include "llvm-c/Core.h" #include "llvm-c/Target.h" @@ -177,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 }; @@ -194,16 +202,34 @@ main(const int argc, char** argv) check_block(tree, NULL); log_debug("ooh we survive segfault"); } + log_debug("creating PIR"); + struct PIR* p = create_PIR(); + if (p == false) log_error("failed to generate PIR"); + else { + typing t = INT8_TYPING; + size_t function_index = add_function_to_PIR(p, sdsnew("main"), &t); + 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 = malloc(sizeof(expr)); + e->t = Val; + e->v.t = t; + e->v.int__val = 0; + size_t var_index = add_Expr_to_block(p, function_index, b_index, e, t); + log_debug("var_index: %d", var_index); + size_t ret_index = add_Ret_to_block(p, function_index, b_index, e); + 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"); + free_PIR(p); + } free_parse_tree(tree); } } log_debug("testing llvm"); - // initialize all - LLVMInitializeAllTargetInfos(); - LLVMInitializeAllTargets(); - LLVMInitializeAllTargetMCs(); - LLVMInitializeAllAsmParsers(); - LLVMInitializeAllAsmPrinters(); + test_llvm_wasm(); test_llvm_native(); 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/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 @@