diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ca46caa6..f84f3dc95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ add_executable(c3c src/compiler/compiler.h src/compiler/types.c src/compiler/module.c + src/compiler/tb_codegen.c src/compiler/llvm_codegen.c src/utils/stringutils.c src/utils/find_msvc.c @@ -162,6 +163,9 @@ endif() target_include_directories(c3c PRIVATE "${CMAKE_SOURCE_DIR}/src/") +target_include_directories(c3c PRIVATE + "${CMAKE_SOURCE_DIR}/tb/") + target_include_directories(c3c_wrappers PRIVATE "${CMAKE_SOURCE_DIR}/wrapper/src/") diff --git a/src/build/build_options.c b/src/build/build_options.c index db27cdc0b..5fee0f448 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -69,6 +69,7 @@ static void usage(void) OUTPUT(" headers [ ...] Analyse files and generate C headers for public methods."); OUTPUT(""); OUTPUT("Options:"); + OUTPUT(" --tinybackend - Use the TinyBackend for compilation."); OUTPUT(" --stdlib - Use this directory as the C3 standard library path."); OUTPUT(" --lib - Use this directory as the C3 library path."); OUTPUT(" --path - Use this as the base directory for the current command."); @@ -397,6 +398,15 @@ static void parse_option(BuildOptions *options) options->compile_option = COMPILE_LEX_PARSE_ONLY; return; case '-': + if (match_longopt("tinybackend")) + { +#if TB_BACKEND + options->backend = BACKEND_TB; + return; +#else + error_exit("error: The TinyBackend is not supported on this platform."); +#endif + } if (match_longopt("version")) { print_version(); @@ -527,6 +537,7 @@ BuildOptions parse_arguments(int argc, const char *argv[]) .command = COMMAND_MISSING, .pie = PIE_DEFAULT, .pic = PIC_DEFAULT, + .backend = BACKEND_LLVM, .files = NULL }; for (int i = DIAG_NONE; i < DIAG_WARNING_TYPE; i++) diff --git a/src/build/build_options.h b/src/build/build_options.h index b6c626ace..ad71d9f8b 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -11,6 +11,13 @@ #define MAX_FILES 2048 #define MAX_THREADS 0xFFFF +#define TB_BACKEND 0 + +typedef enum +{ + BACKEND_LLVM, + BACKEND_TB +} CompilerBackend; typedef enum { @@ -180,6 +187,7 @@ typedef struct BuildOptions_ const char* target_select; const char* path; unsigned version; + CompilerBackend backend; CompilerCommand command; CompileOption compile_option; PieGeneration pie; @@ -231,6 +239,7 @@ typedef struct PieGeneration pie; PicGeneration pic; ArchOsTarget arch_os_target; + CompilerBackend backend; uint32_t symtab_size; struct { diff --git a/src/build/builder.c b/src/build/builder.c index 2c876296a..0b7545966 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -45,6 +45,8 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions * break; } + target->backend = options->backend; + // Copy optimization levels. switch (options->optimization_setting_override) { diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 34c1d536c..7f07f20ed 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -109,12 +109,18 @@ typedef struct CompileData_ Task task; } CompileData; -void thread_compile_task(void *compiledata) +void thread_compile_task_llvm(void *compiledata) { CompileData *data = compiledata; data->object_name = llvm_codegen(data->context); } +void thread_compile_task_tb(void *compiledata) +{ + CompileData *data = compiledata; + data->object_name = tinybackend_codegen(data->context); +} + void sema_analyze_stage(Module *module, AnalysisStage stage) { while (module->stage < stage) @@ -279,6 +285,7 @@ static void setup_bool_define(const char *id, bool value) error_exit("Redefined ident %s", id); } } + void compiler_compile(void) { setup_int_define("C_SHORT_SIZE", platform_target.width_c_short, type_long); @@ -349,16 +356,33 @@ void compiler_compile(void) return; } - llvm_codegen_setup(); - void **gen_contexts = NULL; + void (*task)(void *arg); - for (unsigned i = 0; i < module_count; i++) + switch (active_target.backend) { - void *result = llvm_gen(modules[i]); - if (result) vec_add(gen_contexts, result); + case BACKEND_LLVM: + llvm_codegen_setup(); + for (unsigned i = 0; i < module_count; i++) + { + void *result = llvm_gen(modules[i]); + if (result) vec_add(gen_contexts, result); + } + task = &thread_compile_task_llvm; + break; + case BACKEND_TB: + tinybackend_codegen_setup(); + for (unsigned i = 0; i < module_count; i++) + { + void *result = tinybackend_gen(modules[i]); + if (result) vec_add(gen_contexts, result); + } + task = &thread_compile_task_tb; + break; } + + if (debug_stats) { printf("-- AST/EXPR INFO -- \n"); @@ -398,10 +422,11 @@ void compiler_compile(void) TaskQueueRef queue = taskqueue_create(16); + for (unsigned i = 0; i < output_file_count; i++) { compile_data[i] = (CompileData) { .context = gen_contexts[i] }; - compile_data[i].task = (Task) { &thread_compile_task, &compile_data[i] }; + compile_data[i].task = (Task) { task, &compile_data[i] }; taskqueue_add(queue, &compile_data[i].task); } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index f6368eb31..b6534d907 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1752,6 +1752,10 @@ const char *llvm_codegen(void *context); void *llvm_gen(Module *module); void llvm_codegen_setup(); +const char *tinybackend_codegen(void *context); +void *tinybackend_gen(Module *module); +void tinybackend_codegen_setup(); + void header_gen(Module *module); void global_context_add_type(Type *type); diff --git a/src/compiler/tb_codegen.c b/src/compiler/tb_codegen.c new file mode 100644 index 000000000..171592409 --- /dev/null +++ b/src/compiler/tb_codegen.c @@ -0,0 +1,695 @@ +#include "codegen_internal.h" + +#if TB_BACKEND +#include + +typedef struct +{ + Module *code_module; + const char *object_filename; + + TB_Module *module; + TB_FeatureSet features; + + Decl *current_func_decl; + TB_Function *function; +} GenContext; + +typedef enum +{ + BE_VALUE, + BE_ADDRESS, + BE_ADDRESS_FAILABLE, + BE_BOOLEAN, +} BackendValueKind; + +typedef struct +{ + BackendValueKind kind: 5; + AlignSize alignment; + + Type *type; // Should never be a distinct or canonical type. + + TB_Register value; + TB_Register failable; // TODO what even is a failable? +} BEValue; + +static void tinybackend_emit_expr(GenContext *c, BEValue *value, Expr *expr); + +// Per instance i think? +void tinybackend_codegen_setup() +{ + +} + +static inline bool tinybackend_value_is_addr(BEValue *value) +{ return value->kind == BE_ADDRESS || value->kind == BE_ADDRESS_FAILABLE; } + +static inline bool tinybackend_value_is_bool(BEValue *value) +{ return value->kind == BE_BOOLEAN; } + +static TB_DataType tinybackend_get_type(Type *type) +{ + switch (type->type_kind) + { + case TYPE_U8: + case TYPE_I8: + return TB_TYPE_I8(1); + case TYPE_U16: + case TYPE_I16: + return TB_TYPE_I16(1); + case TYPE_U32: + case TYPE_I32: + return TB_TYPE_I32(1); + case TYPE_U64: + case TYPE_I64: + return TB_TYPE_I64(1); + default: + TODO + } +} + +static inline TB_Register decl_ref(Decl *decl) +{ + if (decl->decl_kind == DECL_VAR && decl->var.kind == VARDECL_UNWRAPPED) return decl_ref(decl->var.alias); + + return (TB_Register)((uintptr_t)decl->backend_ref); +} + +TB_Register +tinybackend_emit_load_aligned(GenContext *c, TB_DataType dt, TB_Register pointer, AlignSize alignment, const char *name) +{ + TB_Register value = tb_inst_load(c->function, dt, pointer, alignment); + return value; +} + +void tinybackend_store_self_aligned(GenContext *c, TB_Register pointer, TB_Register value, Type *type) +{ + tb_inst_store(c->function, tinybackend_get_type(type), pointer, value, type_abi_alignment(type)); +} + +void tinybackend_store_bevalue(GenContext *c, TB_DataType type, BEValue *destination, BEValue *value) +{ + assert(tinybackend_value_is_addr(destination)); + if (value->kind == BE_ADDRESS && !type_is_abi_aggregate(value->type)) + { + value->value = tinybackend_emit_load_aligned(c, + tinybackend_get_type(value->type), + value->value, + value->alignment, + ""); + value->kind = BE_VALUE; + } + + AlignSize alignment = destination->alignment; + switch (value->kind) + { + case BE_BOOLEAN: + value->value = tb_inst_zxt(c->function, value->value, TB_TYPE_I8(1)); + value->kind = BE_VALUE; + FALLTHROUGH; + case BE_VALUE: + tb_inst_store(c->function, + tinybackend_get_type(destination->type), + destination->value, + value->value, + alignment ? alignment : type_abi_alignment(value->type)); + return; + case BE_ADDRESS: + { + // Here we do an optimized(?) memcopy. + ByteSize size = type_size(value->type); + TB_Register copy_size = tb_inst_iconst(c->function, + size <= UINT32_MAX ? TB_TYPE_I32(1) : TB_TYPE_I64(1), + size); + + TB_Register source = value->value; + tb_inst_memcpy(c->function, + destination->value, + value->value, copy_size, + value->alignment ? value->alignment : type_abi_alignment(value->type)); + return; + } + default: + UNREACHABLE + } +} + +void tinybackend_value_set_address(BEValue *value, TB_Register tb_value, Type *type) +{ + value->value = tb_value; + value->alignment = type_abi_alignment(type); + value->kind = BE_ADDRESS; + value->type = type_lowering(type); +} + +void tinybackend_value_set_decl_address(BEValue *value, Decl *decl) +{ + decl = decl_flatten(decl); + tinybackend_value_set_address(value, decl_ref(decl), decl->type); + value->alignment = decl->alignment; +} + +void tinybackend_value_set(BEValue *value, TB_Register reg, Type *type) +{ + type = type_lowering(type); + assert(reg || type == type_void); + value->value = reg; + value->alignment = type_abi_alignment(type); + value->kind = BE_VALUE; + value->type = type; +} + +static void tinybackend_emit_const_expr(GenContext *c, BEValue *value, Expr *expr) +{ + Type *type = type_reduced_from_expr(expr)->canonical; + + switch (expr->const_expr.const_kind) + { + case CONST_INTEGER: + { + TB_Register reg; + Int128 i = expr->const_expr.ixx.i; + + TB_DataType dt = tinybackend_get_type(type); + printf("Const int type: %d\n", dt.type); + + switch (expr->const_expr.ixx.type) + { + case TYPE_I128: + case TYPE_U128: + reg = tb_inst_iconst128(c->function, dt, (TB_Int128){ .lo = i.low, .hi = i.high }); + break; + default: + reg = tb_inst_iconst(c->function, dt, i.low); + break; + } + + tinybackend_value_set(value, reg, type); + return; + } + default: + TODO + } +} + +BEValue tinybackend_emit_assign_expr(GenContext *c, BEValue *ref, Expr *expr, TB_Register failable) +{ + assert(ref->kind == BE_ADDRESS || ref->kind == BE_ADDRESS_FAILABLE); + + BEValue value; + tinybackend_emit_expr(c, &value, expr); + tinybackend_store_bevalue(c, tinybackend_get_type(ref->type), ref, &value); + + return value; +} + +static int find_member_index(Decl *parent, Decl *member) +{ + VECEACH(parent->strukt.members, i) + { + Decl *maybe_member = parent->strukt.members[i]; + if (member == maybe_member) + { + return (int)i; + } + if (!maybe_member->name) + { + if (find_member_index(maybe_member, member) != -1) return (int)i; + } + } + return -1; +} + +static void tinybackend_emit_member_addr(GenContext *c, BEValue *value, Decl *parent, Decl *member) +{ + assert(member->resolve_status == RESOLVE_DONE); + + Decl *found = NULL; + do + { + int index = find_member_index(parent, member); + assert(index > -1); + found = parent->strukt.members[index]; + + // Unlike LLVM, TinyBackend doesn't handle aggregates for you + // so we need offsetof to perform member accesses + switch (parent->type->canonical->type_kind) + { + case TYPE_UNION: + { + TODO + } + break; + case TYPE_STRUCT: + { + TB_Register ref = tb_inst_member_access(c->function, value->value, found->offset); + + tinybackend_value_set_address(value, ref, member->type); + value->alignment = found->alignment; + } + break; + default: + UNREACHABLE + } + parent = found; + } while (found != member); +} + +void tinybackend_emit_access_addr(GenContext *c, BEValue *be_value, Expr *expr) +{ + Expr *parent = expr->access_expr.parent; + tinybackend_emit_expr(c, be_value, parent); + Decl *member = expr->access_expr.ref; + + tinybackend_emit_member_addr(c, be_value, type_lowering(parent->type)->decl, member); +} + +static inline TB_ArithmaticBehavior tinybackend_get_arith_behavior(Type *type) +{ + if (active_target.feature.trap_on_wrap) + { + return type_is_unsigned(type) ? TB_UNSIGNED_TRAP_ON_WRAP : TB_SIGNED_TRAP_ON_WRAP; + } + + return TB_CAN_WRAP; +} + +void tinybackend_value_rvalue(GenContext *c, BEValue *value) +{ + if (value->kind != BE_ADDRESS && value->kind != BE_ADDRESS_FAILABLE) + { + if (value->type->type_kind == TYPE_BOOL && value->kind != BE_BOOLEAN) + { + //value->value = LLVMBuildTrunc(c->builder, value->value, c->bool_type, ""); + //value->kind = BE_BOOLEAN; + TODO + } + return; + } + //llvm_value_fold_failable(c, value); + value->value = tinybackend_emit_load_aligned(c, + tinybackend_get_type(value->type), + value->value, + value->alignment ? value->alignment : type_abi_alignment(value->type), + ""); + + if (value->type->type_kind == TYPE_BOOL) + { + //value->value = LLVMBuildTrunc(c->builder, value->value, c->bool_type, ""); + //value->kind = BE_BOOLEAN; + //return; + TODO + } + value->kind = BE_VALUE; +} + +void tinybackend_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs_loaded, BinaryOp binary_op) +{ + + if (binary_op == BINARYOP_AND || binary_op == BINARYOP_OR) + { + //gencontext_emit_logical_and_or(c, be_value, expr, binary_op); + //return; + TODO + } + BEValue lhs; + if (lhs_loaded) + { + lhs = *lhs_loaded; + } + else + { + tinybackend_emit_expr(c, &lhs, expr->binary_expr.left); + } + tinybackend_value_rvalue(c, &lhs); + + BEValue rhs; + tinybackend_emit_expr(c, &rhs, expr->binary_expr.right); + tinybackend_value_rvalue(c, &rhs); + + /*EMIT_LOC(c, expr); + if (binary_op >= BINARYOP_GT && binary_op <= BINARYOP_EQ) + { + llvm_emit_comparison(c, be_value, &lhs, &rhs, binary_op); + return; + }*/ + + Type *lhs_type = lhs.type; + Type *rhs_type = rhs.type; + Type *vector_type = lhs_type->type_kind == TYPE_VECTOR ? lhs_type->vector.base : NULL; + bool is_float = type_is_float(lhs_type) || (vector_type && type_is_float(vector_type)); + + TB_Register val = TB_NULL_REG; + TB_Register lhs_value = lhs.value; + TB_Register rhs_value = rhs.value; + + TB_DataType dt = tinybackend_get_type(lhs_type); + switch (binary_op) + { + case BINARYOP_ERROR: + UNREACHABLE + case BINARYOP_MULT: + if (is_float) + { + val = tb_inst_fmul(c->function, dt, lhs_value, rhs_value); + break; + } + + // TODO(NeGate): review this later, maybe it shouldn't be NO_WRAP + val = tb_inst_mul(c->function, dt, lhs_value, rhs_value, TB_ASSUME_NUW); + break; + case BINARYOP_SUB: + if (lhs_type->type_kind == TYPE_POINTER) + { + /*bool is_constant = LLVMIsConstant(lhs_value) && LLVMIsConstant(rhs_value); + if (lhs_type == rhs_type) + { + LLVMTypeRef int_type = llvm_get_type(c, type_iptrdiff); + val = LLVMBuildSub(c->builder, LLVMBuildPtrToInt(c->builder, lhs_value, int_type, ""), + LLVMBuildPtrToInt(c->builder, rhs_value, int_type, ""), ""); + val = LLVMBuildExactSDiv(c->builder, val, llvm_const_int(c, type_iptrdiff, type_abi_alignment(lhs_type->pointer)), ""); + break; + } + rhs_value = LLVMConstNeg(rhs_value); + val = llvm_emit_pointer_gep_raw(c, llvm_get_pointee_type(c, lhs_type), lhs_value, rhs_value);*/ + TODO + } + if (is_float) + { + val = tb_inst_fsub(c->function, dt, lhs_value, rhs_value); + break; + } + val = tb_inst_mul(c->function, dt, lhs_value, rhs_value, tinybackend_get_arith_behavior(lhs_type)); + break; + case BINARYOP_ADD: + if (lhs_type->type_kind == TYPE_POINTER) + { + TODO + } + if (is_float) + { + val = tb_inst_fadd(c->function, dt, lhs_value, rhs_value); + break; + } + val = tb_inst_add(c->function, dt, lhs_value, rhs_value, tinybackend_get_arith_behavior(lhs_type)); + break; + case BINARYOP_DIV: + //llvm_emit_trap_zero(c, rhs_type, rhs_value, "% by zero", TOKLOC(expr->span.loc)); + if (is_float) + { + val = tb_inst_fdiv(c->function, dt, lhs_value, rhs_value); + break; + } + + val = tb_inst_div(c->function, dt, lhs_value, rhs_value, !type_is_unsigned(lhs_type)); + break; + case BINARYOP_MOD: + { + TODO + } + case BINARYOP_SHR: + if (type_is_unsigned(lhs_type)) + { + val = tb_inst_shr(c->function, dt, lhs_value, rhs_value); + return; + } + + val = tb_inst_sar(c->function, dt, lhs_value, rhs_value); + break; + case BINARYOP_SHL: + val = tb_inst_shl(c->function, dt, lhs_value, rhs_value, TB_ASSUME_NUW); + break; + case BINARYOP_BIT_AND: + val = tb_inst_and(c->function, dt, lhs_value, rhs_value); + break; + case BINARYOP_BIT_OR: + val = tb_inst_or(c->function, dt, lhs_value, rhs_value); + break; + case BINARYOP_BIT_XOR: + TODO + break; + case BINARYOP_EQ: + case BINARYOP_NE: + case BINARYOP_GE: + case BINARYOP_GT: + case BINARYOP_LE: + case BINARYOP_LT: + UNREACHABLE + case BINARYOP_AND: + case BINARYOP_OR: + case BINARYOP_ASSIGN: + case BINARYOP_MULT_ASSIGN: + case BINARYOP_ADD_ASSIGN: + case BINARYOP_SUB_ASSIGN: + case BINARYOP_DIV_ASSIGN: + case BINARYOP_MOD_ASSIGN: + case BINARYOP_BIT_AND_ASSIGN: + case BINARYOP_BIT_OR_ASSIGN: + case BINARYOP_BIT_XOR_ASSIGN: + case BINARYOP_SHR_ASSIGN: + case BINARYOP_SHL_ASSIGN: + UNREACHABLE + } + assert(val); + tinybackend_value_set(be_value, val, expr->type); +} + +void tinybackend_value_addr(GenContext *c, BEValue *value) +{ + if (value->kind == BE_ADDRESS) return; + + TB_Register temp = tb_inst_local(c->function, type_size(value->type), type_alloca_alignment(value->type)); + tinybackend_store_self_aligned(c, temp, value->value, value->type); + tinybackend_value_set_address(value, temp, value->type); +} + +static void tinybackend_emit_binary_expr(GenContext *c, BEValue *be_value, Expr *expr) +{ + BinaryOp binary_op = expr->binary_expr.operator; + /*if (binary_op >= BINARYOP_ASSIGN && expr_is_vector_index(expr->binary_expr.left)) + { + tinybackend_emit_vector_assign_expr(c, be_value, expr); + return; + }*/ + printf("A\n"); + if (binary_op > BINARYOP_ASSIGN) + { + BinaryOp base_op = binaryop_assign_base_op(binary_op); + assert(base_op != BINARYOP_ERROR); + BEValue addr; + tinybackend_emit_expr(c, &addr, expr->binary_expr.left); + tinybackend_value_addr(c, &addr); + tinybackend_emit_binary(c, be_value, expr, &addr, base_op); + tinybackend_store_bevalue(c, tinybackend_get_type(addr.type), &addr, be_value); + return; + } + printf("B\n"); + if (binary_op == BINARYOP_ASSIGN) + { + Expr *left = expr->binary_expr.left; + tinybackend_emit_expr(c, be_value, expr->binary_expr.left); + assert(tinybackend_value_is_addr(be_value)); + + *be_value = tinybackend_emit_assign_expr(c, be_value, expr->binary_expr.right, TB_NULL_REG /* failable_ref */); + return; + } + + tinybackend_emit_binary(c, be_value, expr, NULL, binary_op); +} + +static void tinybackend_emit_expr(GenContext *c, BEValue *value, Expr *expr) +{ + printf("expr->expr_kind = %d\n", expr->expr_kind); + + switch (expr->expr_kind) + { + case EXPR_CONST: + tinybackend_emit_const_expr(c, value, expr); + return; + case EXPR_ACCESS: + tinybackend_emit_access_addr(c, value, expr); + return; + case EXPR_BINARY: + tinybackend_emit_binary_expr(c, value, expr); + return; + case EXPR_IDENTIFIER: + case EXPR_CONST_IDENTIFIER: + tinybackend_value_set_decl_address(value, expr->identifier_expr.decl); + return; + default: + TODO + } +} + +static TB_Register tinybackend_emit_local_decl(GenContext *c, Decl *decl) +{ + if (decl->var.is_static) + { + TODO + } + + TB_DataType dt = tinybackend_get_type(decl->type); + ByteSize size = type_size(decl->type); + AlignSize align = decl->alignment; + + TB_Register reg = tb_inst_local(c->function, size, align); + decl->backend_ref = (void *)((size_t)reg); + + printf("Local declare: %s (size: %ld, align: %u)\n", decl->type->name, (long)size, align); + + if (decl->var.failable_ref) + { + TODO + } + + Expr *init = decl->var.init_expr; + if (init) + { + // If we don't have undef, then make an assign. + if (init->expr_kind != EXPR_UNDEF) + { + BEValue value; + tinybackend_value_set_decl_address(&value, decl); + tinybackend_emit_assign_expr(c, + &value, + decl->var.init_expr, + (TB_Register)((uintptr_t)decl->var.failable_ref)); + } + } + else + { + Type *type = type_lowering(decl->type); + + // Normal case, zero init. + if (type_is_builtin(type->type_kind) || type->type_kind == TYPE_POINTER) + { + tb_inst_store(c->function, dt, reg, tb_inst_iconst(c->function, dt, 0), align); + } + else + { + TODO + } + } + + return reg; +} + +static void tinybackend_emit_stmt(GenContext *c, Ast *ast) +{ + switch (ast->ast_kind) + { + case AST_DECLARE_STMT: + tinybackend_emit_local_decl(c, ast->declare_stmt); + break; + case AST_RETURN_STMT: + //gencontext_emit_return(c, ast); + tb_inst_ret(c->function, TB_TYPE_VOID(), TB_NULL_REG); + break; + default: + break; + } +} + +static void tinybackend_emit_function_body(GenContext *c, Decl *decl) +{ + printf("Function: %s\n", decl->external_name); + + c->current_func_decl = decl; + + // TODO(NeGate): Actually interpret the return value + c->function = tb_function_create(c->module, decl->external_name, TB_TYPE_VOID()); + + VECEACH(decl->func_decl.body->compound_stmt.stmts, i) + { + tinybackend_emit_stmt(c, decl->func_decl.body->compound_stmt.stmts[i]); + } + + tb_function_print(c->function); +} + +static void tinybackend_gen_context(GenContext *c) +{ + scratch_buffer_clear(); + StringSlice slice = strtoslice(c->code_module->name->module); + while (true) + { + StringSlice part = strnexttok(&slice, ':'); + scratch_buffer_append_len(part.ptr, part.len); + if (!slice.len) break; + slice.ptr++; + slice.len--; + scratch_buffer_append_char('.'); + } + const char *result = scratch_buffer_to_string(); + // TODO filename should depend on platform. + c->object_filename = strformat("%s.o", result); +} + +// Generate context per module (single threaded) +void *tinybackend_gen(Module *module) +{ + GenContext *c = calloc(sizeof(GenContext), 1); + c->code_module = module; + + // TODO identify target architecture + c->module = tb_module_create(TB_ARCH_X86_64, TB_SYSTEM_LINUX, &c->features, TB_OPT_O0, 1); + + tinybackend_gen_context(c); + + printf("Module: %.*s\n", module->name->len, module->name->module); + + // Forward decls + VECEACH(module->contexts, j) + { + Context *context = module->contexts[j]; + + printf(" External symbols: %d\n", vec_size(context->external_symbol_list)); + printf(" Functions: %d\n", vec_size(context->functions)); + } + + VECEACH(module->contexts, j) + { + Context *context = module->contexts[j]; + + VECEACH(context->functions, i) + { + Decl *decl = context->functions[i]; + if (decl->func_decl.body) tinybackend_emit_function_body(c, decl); + } + } + + tb_module_compile(c->module); + return c; +} + +// Compile module (multi threaded) +const char *tinybackend_codegen(void *context) +{ + GenContext *c = (GenContext *)context; + + // Write out the object file + FILE *file = fopen(c->object_filename, "wb"); + tb_module_export(c->module, file); + fclose(file); + + return c->object_filename; +} + +#else + +const char *tinybackend_codegen(void *context) +{ + FATAL_ERROR("Not enabled"); +} +void *tinybackend_gen(Module *module) +{ + FATAL_ERROR("Not enabled"); +} +void tinybackend_codegen_setup() +{ + FATAL_ERROR("TB not enabled"); +} + +#endif + diff --git a/tb/tb.h b/tb/tb.h new file mode 100644 index 000000000..e94e5efe6 --- /dev/null +++ b/tb/tb.h @@ -0,0 +1,315 @@ +// _______ _ ____ _ _ +// |__ __(_) | _ \ | | | | +// | | _ _ __ _ _| |_) | __ _ ___| | _____ _ __ __| | +// | | | | '_ \| | | | _ < / _` |/ __| |/ / _ \ '_ \ / _` | +// | | | | | | | |_| | |_) | (_| | (__| < __/ | | | (_| | +// |_| |_|_| |_|\__, |____/ \__,_|\___|_|\_\___|_| |_|\__,_| +// __/ | +// |___/ +// +// It's a relatively small compiler backend all behind a +// simple C API! To get started: TODO +// +#ifndef _TINYBACKEND_H_ +#define _TINYBACKEND_H_ + +// Windows likes it's secure functions, i kinda do too +// but only sometimes and this isn't one of them +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TB_MAX_THREADS +#define TB_MAX_THREADS 16 +#endif + +// Per-thread +#ifndef TB_TEMPORARY_STORAGE_SIZE +#define TB_TEMPORARY_STORAGE_SIZE (1 << 20) +#endif + +#ifndef TB_MAX_FUNCTIONS +#define TB_MAX_FUNCTIONS (1 << 22) +#endif + +#define TB_HOST_UNKNOWN 0 +#define TB_HOST_X86_64 1 + +// While generating the IR, it's possible to +// perform some optimizations on the spot such +// as CSE and constant folding, if this define +// is 0, the CSE is disabled. +#define TB_FRONTEND_OPT 0 + +// If on, the labels aren't marked in the object file +// might save on performance at the cost of some assembly +// readability. +#define TB_STRIP_LABELS 0 + +#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) +#define TB_HOST_ARCH TB_HOST_X86_64 +#else +#define TB_HOST_ARCH TB_HOST_UNKNOWN +#endif + +#define TB_API extern + +#define TB_NULL_REG ((TB_Register)0) +#define TB_REG_MAX ((TB_Register)INT32_MAX) + +typedef enum TB_ArithmaticBehavior { + // No overflow will assume the value does not + // overflow and if it does this can be considered + // undefined behavior with unknown consequences. + // no signed wrap + TB_ASSUME_NSW, + // no unsigned wrap + TB_ASSUME_NUW, + + // Wrapping will allow the integer to safely wrap. + TB_CAN_WRAP, + + // Overflow check will throw an error if the result + // cannot be represented in the resulting type. + TB_SIGNED_TRAP_ON_WRAP, + TB_UNSIGNED_TRAP_ON_WRAP, + + // Saturated arithmatic will clamp the results in the + // event of overflow/underflow. + TB_SATURATED_UNSIGNED, + TB_SATURATED_SIGNED +} TB_ArithmaticBehavior; + +typedef enum TB_Arch { + TB_ARCH_X86_64, + TB_ARCH_AARCH64 +} TB_Arch; + +typedef enum TB_System { + TB_SYSTEM_WINDOWS, + TB_SYSTEM_LINUX, + + // TODO(NeGate): Actually implement these lol + TB_SYSTEM_MACOS, + TB_SYSTEM_ANDROID +} TB_System; + +typedef enum TB_BranchHint { + TB_BRANCH_HINT_NONE, + TB_BRANCH_HINT_LIKELY, + TB_BRANCH_HINT_UNLIKELY +} TB_BranchHint; + +typedef enum TB_OptLevel { + TB_OPT_O0, + + // DCE + // CSE + // MEM2REG + TB_OPT_O1, + + // DCE + // CSE + // MEM2REG + TB_OPT_SIZE, + + // DCE + // CSE + // MEM2REG + // LOOP_UNROLL + TB_OPT_SPEED, +} TB_OptLevel; + +enum { + TB_VOID, + // Boolean + TB_BOOL, + // Integers + TB_I8, TB_I16, TB_I32, TB_I64, TB_I128, + // IEEE 754 Floating point + TB_F32, TB_F64, + // Pointers + // NOTE(NeGate): consider support for multiple address spaces + TB_PTR, + + TB_MAX_TYPES +}; + +#define TB_IS_INTEGER_TYPE(x) ((x) >= TB_I8 && (x) <= TB_I128) +#define TB_IS_FLOAT_TYPE(x) ((x) >= TB_F32 && (x) <= TB_F64) +#define TB_IS_POINTER_TYPE(x) ((x) == TB_PTR) + +typedef struct TB_DataType { + uint8_t type; + uint8_t count; // 0 is illegal, except on VOID, it doesn't matter there +} TB_DataType; + +typedef struct TB_Int128 { + uint64_t lo; + uint64_t hi; +} TB_Int128; + +typedef struct TB_FeatureConstraints { + int max_vector_width[TB_MAX_TYPES]; +} TB_FeatureConstraints; + +typedef struct TB_FeatureSet { + struct { + bool sse3 : 1; + + bool popcnt : 1; + bool lzcnt : 1; + bool sse41 : 1; + bool sse42 : 1; + + bool clmul : 1; + bool f16c : 1; + + bool bmi1 : 1; + bool bmi2 : 1; + + bool avx : 1; + bool avx2 : 1; + } x64; + struct { + bool bf16 : 1; + } aarch64; +} TB_FeatureSet; + +typedef int TB_Label; + +typedef struct TB_SwitchEntry { + uint32_t key; + TB_Label value; +} TB_SwitchEntry; + +typedef int TB_FileID; +typedef int TB_ExternalID; +typedef struct TB_Module TB_Module; +typedef struct TB_Function TB_Function; +typedef int32_t TB_Register; +typedef struct TB_FunctionOutput TB_FunctionOutput; + +// ******************************* +// Public macros +// ******************************* +#define TB_TYPE_VOID() (TB_DataType){ .type = TB_VOID } + +#define TB_TYPE_I8(c) (TB_DataType){ .type = TB_I8, .count = c } +#define TB_TYPE_I16(c) (TB_DataType){ .type = TB_I16, .count = c } +#define TB_TYPE_I32(c) (TB_DataType){ .type = TB_I32, .count = c } +#define TB_TYPE_I64(c) (TB_DataType){ .type = TB_I64, .count = c } +#define TB_TYPE_I128(c) (TB_DataType){ .type = TB_I128, .count = c } + +#define TB_TYPE_F32(c) (TB_DataType){ .type = TB_F32, .count = c } +#define TB_TYPE_F64(c) (TB_DataType){ .type = TB_F64, .count = c } + +#define TB_TYPE_BOOL(c) (TB_DataType){ .type = TB_BOOL, .count = c } +#define TB_TYPE_PTR() (TB_DataType){ .type = TB_PTR, .count = 1 } + +// ******************************* +// Public functions +// ******************************* +TB_API void tb_get_constraints(TB_Arch target_arch, const TB_FeatureSet* features, TB_FeatureConstraints* constraints); + +TB_API TB_Module* tb_module_create(TB_Arch target_arch, TB_System target_system, const TB_FeatureSet* features, int optimization_level, int max_threads); +TB_API bool tb_module_compile_func(TB_Module* m, TB_Function* f); +TB_API size_t tb_DEBUG_module_get_full_node_count(TB_Module* m); +TB_API void tb_module_destroy(TB_Module* m); + +TB_API bool tb_module_compile(TB_Module* m); +TB_API bool tb_module_export(TB_Module* m, FILE* f); +TB_API void tb_module_export_jit(TB_Module* m); + +TB_API void* tb_module_get_jit_func_by_name(TB_Module* m, const char* name); +TB_API void* tb_module_get_jit_func_by_id(TB_Module* m, size_t i); +TB_API void* tb_module_get_jit_func(TB_Module* m, TB_Function* f); + +TB_API TB_Function* tb_function_create(TB_Module* m, const char* name, TB_DataType return_dt); +TB_API TB_ExternalID tb_module_extern(TB_Module* m, const char* name); +TB_API TB_FileID tb_register_file(TB_Module* m, const char* path); + +TB_API TB_Label tb_get_current_label(TB_Function* f); +TB_API void tb_inst_loc(TB_Function* f, TB_FileID file, int line); + +TB_API TB_Register tb_inst_param(TB_Function* f, TB_DataType dt); +TB_API TB_Register tb_inst_param_addr(TB_Function* f, TB_Register param); + +TB_API TB_Register tb_inst_sxt(TB_Function* f, TB_Register src, TB_DataType dt); +TB_API TB_Register tb_inst_zxt(TB_Function* f, TB_Register src, TB_DataType dt); + +TB_API TB_Register tb_inst_local(TB_Function* f, uint32_t size, uint32_t alignment); +TB_API TB_Register tb_inst_load(TB_Function* f, TB_DataType dt, TB_Register addr, uint32_t alignment); +TB_API void tb_inst_store(TB_Function* f, TB_DataType dt, TB_Register addr, TB_Register val, uint32_t alignment); + +TB_API TB_Register tb_inst_iconst(TB_Function* f, TB_DataType dt, uint64_t imm); +TB_API TB_Register tb_inst_iconst128(TB_Function* f, TB_DataType dt, TB_Int128 imm); + +TB_API TB_Register tb_inst_fconst(TB_Function* f, TB_DataType dt, double imm); + +TB_API TB_Register tb_inst_array_access(TB_Function* f, TB_Register base, TB_Register index, uint32_t stride); +TB_API TB_Register tb_inst_member_access(TB_Function* f, TB_Register base, int32_t offset); +TB_API TB_Register tb_inst_call(TB_Function* f, TB_DataType dt, const TB_Function* target, size_t param_count, const TB_Register* params); +TB_API TB_Register tb_inst_ecall(TB_Function* f, TB_DataType dt, const TB_ExternalID target, size_t param_count, const TB_Register* params); + +TB_API void tb_inst_memset(TB_Function* f, TB_Register dst, TB_Register val, TB_Register size, int align); +TB_API void tb_inst_memcpy(TB_Function* f, TB_Register dst, TB_Register src, TB_Register size, int align); + +TB_API TB_Register tb_inst_not(TB_Function* f, TB_DataType dt, TB_Register n); +TB_API TB_Register tb_inst_neg(TB_Function* f, TB_DataType dt, TB_Register n); + +TB_API TB_Register tb_inst_and(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_or(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_xor(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_add(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b, TB_ArithmaticBehavior arith_behavior); +TB_API TB_Register tb_inst_sub(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b, TB_ArithmaticBehavior arith_behavior); +TB_API TB_Register tb_inst_mul(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b, TB_ArithmaticBehavior arith_behavior); +TB_API TB_Register tb_inst_div(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b, bool signedness); + +TB_API TB_Register tb_inst_shl(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b, TB_ArithmaticBehavior arith_behavior); +TB_API TB_Register tb_inst_sar(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_shr(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_fadd(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_fsub(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_fmul(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_fdiv(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_cmp_eq(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_ne(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_cmp_slt(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_sle(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_sgt(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_sge(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_cmp_ult(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_ule(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_ugt(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_uge(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_cmp_flt(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_fle(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_fgt(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); +TB_API TB_Register tb_inst_cmp_fge(TB_Function* f, TB_DataType dt, TB_Register a, TB_Register b); + +TB_API TB_Register tb_inst_phi2(TB_Function* f, TB_DataType dt, TB_Label a_label, TB_Register a, TB_Label b_label, TB_Register b); +TB_API TB_Register tb_inst_label(TB_Function* f, TB_Label id); +TB_API void tb_inst_goto(TB_Function* f, TB_Label id); +TB_API TB_Register tb_inst_if(TB_Function* f, TB_Register cond, TB_Label if_true, TB_Label if_false); +TB_API void tb_inst_switch(TB_Function* f, TB_DataType dt, TB_Register key, TB_Label default_label, size_t entry_count, const TB_SwitchEntry* entries); +TB_API void tb_inst_ret(TB_Function* f, TB_DataType dt, TB_Register value); + +TB_API void tb_function_print(TB_Function* f); + +#endif /* _TINYBACKEND_H_ */