diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a8cff96a..ea68e1294 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ add_executable(c3c src/compiler/sema_types.c src/compiler/sema_stmts.c src/compiler/number.c + src/compiler/linker.c src/utils/vmem.c src/utils/vmem.h src/utils/whereami.c diff --git a/resources/examples/notworking/vector.c3 b/resources/examples/notworking/vector.c3 index 4e8eadc4b..56ee0c063 100644 --- a/resources/examples/notworking/vector.c3 +++ b/resources/examples/notworking/vector.c3 @@ -2,7 +2,7 @@ module vector(Type); public struct Vector { - Type[*] array; + Type[] array; } public func void Vector.init() diff --git a/resources/testproject/bar.c3 b/resources/testproject/bar.c3 index 5721cc930..3ced5c41f 100644 --- a/resources/testproject/bar.c3 +++ b/resources/testproject/bar.c3 @@ -1,6 +1,6 @@ module bar; -import baz.foo; +import baz::foo; import testbaz; public func void test() diff --git a/resources/testproject/project.toml b/resources/testproject/project.toml index 56c022685..5aa916419 100644 --- a/resources/testproject/project.toml +++ b/resources/testproject/project.toml @@ -7,4 +7,5 @@ warnings = ["no-unused"] # sources compiled sources = ["./**"] # libraries to use -libs = [] \ No newline at end of file +libs = [] +soft-float = true \ No newline at end of file diff --git a/src/build/build_internal.h b/src/build/build_internal.h index d43596fe7..a85a81e26 100644 --- a/src/build/build_internal.h +++ b/src/build/build_internal.h @@ -7,6 +7,7 @@ #include "utils/lib.h" #include "utils/toml.h" #include "build_options.h" +#define DEFAULT_SYMTAB_SIZE (64 * 1024) typedef struct { @@ -14,4 +15,5 @@ typedef struct } Project; Project *project_load(void); -BuildTarget *project_select_target(Project *project, const char *optional_target); \ No newline at end of file +BuildTarget *project_select_target(Project *project, const char *optional_target); + diff --git a/src/build/build_options.c b/src/build/build_options.c index 8651888e8..4e09a6e98 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -11,10 +11,17 @@ #include #include -static const int DEFAULT_SYMTAB_SIZE = 64 * 1024; -static const int MAX_SYMTAB_SIZE = 1024 * 1024; -static char *arch_os_target[ARCH_OS_TARGET_LAST + 1] = { + + + + +static int arg_index; +static int arg_count; +static const char** args; +static const char* current_arg; + +char *arch_os_target[ARCH_OS_TARGET_LAST + 1] = { [X86_FREEBSD] = "x86_freebsd", [X86_OPENBSD] = "x86_openbsd", [X86_MCU] = "x86_mcu", @@ -33,13 +40,6 @@ static char *arch_os_target[ARCH_OS_TARGET_LAST + 1] = { [WASM64] = "wasm64", }; -BuildOptions build_options; -static int arg_index; -static int arg_count; -static const char** args; -static const char* current_arg; - - #define EOUTPUT(string, ...) fprintf(stderr, string "\n", ##__VA_ARGS__) #define OUTPUT(string, ...) fprintf(stdout, string "\n", ##__VA_ARGS__) #define FAIL_WITH_ERR(string, ...) do { fprintf(stderr, "Error: " string "\n\n", ##__VA_ARGS__); usage(); exit(EXIT_FAILURE); } while (0) @@ -139,14 +139,14 @@ static inline bool match_shortopt(const char* name) } -void append_file() +void append_file(BuildOptions *build_options) { - if (vec_size(build_options.files) == MAX_FILES) + if (vec_size(build_options->files) == MAX_FILES) { fprintf(stderr, "Max %d files may be specified\n", MAX_FILES); exit(EXIT_FAILURE); } - build_options.files = VECADD(build_options.files, current_arg); + build_options->files = VECADD(build_options->files, current_arg); } static bool arg_match(const char *candidate) @@ -154,87 +154,86 @@ static bool arg_match(const char *candidate) return strcmp(current_arg, candidate) == 0; } -static void parse_optional_target() +static void parse_optional_target(BuildOptions *options) { if (at_end() || next_is_opt()) { - build_options.target_select = NULL; + options->target_select = NULL; } else { - build_options.target_select = next_arg(); + options->target_select = next_arg(); } } -static void parse_command(void) +static void parse_command(BuildOptions *options) { if (arg_match("init")) { - build_options.command = COMMAND_INIT; + options->command = COMMAND_INIT; if (at_end() || next_is_opt()) error_exit("Expected a project name after init"); - build_options.project_name = next_arg(); + options->project_name = next_arg(); return; } if (arg_match("utest")) { - build_options.command = COMMAND_UNIT_TEST; + options->command = COMMAND_UNIT_TEST; return; } if (arg_match("compile")) { - build_options.command = COMMAND_COMPILE; + options->command = COMMAND_COMPILE; return; } if (arg_match("headers")) { - build_options.command = COMMAND_GENERATE_HEADERS; + options->command = COMMAND_GENERATE_HEADERS; return; } if (arg_match("build")) { - build_options.command = COMMAND_BUILD; - parse_optional_target(); + options->command = COMMAND_BUILD; + parse_optional_target(options); return; } if (arg_match("run")) { - build_options.command = COMMAND_RUN; - parse_optional_target(); + options->command = COMMAND_RUN; + parse_optional_target(options); return; } if (arg_match("compile-run")) { - build_options.command = COMMAND_COMPILE_RUN; - parse_optional_target(); + options->command = COMMAND_COMPILE_RUN; return; } if (arg_match("clean-run")) { - build_options.command = COMMAND_CLEAN_RUN; - parse_optional_target(); + options->command = COMMAND_CLEAN_RUN; + parse_optional_target(options); return; } if (arg_match("clean")) { - build_options.command = COMMAND_CLEAN; + options->command = COMMAND_CLEAN; return; } if (arg_match("dist")) { - build_options.command = COMMAND_CLEAN_RUN; - parse_optional_target(); + options->command = COMMAND_CLEAN_RUN; + parse_optional_target(options); return; } if (arg_match("directives")) { - build_options.command = COMMAND_DOCS; - parse_optional_target(); + options->command = COMMAND_DOCS; + parse_optional_target(options); return; } if (arg_match("bench")) { - build_options.command = COMMAND_BENCH; - parse_optional_target(); + options->command = COMMAND_BENCH; + parse_optional_target(options); return; } FAIL_WITH_ERR("Cannot process the unknown command \"%s\".", current_arg); @@ -242,126 +241,63 @@ static void parse_command(void) static void print_all_targets(void) { OUTPUT("Available targets:"); - for (unsigned i = 0; i <= ARCH_OS_TARGET_LAST; i++) + for (unsigned i = 1; i <= ARCH_OS_TARGET_LAST; i++) { OUTPUT(" %s", arch_os_target[i]); } } -static void parse_option(void) +static void parse_option(BuildOptions *options) { switch (current_arg[1]) { case 'g': if (match_shortopt("gline-tables-only")) { - build_options.debug_info = DEBUG_INFO_LINE_TABLES; + options->debug_info_override = DEBUG_INFO_LINE_TABLES; FATAL_ERROR("Line tables only are currently not available"); } if (match_shortopt("g") || match_shortopt("g1")) { - build_options.debug_info = DEBUG_INFO_FULL; + options->debug_info_override = DEBUG_INFO_FULL; return; } if (match_shortopt("g0")) { - build_options.debug_info = DEBUG_INFO_NONE; + options->debug_info_override = DEBUG_INFO_NONE; return; } FAIL_WITH_ERR("Unknown debug argument -%s.", ¤t_arg[1]); case 'h': break; - case 'f': - if (match_shortopt("freg-struct-return")) - { - build_options.feature.reg_struct_return = true; - return; - } - if (match_shortopt("fpie")) - { - build_options.pie = PIE_SMALL; - return; - } - if (match_shortopt("fPIE")) - { - build_options.pie = PIE_BIG; - return; - } - if (match_shortopt("fno-pie")) - { - build_options.pie = PIE_NONE; - return; - } - if (match_shortopt("fpic")) - { - build_options.pic = PIC_SMALL; - return; - } - if (match_shortopt("fPIC")) - { - build_options.pic = PIC_BIG; - return; - } - if (match_shortopt("fno-pic")) - { - build_options.pic = PIC_NONE; - return; - } - if (match_shortopt("fpcc-struct-return")) - { - build_options.feature.stack_struct_return = true; - return; - } - if (match_shortopt("fno-memcpy-pass")) - { - build_options.feature.no_memcpy_pass = true; - return; - } - FAIL_WITH_ERR("Unknown argument -%s.", ¤t_arg[1]); - case 'm': - if (match_shortopt("msoft-float")) - { - build_options.feature.soft_float = true; - build_options.feature.no_soft_float = false; - return; - } - if (match_shortopt("mno-soft-float")) - { - build_options.feature.soft_float = true; - build_options.feature.no_soft_float = false; - return; - } - FAIL_WITH_ERR("Cannot process the unknown command \"%s\".", current_arg); case 'O': - if (build_options.optimization_level != OPTIMIZATION_NOT_SET) + if (options->optimization_setting_override != OPT_SETTING_NOT_SET) { FAIL_WITH_ERR("Multiple optimization levels were set."); } if (match_shortopt("O0")) { - build_options.optimization_level = OPTIMIZATION_NONE; + options->optimization_setting_override = OPT_SETTING_O0; } else if (match_shortopt("O1")) { - build_options.optimization_level = OPTIMIZATION_LESS; + options->optimization_setting_override = OPT_SETTING_O1; } else if (match_shortopt("O2")) { - build_options.optimization_level = OPTIMIZATION_DEFAULT; + options->optimization_setting_override = OPT_SETTING_O2; + } + else if (match_shortopt("O2")) + { + options->optimization_setting_override = OPT_SETTING_O3; } else if (match_shortopt("Os")) { - build_options.optimization_level = OPTIMIZATION_DEFAULT; - build_options.size_optimization_level = SIZE_OPTIMIZATION_SMALL; + options->optimization_setting_override = OPT_SETTING_OSMALL; } else if (match_shortopt("Oz")) { - build_options.optimization_level = OPTIMIZATION_DEFAULT; - build_options.size_optimization_level = SIZE_OPTIMIZATION_TINY; - } - else if (match_shortopt("O3")) - { - build_options.optimization_level = OPTIMIZATION_AGGRESSIVE; + options->optimization_setting_override = OPT_SETTING_OTINY; } else { @@ -369,18 +305,18 @@ static void parse_option(void) } return; case 'E': - if (build_options.compile_option != COMPILE_NORMAL) + if (options->compile_option != COMPILE_NORMAL) { FAIL_WITH_ERR("Illegal combination of compile options."); } - build_options.compile_option = COMPILE_LEX_ONLY; + options->compile_option = COMPILE_LEX_ONLY; return; case 'P': - if (build_options.compile_option != COMPILE_NORMAL) + if (options->compile_option != COMPILE_NORMAL) { FAIL_WITH_ERR("Illegal combination of compile options."); } - build_options.compile_option = COMPILE_LEX_PARSE_ONLY; + options->compile_option = COMPILE_LEX_PARSE_ONLY; return; case '-': if (match_longopt("about")) @@ -393,13 +329,11 @@ static void parse_option(void) { if (at_end() || next_is_opt()) error_exit("error: --target needs a arch+os definition."); const char *target = next_arg(); - for (unsigned i = 1; i <= ARCH_OS_TARGET_LAST; i++) + ArchOsTarget target_arch_os = arch_os_target_from_string(target); + if (target_arch_os >= 0) { - if (strcasecmp(arch_os_target[i], target) == 0) - { - build_options.arch_os_target = i; - return; - } + options->arch_os_target_override = target_arch_os; + return; } OUTPUT("Available targets:"); EOUTPUT("Invalid target %s.", target); @@ -422,35 +356,25 @@ static void parse_option(void) } if (match_longopt("emit-llvm")) { - build_options.emit_llvm = true; + options->emit_llvm = true; return; } if (match_longopt("lib")) { if (at_end() || next_is_opt()) error_exit("error: --lib needs a directory."); - if (build_options.lib_count == MAX_LIB_DIRS) error_exit("Max %d libraries may be specified.", MAX_LIB_DIRS); - build_options.lib_dir[build_options.lib_count++] = check_dir(next_arg()); + if (options->lib_count == MAX_LIB_DIRS) error_exit("Max %d libraries may be specified.", MAX_LIB_DIRS); + options->lib_dir[options->lib_count++] = check_dir(next_arg()); return; } if (match_longopt("test")) { - build_options.test_mode = true; + options->test_mode = true; return; } if (match_longopt("path")) { if (at_end() || next_is_opt()) error_exit("error: --path needs a directory."); - build_options.path = check_dir(next_arg()); - return; - } - if (match_longopt("symtab")) - { - if (at_end() || next_is_opt()) error_exit("error: --symtab needs a number."); - const char *number = next_arg(); - int size = atoi(number); // NOLINT(cert-err34-c) - if (size < 1024) error_exit("error: --symtab valid size > 1024."); - if (size > MAX_SYMTAB_SIZE) error_exit("error: --symptab size cannot exceed %d", MAX_SYMTAB_SIZE); - build_options.symtab_size = size; + options->path = check_dir(next_arg()); return; } if (match_longopt("help")) @@ -466,7 +390,7 @@ static void parse_option(void) } -void parse_arguments(int argc, const char *argv[]) +BuildOptions parse_arguments(int argc, const char *argv[]) { arg_count = argc; args = argv; @@ -477,16 +401,16 @@ void parse_arguments(int argc, const char *argv[]) exit(EXIT_SUCCESS); } - build_options.path = "."; - build_options.emit_llvm = false; - build_options.emit_bitcode = true; - build_options.optimization_level = OPTIMIZATION_NOT_SET; - build_options.size_optimization_level = SIZE_OPTIMIZATION_NOT_SET; - build_options.debug_info = DEBUG_INFO_NONE; - build_options.debug_mode = false; - build_options.command = COMMAND_MISSING; - build_options.symtab_size = DEFAULT_SYMTAB_SIZE; - build_options.files = NULL; + BuildOptions build_options = { + .path = ".", + .emit_llvm = false, + .emit_bitcode = true, + .optimization_setting_override = OPT_SETTING_NOT_SET, + .debug_info_override = DEBUG_INFO_NOT_SET, + .debug_mode = false, + .command = COMMAND_MISSING, + .files = NULL + }; for (int i = DIAG_NONE; i < DIAG_WARNING_TYPE; i++) { build_options.severity[i] = DIAG_IGNORE; @@ -505,19 +429,19 @@ void parse_arguments(int argc, const char *argv[]) current_arg = args[arg_index]; if (current_arg[0] == '-') { - parse_option(); + parse_option(&build_options); continue; } if (build_options.command == COMMAND_MISSING) { - parse_command(); + parse_command(&build_options); continue; } if (build_options.command == COMMAND_COMPILE_RUN || build_options.command == COMMAND_COMPILE || build_options.command == COMMAND_GENERATE_HEADERS) { - append_file(); + append_file(&build_options); continue; } FAIL_WITH_ERR("Found the unexpected argument \"%s\".", current_arg); @@ -526,13 +450,18 @@ void parse_arguments(int argc, const char *argv[]) { FAIL_WITH_ERR("No command found."); } - if (build_options.optimization_level == OPTIMIZATION_NOT_SET) - { - build_options.optimization_level = OPTIMIZATION_DEFAULT; - } - if (build_options.size_optimization_level == SIZE_OPTIMIZATION_NOT_SET) - { - build_options.size_optimization_level = SIZE_OPTIMIZATION_NONE; - } - + return build_options; } + + +ArchOsTarget arch_os_target_from_string(const char *target) +{ + for (unsigned i = 1; i <= ARCH_OS_TARGET_LAST; i++) + { + if (strcasecmp(arch_os_target[i], target) == 0) + { + return i; + } + } + return -1; +} \ No newline at end of file diff --git a/src/build/build_options.h b/src/build/build_options.h index c8ab93a72..1e8f0e940 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -71,6 +71,17 @@ typedef enum COMPILE_OUTPUT_AST, } CompileOption; +typedef enum +{ + OPT_SETTING_NOT_SET = -1, + OPT_SETTING_O0 = 0, + OPT_SETTING_O1 = 1, + OPT_SETTING_O2 = 2, + OPT_SETTING_O3 = 3, + OPT_SETTING_OSMALL = 4, + OPT_SETTING_OTINY = 5, +} OptimizationSetting; + typedef enum { OPTIMIZATION_NOT_SET = -1, @@ -100,14 +111,29 @@ typedef enum // Values correspond to LLVM values typedef enum { - PIE_NONE = -1, - PIE_DEFAULT = 0, + PIE_DEFAULT = -1, + PIE_NONE = 0, PIE_SMALL = 1, PIE_BIG = 2, } PieGeneration; typedef enum { + SOFT_FLOAT_DEFAULT = -1, + SOFT_FLOAT_NONE = 0, + SOFT_FLOAT_YES = 1 +} SoftFloat; + +typedef enum +{ + STRUCT_RETURN_DEFAULT = -1, + STRUCT_RETURN_STACK = 0, + STRUCT_RETURN_REG = 1 +} StructReturn; + +typedef enum +{ + DEBUG_INFO_NOT_SET = -1, DEBUG_INFO_NONE, DEBUG_INFO_LINE_TABLES, DEBUG_INFO_FULL @@ -135,46 +161,31 @@ typedef enum ARCH_OS_TARGET_LAST = WASM64 } ArchOsTarget; -typedef struct +typedef struct BuildOptions_ { const char* lib_dir[MAX_LIB_DIRS]; int lib_count; const char** files; const char* project_name; - ArchOsTarget arch_os_target; const char* target_select; const char* path; - const char* cpu; - const char* target_triple; - PicGeneration pic; - PieGeneration pie; - bool generate_lib; - struct - { - bool reg_struct_return : 1; - bool stack_struct_return : 1; - bool no_memcpy_pass : 1; - bool soft_float : 1; - bool no_soft_float : 1; - } feature; unsigned version; CompilerCommand command; - uint32_t symtab_size; CompileOption compile_option; DiagnosticsSeverity severity[DIAG_END_SENTINEL]; - OptimizationLevel optimization_level; - SizeOptimizationLevel size_optimization_level; - DebugInfo debug_info; + OptimizationSetting optimization_setting_override; + DebugInfo debug_info_override; + ArchOsTarget arch_os_target_override; bool debug_mode; bool emit_llvm; bool emit_bitcode; bool test_mode; - bool trap_wrapping; } BuildOptions; + typedef enum { TARGET_TYPE_EXECUTABLE, @@ -191,9 +202,32 @@ typedef struct const char *langrev; const char **sources; const char **libraries; - const char *target_triple; + const char *cpu; + bool run_after_compile : 1; + bool test_output : 1; + bool output_headers : 1; + bool output_ast : 1; + bool lex_only : 1; + bool parse_only : 1; + bool emit_llvm : 1; + bool emit_object_files : 1; + OptimizationLevel optimization_level; + SizeOptimizationLevel size_optimization_level; + DebugInfo debug_info; + PieGeneration pie; + PicGeneration pic; + ArchOsTarget arch_os_target; + uint32_t symtab_size; + struct + { + SoftFloat soft_float : 3; + StructReturn struct_return : 3; + bool no_memcpy_pass : 1; + bool trap_on_wrap : 1; + bool safe_mode : 1; + } feature; } BuildTarget; -extern BuildOptions build_options; -void parse_arguments(int argc, const char *argv[]); +BuildOptions parse_arguments(int argc, const char *argv[]); +ArchOsTarget arch_os_target_from_string(const char *target); \ No newline at end of file diff --git a/src/build/builder.c b/src/build/builder.c index 5301b1c90..1d13febaf 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -6,23 +6,144 @@ #include "build_internal.h" #include "build_options.h" + void load_library_files(void) {} void load_files(void) {} void compile_files(BuildTarget *target); -void build(void) +void build(const char *optional_target) { // Locate the project.toml file_find_top_dir(); // Parse it Project *project = project_load(); - BuildTarget *target = project_select_target(project, build_options.target_select); - - if (!target->target_triple && build_options.target_triple) - { - target->target_triple = build_options.target_triple; - } + BuildTarget *target = project_select_target(project, optional_target); load_library_files(); compile_files(target); +} + +static void update_build_target_from_options(BuildTarget *target, BuildOptions *options) +{ + switch (options->command) + { + case COMMAND_RUN: + case COMMAND_COMPILE_RUN: + case COMMAND_CLEAN_RUN: + target->run_after_compile = true; + break; + default: + target->run_after_compile = false; + break; + } + + switch (options->command) + { + case COMMAND_BUILD: + target->output_headers = target->type == TARGET_TYPE_DYNAMIC_LIB || target->type == TARGET_TYPE_STATIC_LIB; + break; + case COMMAND_GENERATE_HEADERS: + target->output_headers = true; + break; + default: + target->output_headers = false; + break; + } + + // Copy optimization levels. + switch (options->optimization_setting_override) + { + case OPT_SETTING_O0: + target->optimization_level = OPTIMIZATION_NONE; + target->size_optimization_level = SIZE_OPTIMIZATION_NONE; + break; + case OPT_SETTING_O1: + target->optimization_level = OPTIMIZATION_LESS; + target->size_optimization_level = SIZE_OPTIMIZATION_NONE; + break; + case OPT_SETTING_O2: + target->optimization_level = OPTIMIZATION_DEFAULT; + target->size_optimization_level = SIZE_OPTIMIZATION_NONE; + break; + case OPT_SETTING_O3: + target->optimization_level = OPTIMIZATION_AGGRESSIVE; + target->size_optimization_level = SIZE_OPTIMIZATION_NONE; + break; + case OPT_SETTING_OSMALL: + target->optimization_level = OPTIMIZATION_DEFAULT; + target->size_optimization_level = SIZE_OPTIMIZATION_SMALL; + break; + case OPT_SETTING_OTINY: + target->optimization_level = OPTIMIZATION_DEFAULT; + target->size_optimization_level = SIZE_OPTIMIZATION_TINY; + break; + case OPT_SETTING_NOT_SET: + break; + default: + UNREACHABLE + } + if (options->debug_info_override != DEBUG_INFO_NOT_SET) + { + target->debug_info = options->debug_info_override; + } + if (options->arch_os_target_override != ARCH_OS_TARGET_DEFAULT) + { + target->arch_os_target = options->arch_os_target_override; + } + target->emit_llvm = options->emit_llvm; + switch (options->compile_option) + { + case COMPILE_NORMAL: + target->emit_object_files = true; + break; + case COMPILE_LEX_ONLY: + target->lex_only = true; + break; + case COMPILE_LEX_PARSE_ONLY: + target->parse_only = true; + break; + case COMPILE_OUTPUT_HEADERS: + target->output_headers = true; + target->run_after_compile = false; + target->emit_object_files = false; + break; + case COMPILE_OUTPUT_AST: + target->parse_only = true; + target->output_ast = true; + break; + } + if (options->test_mode) + { + target->test_output = true; + target->emit_llvm = false; + target->emit_object_files = false; + } +} + +void init_default_build_target(BuildTarget *target, BuildOptions *options, const char *name) +{ + *target = (BuildTarget) { + .type = TARGET_TYPE_EXECUTABLE, + .sources = options->files, + .name = name, + .optimization_level = OPTIMIZATION_DEFAULT, + .size_optimization_level = SIZE_OPTIMIZATION_NONE, + .symtab_size = DEFAULT_SYMTAB_SIZE, + .debug_info = DEBUG_INFO_NONE, + .arch_os_target = ARCH_OS_TARGET_DEFAULT + }; + update_build_target_from_options(target, options); +} + +void init_build_target(BuildTarget *target, BuildOptions *options) +{ + // Locate the project.toml + file_find_top_dir(); + // Parse it + Project *project = project_load(); + *target = *project_select_target(project, options->target_select); + + update_build_target_from_options(target, options); + + load_library_files(); } \ No newline at end of file diff --git a/src/build/project.c b/src/build/project.c index b52335016..92ba58e14 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -4,6 +4,7 @@ #include #include "build_internal.h" +#define MAX_SYMTAB_SIZE (1024 * 1024) TomlArray *get_array(TomlTable *table, const char *key) { @@ -43,6 +44,55 @@ const char *get_valid_string(TomlTable *table, const char *key, const char *cate return copy_toml_string(value->value.string); } +int get_valid_bool(TomlTable *table, const char *key, const char *category, int default_val) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) return default_val; + if (value->type != TOML_BOOLEAN) + { + error_exit("%s had an invalid mandatory '%s' field that was not a boolean, please correct it.", category, key); + } + return value->value.boolean; +} + + +static int get_valid_string_setting(TomlTable *table, const char *key, const char *category, const char** values, int first_result, int count, const char *expected) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) + { + return -1; + } + if (value->type == TOML_STRING) + { + for (int i = 0; i < count; i++) + { + unsigned str_len = strlen(values[i]); + if (str_len != value->value.string->len) continue; + if (memcmp(values[i], value->value.string->str, str_len) == 0) return i + first_result; + } + } + error_exit("%s had an invalid value for '%s', expected %s", category, key, expected); +} + +long get_valid_integer(TomlTable *table, const char *key, const char *category, bool mandatory) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) + { + if (mandatory) + { + error_exit("%s was missing a mandatory '%s' field, please add it.", category, key); + } + return -1; + } + if (value->type != TOML_INTEGER) + { + error_exit("%s had an invalid mandatory '%s' field that was not an integer, please correct it.", category, key); + } + return value->value.integer; +} + static const char **get_valid_array(TomlTable *table, const char *key, const char *category, bool mandatory) { @@ -79,6 +129,12 @@ void project_add_target(Project *project, TomlValue *wrapped_table, const char * error_exit("The %s had an invalid %s. Please check your [[%s]] configurations.", PROJECT_TOML, type, type_key); } BuildTarget *target = CALLOCS(BuildTarget); + target->optimization_level = OPTIMIZATION_DEFAULT; + target->size_optimization_level = SIZE_OPTIMIZATION_NONE; + target->arch_os_target = ARCH_OS_TARGET_DEFAULT; + target->debug_info = DEBUG_INFO_NONE; + target->symtab_size = DEFAULT_SYMTAB_SIZE; + vec_add(project->targets, target); TomlTable *table = wrapped_table->value.table; @@ -98,6 +154,50 @@ void project_add_target(Project *project, TomlValue *wrapped_table, const char * target->langrev = get_valid_string(table, "langrev", type, false); target->sources = get_valid_array(table, "sources", type, true); target->libraries = get_valid_array(table, "libs", type, false); + static const char *debug_infos[3] = { + [DEBUG_INFO_FULL] = "full", + [DEBUG_INFO_NONE] = "none", + [DEBUG_INFO_LINE_TABLES] = "line-tables" + }; + DebugInfo info = get_valid_string_setting(table, "debug-info", type, debug_infos, 0, 3, "one of 'full' 'line-table' or 'none'."); + if (info > -1) target->debug_info = info; + const char *arch_os_string = get_valid_string(table, "target", type, false); + if (arch_os_string) + { + ArchOsTarget arch_os = arch_os_target_from_string(arch_os_string); + if (arch_os < 0) error_exit("Error reading %s: %s target was not valid.", PROJECT_TOML, type); + target->arch_os_target = arch_os; + } + long symtab_size = get_valid_integer(table, "symtab", type, false); + if (symtab_size > 0) + { + if (symtab_size < 1024) + { + error_exit("Error reading %s: %s symtab was less than 1024.", PROJECT_TOML, type); + } + if (symtab_size > MAX_SYMTAB_SIZE) + { + error_exit("Error reading %s: %s symtab may not exceed %d.", PROJECT_TOML, type, MAX_SYMTAB_SIZE); + } + target->symtab_size = symtab_size; + } + const char *cpu = get_valid_string(table, "cpu", type, false); + target->cpu = cpu ? cpu : "generic"; + + static const char *pies[3] = { + [PIE_SMALL] = "yes-limited", + [PIE_NONE] = "no", + [PIE_BIG] = "yes-unlimited" + }; + target->pie = get_valid_string_setting(table, "pie", type, pies, 0, 3, "'yes-limited', 'yes-unlimited' or 'no'."); + target->pic = get_valid_string_setting(table, "pic", type, pies, 0, 3, "'yes-limited', 'yes-unlimited' or 'no'."); + + target->pic = PIC_DEFAULT; + target->feature.no_memcpy_pass = get_valid_bool(table, "no-memcpy-pass", type, false); + target->feature.trap_on_wrap = get_valid_bool(table, "trap-on-wrap", type, false); + // Use the fact that they correspond to 0, 1, -1 + target->feature.struct_return = get_valid_bool(table, "stack-struct-return", type, STRUCT_RETURN_DEFAULT); + target->feature.soft_float = get_valid_bool(table, "soft-float", type, SOFT_FLOAT_DEFAULT); } static void project_add_targets(Project *project, TomlTable *table, const char *type, const char *type_key, TargetType target_type) diff --git a/src/build/project_creation.c b/src/build/project_creation.c index db32d05fa..7c81f8b37 100644 --- a/src/build/project_creation.c +++ b/src/build/project_creation.c @@ -29,33 +29,33 @@ const char* TOML = "# libraries to use\n" "libs = [\"lib/**\"]\n"; -void create_project(void) +void create_project(BuildOptions *build_options) { for (int i = 0; ; i++) { - char c = build_options.project_name[i]; + char c = build_options->project_name[i]; if (c == '\0') break; if (!is_alphanum_(c)) { - fprintf(stderr, "'%s' is not a valid project name.\n", build_options.project_name); + fprintf(stderr, "'%s' is not a valid project name.\n", build_options->project_name); exit(EXIT_FAILURE); } } - if (chdir(build_options.path)) + if (chdir(build_options->path)) { - fprintf(stderr, "Can't open path %s\n", build_options.path); + fprintf(stderr, "Can't open path %s\n", build_options->path); exit(EXIT_FAILURE); } - int error = mkdir(build_options.project_name, 0755); + int error = mkdir(build_options->project_name, 0755); if (error) { - fprintf(stderr, "Could not create directory %s: %s\n", build_options.project_name, strerror(errno)); + fprintf(stderr, "Could not create directory %s: %s\n", build_options->project_name, strerror(errno)); exit(EXIT_FAILURE); } - if (chdir(build_options.project_name)) goto ERROR; + if (chdir(build_options->project_name)) goto ERROR; FILE *file = fopen("LICENCE", "a"); if (!file) goto ERROR; @@ -67,7 +67,7 @@ void create_project(void) file = fopen("project.toml", "a"); if (!file) goto ERROR; - fprintf(file, TOML, build_options.project_name); + fprintf(file, TOML, build_options->project_name); fclose(file); if (mkdir("lib", 0755)) goto ERROR; @@ -80,7 +80,7 @@ void create_project(void) chdir("test"); - if (mkdir(build_options.project_name, 0755)) goto ERROR; + if (mkdir(build_options->project_name, 0755)) goto ERROR; chdir(".."); @@ -106,9 +106,9 @@ void create_project(void) chdir("src"); - if (mkdir(build_options.project_name, 0755)) goto ERROR; + if (mkdir(build_options->project_name, 0755)) goto ERROR; - chdir(build_options.project_name); + chdir(build_options->project_name); file = fopen("main.c3", "a"); if (!file) goto ERROR; @@ -116,14 +116,14 @@ void create_project(void) chdir("../.."); - printf("Project '%s' created.\n", build_options.project_name); + printf("Project '%s' created.\n", build_options->project_name); exit(EXIT_SUCCESS); ERROR: fprintf(stderr, "Err: %s\n", strerror(errno)); printf("Something went wrong creating the project."); - chdir(build_options.path); - rmdir(build_options.project_name); + chdir(build_options->path); + rmdir(build_options->project_name); exit(EXIT_FAILURE); } diff --git a/src/build/project_creation.h b/src/build/project_creation.h index 5afcd5337..0854a319f 100644 --- a/src/build/project_creation.h +++ b/src/build/project_creation.h @@ -4,5 +4,5 @@ // Use of this source code is governed by the GNU LGPLv3.0 license // a copy of which can be found in the LICENSE file. - -void create_project(void); \ No newline at end of file +struct BuildOptions_; +void create_project(struct BuildOptions_ *build_options); \ No newline at end of file diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 1391d9122..7fe0cfb40 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -518,7 +518,8 @@ void fprint_expr_recursive(Context *context, FILE *file, Expr *expr, int indent) DUMP("(enumconstant)"); return; case EXPR_TYPEINFO: - TODO; + DUMP("(typeinfo)"); + return; case EXPR_SLICE_ASSIGN: DUMP("(sliceassign"); DUMPEXPC(expr); diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index e22a1e389..8249fbcba 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -4,6 +4,7 @@ #include "compiler_internal.h" #include "../build/build_options.h" +#include Compiler compiler; @@ -64,7 +65,7 @@ void compiler_parse(BuildTarget *target) bool loaded = false; File *file = source_file_load(target->sources[i], &loaded); if (loaded) continue; - diag_reset(); + diag_setup(target->test_output); Context *context = context_create(file, target); parse_file(context); context_print_ast(context, stdout); @@ -75,7 +76,7 @@ void compiler_parse(BuildTarget *target) void compiler_compile(BuildTarget *target) { Context **contexts = NULL; - diag_reset(); + diag_setup(target->test_output); if (compiler.lib_dir) { vec_add(target->sources, strformat("%s/std/builtin.c3", compiler.lib_dir)); @@ -131,7 +132,7 @@ void compiler_compile(BuildTarget *target) } if (diagnostics.errors > 0) exit(EXIT_FAILURE); - if (build_options.command == COMMAND_GENERATE_HEADERS) + if (target->output_headers) { for (unsigned i = 0; i < source_count; i++) { @@ -144,7 +145,7 @@ void compiler_compile(BuildTarget *target) llvm_codegen_setup(); - void **gen_contexts = malloc(source_count * sizeof(void*)); + void **gen_contexts = malloc(source_count * sizeof(void *)); for (unsigned i = 0; i < source_count; i++) { Context *context = contexts[i]; @@ -169,13 +170,30 @@ void compiler_compile(BuildTarget *target) print_arena_status(); - free_arena(); + + bool create_exe = !target->test_output && (target->type == TARGET_TYPE_EXECUTABLE || target->type == TARGET_TYPE_TEST); +#if PLATFORM_WINDOWS + create_exe = false; +#endif + const char **obj_files = NULL; for (unsigned i = 0; i < source_count; i++) { - llvm_codegen(gen_contexts[i]); + const char *file_name = llvm_codegen(gen_contexts[i]); + assert(file_name || !create_exe); + vec_add(obj_files, file_name); } + if (create_exe) + { + linker(target->name, obj_files, source_count); + if (target->run_after_compile) + { + system(strformat("./%s", target->name)); + } + } + + free_arena(); exit(EXIT_SUCCESS); } @@ -220,30 +238,23 @@ static void target_expand_source_names(BuildTarget *target) void compile_files(BuildTarget *target) { - if (!target) - { - target = CALLOCS(BuildTarget); - target->type = TARGET_TYPE_EXECUTABLE; - target->sources = build_options.files; - target->name = "a.out"; - } + symtab_init(target->symtab_size ? target->symtab_size : 64 * 1024); + assert(target); target_expand_source_names(target); - target_setup(); + target_setup(target); if (!vec_size(target->sources)) error_exit("No files to compile."); - switch (build_options.compile_option) + if (target->lex_only) { - case COMPILE_LEX_ONLY: - compiler_lex(target); - break; - case COMPILE_LEX_PARSE_ONLY: - compiler_parse(target); - break; - case COMPILE_OUTPUT_HEADERS: - default: - compiler_compile(target); - break; + compiler_lex(target); + return; } + if (target->parse_only) + { + compiler_parse(target); + return; + } + compiler_compile(target); } @@ -258,6 +269,7 @@ void compiler_add_type(Type *type) assert(type_ok(type)); VECADD(compiler.type, type); } + Module *compiler_find_or_create_module(Path *module_name) { Module *module = stable_get(&compiler.modules, module_name->module); diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h index 251347f84..11b22b07a 100644 --- a/src/compiler/compiler.h +++ b/src/compiler/compiler.h @@ -8,6 +8,7 @@ void compiler_init(); void compile_files(BuildTarget *target); -void build(); +void init_build_target(BuildTarget *build_target, BuildOptions *build_options); +void init_default_build_target(BuildTarget *target, BuildOptions *options, const char *name); void symtab_init(uint32_t max_size); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 415c92f36..e5a09ca55 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -24,6 +24,11 @@ typedef int32_t AlignSize; typedef int32_t ScopeId; +#if PLATFORM_WINDOWS +#define DEFAULT_EXE "a.exe" +#else +#define DEFAULT_EXE "a.out" +#endif typedef uint32_t SourceLoc; typedef struct @@ -136,6 +141,7 @@ typedef struct _Diagnostics bool panic_mode; unsigned errors; unsigned warnings; + bool test_mode; } Diagnostics; typedef struct @@ -209,6 +215,7 @@ struct _Type const char *name; Type **type_cache; void *backend_type; + void *backend_aux_type; void *backend_typeid; void *backend_debug_type; union @@ -561,7 +568,7 @@ typedef struct typedef struct { - bool is_struct_function : 1; + bool is_type_method : 1; bool is_pointer_call : 1; Expr *function; Expr **arguments; @@ -762,7 +769,6 @@ struct _Expr bool failable : 1; bool pure : 1; bool constant : 1; - bool reeval : 1; SourceSpan span; Type *type; Type *original_type; @@ -869,9 +875,11 @@ typedef struct typedef struct { bool is_type; + bool is_type_list; union { TypeInfo *type_info; + TypeInfo **type_infos; Expr *expr; }; Ast *body; @@ -1206,6 +1214,7 @@ typedef struct _Context Decl **generic_defines; Decl **functions; Decl **macros; + Decl **generics; Decl **methods; Decl **vars; Decl **incr_array; @@ -1233,7 +1242,6 @@ typedef struct _Context // Reusable returns cache. Ast **returns_cache; }; - Decl *evaluating_macro; Type *rtype; bool failable_return; int in_volatile_section; @@ -1374,7 +1382,7 @@ extern Type *type_u128, *type_i128; extern Type *type_compint, *type_compfloat; extern Type *type_c_short, *type_c_int, *type_c_long, *type_c_longlong; extern Type *type_c_ushort, *type_c_uint, *type_c_ulong, *type_c_ulonglong; -extern Type *type_typeid, *type_error, *type_typeinfo; +extern Type *type_typeid, *type_error, *type_typeinfo, *type_varheader; extern const char *attribute_list[NUMBER_OF_ATTRIBUTES]; @@ -1452,7 +1460,7 @@ static inline bool builtin_may_negate(Type *canonical) bool cast_implicit(Expr *expr, Type *to_type); bool cast(Expr *expr, Type *to_type); -bool cast_to_bool_implicit(Expr *expr); + bool cast_may_implicit(Type *from_type, Type *to_type); bool cast_may_explicit(Type *from_type, Type *to_type); bool cast_implicit_bit_width(Expr *expr, Type *to_type); @@ -1461,7 +1469,7 @@ CastKind cast_to_bool_kind(Type *type); bool cast_implicitly_to_runtime(Expr *expr); -void llvm_codegen(void *module); +const char *llvm_codegen(void *context); void *llvm_gen(Context *context); void llvm_codegen_setup(); @@ -1498,7 +1506,7 @@ static inline DeclKind decl_from_token(TokenType type); #pragma mark --- Diag functions -void diag_reset(void); +void diag_setup(bool test_output); void diag_verror_range(SourceLocation *location, const char *message, va_list args); @@ -1638,7 +1646,7 @@ void stable_clear(STable *table); const char *symtab_add(const char *symbol, uint32_t len, uint32_t fnv1hash, TokenType *type); -void target_setup(void); +void target_setup(BuildTarget *build_target); int target_alloca_addr_space(); void *target_data_layout(); void *target_machine(); @@ -1992,6 +2000,8 @@ static inline size_t type_min_alignment(size_t a, size_t b) return (a | b) & (1 + ~(a | b)); } +void linker(const char *output_file, const char **files, unsigned file_count); + #define TRY_AST_OR(_ast_stmt, _res) ({ Ast* _ast = (_ast_stmt); if (!ast_ok(_ast)) return _res; _ast; }) #define TRY_EXPR_OR(_expr_stmt, _res) ({ Expr* _expr = (_expr_stmt); if (!expr_ok(_expr)) return _res; _expr; }) #define TRY_TYPE_OR(_type_stmt, _res) ({ TypeInfo* _type = (_type_stmt); if (!type_info_ok(_type)) return _res; _type; }) diff --git a/src/compiler/context.c b/src/compiler/context.c index f56d373a3..65d16db4f 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -94,7 +94,10 @@ void context_register_global_decl(Context *context, Decl *decl) switch (decl->decl_kind) { case DECL_POISONED: + break; case DECL_GENERIC: + vec_add(context->generics, decl); + decl_set_external_name(decl); break; case DECL_MACRO: vec_add(context->macros, decl); diff --git a/src/compiler/diagnostics.c b/src/compiler/diagnostics.c index 3919950da..d516b8710 100644 --- a/src/compiler/diagnostics.c +++ b/src/compiler/diagnostics.c @@ -7,11 +7,12 @@ Diagnostics diagnostics; -void diag_reset(void) +void diag_setup(bool test_output) { diagnostics.panic_mode = false; diagnostics.errors = 0; diagnostics.warnings = 0; + diagnostics.test_mode = test_output; } typedef enum @@ -23,7 +24,7 @@ typedef enum static void print_error2(SourceLocation *location, const char *message, PrintType print_type) { - if (build_options.test_mode) + if (diagnostics.test_mode) { switch (print_type) { diff --git a/src/compiler/headers.c b/src/compiler/headers.c index 79646660e..6dcebb3b4 100644 --- a/src/compiler/headers.c +++ b/src/compiler/headers.c @@ -52,7 +52,8 @@ static void header_print_type(FILE *file, Type *type) OUTPUT("int64_t"); return; case TYPE_I128: - TODO + OUTPUT("__int128"); + return; case TYPE_U8: OUTPUT("uint8_t"); return; @@ -66,9 +67,11 @@ static void header_print_type(FILE *file, Type *type) OUTPUT("uint64_t"); return; case TYPE_U128: - TODO + OUTPUT("unsigned __int128"); + return; case TYPE_F16: - TODO + OUTPUT("__fp16"); + return; case TYPE_F32: OUTPUT("float"); return; @@ -77,7 +80,8 @@ static void header_print_type(FILE *file, Type *type) OUTPUT("double"); return; case TYPE_F128: - TODO + OUTPUT("__float128"); + return; case TYPE_TYPEID: OUTPUT("c3typeid_t"); return; diff --git a/src/compiler/linker.c b/src/compiler/linker.c new file mode 100644 index 000000000..46b1afc22 --- /dev/null +++ b/src/compiler/linker.c @@ -0,0 +1,31 @@ +#include "compiler_internal.h" + +#if PLATFORM_WINDOWS +static void link_exe(const char *output_file, const char **files_to_link, unsigned file_count) +{ + TODO +} +#else +static void link_exe(const char *output_file, const char **files_to_link, unsigned file_count) +{ + char *result = NULL; + asprintf(&result, "cc -o %s ", output_file); + + for (unsigned i = 0; i < file_count; i++) + { + char *new_res = NULL; + asprintf(&new_res, "%s %s", result, files_to_link[i]); + free(result); + result = new_res; + } + if (system(result) != EXIT_SUCCESS) + { + error_exit("Failed to create an executable."); + } +} +#endif + +void linker(const char *output_file, const char **files, unsigned file_count) +{ + link_exe(output_file, files, file_count); +} diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index c899c6701..f6744d216 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -6,7 +6,6 @@ -static int get_inlining_threshold(void); static void diagnostics_handler(LLVMDiagnosticInfoRef ref, void *context) { char *message = LLVMGetDiagInfoDescription(ref); @@ -16,7 +15,6 @@ static void diagnostics_handler(LLVMDiagnosticInfoRef ref, void *context) { case LLVMDSError: error_exit("LLVM error generating code for %s: %s", ((GenContext *)context)->ast_context->module->name, message); - break; case LLVMDSWarning: severerity_name = "warning"; break; @@ -35,6 +33,7 @@ static void gencontext_init(GenContext *context, Context *ast_context) { memset(context, 0, sizeof(GenContext)); context->context = LLVMContextCreate(); + context->build_target = ast_context->target; context->bool_type = LLVMInt1TypeInContext(context->context); context->byte_type = LLVMInt8TypeInContext(context->context); LLVMContextSetDiagnosticHandler(context->context, &diagnostics_handler, context); @@ -43,8 +42,6 @@ static void gencontext_init(GenContext *context, Context *ast_context) static void gencontext_destroy(GenContext *context) { - free(context->ir_filename); - free(context->object_filename); LLVMContextDispose(context->context); free(context); } @@ -306,18 +303,30 @@ void llvm_emit_ptr_from_array(GenContext *c, BEValue *value) // TODO insert trap on overflow. LLVMTypeRef subarray_type = llvm_get_type(c, value->type); assert(value->kind == BE_ADDRESS); - LLVMValueRef pointer_addr = LLVMBuildStructGEP2(c->builder, subarray_type, value->value, 0, "subarray_ptr"); + LLVMValueRef pointer_addr = LLVMBuildStructGEP2(c->builder, subarray_type, value->value, 0, "subarrayptr"); LLVMTypeRef pointer_type = llvm_get_type(c, type_get_ptr(value->type->array.base)); AlignSize alignment = type_abi_alignment(type_voidptr); // We need to pick the worst alignment in case this is packed in an array. if (value->alignment < alignment) alignment = value->alignment; llvm_value_set_address_align(value, - llvm_emit_load_aligned(c, pointer_type, pointer_addr, 0, "subarrptr"), value->type, alignment); + llvm_emit_load_aligned(c, pointer_type, pointer_addr, 0, "saptr"), value->type, alignment); return; } case TYPE_STRLIT: TODO case TYPE_VARARRAY: + { + llvm_value_rvalue(c, value); + LLVMTypeRef struct_type = value->type->backend_aux_type; + LLVMValueRef pointer_addr = LLVMBuildStructGEP2(c->builder, struct_type, value->value, 3, "vararrptr"); + LLVMTypeRef pointer_type = llvm_get_type(c, type_get_ptr(value->type->array.base)); + AlignSize alignment = type_abi_alignment(type_voidptr); + // We need to pick the worst alignment in case this is packed in an array. + if (value->alignment < alignment) alignment = value->alignment; + llvm_value_set_address_align(value, + llvm_emit_load_aligned(c, pointer_type, pointer_addr, 0, "vaptr"), value->type, alignment); + return; + } default: UNREACHABLE } @@ -410,7 +419,7 @@ static void gencontext_verify_ir(GenContext *context) void gencontext_emit_object_file(GenContext *context) { char *err = ""; - LLVMSetTarget(context->module, build_options.target_triple); + LLVMSetTarget(context->module, build_target.target_triple); char *layout = LLVMCopyStringRepOfTargetData(target_data_layout()); LLVMSetDataLayout(context->module, layout); LLVMDisposeMessage(layout); @@ -468,13 +477,13 @@ void llvm_emit_local_var_alloca(GenContext *c, Decl *decl) * Values here taken from LLVM. * @return return the inlining threshold given the build options. */ -static int get_inlining_threshold(void) +static int get_inlining_threshold(BuildTarget *target) { - if (build_options.optimization_level == OPTIMIZATION_AGGRESSIVE) + if (target->optimization_level == OPTIMIZATION_AGGRESSIVE) { return 250; } - switch (build_options.size_optimization_level) + switch (target->size_optimization_level) { case SIZE_OPTIMIZATION_TINY: return 5; @@ -751,19 +760,20 @@ void llvm_value_set_address(BEValue *value, LLVMValueRef llvm_value, Type *type) void llvm_value_fold_failable(GenContext *c, BEValue *value) { + if (value->kind == BE_ADDRESS_FAILABLE) { LLVMBasicBlockRef after_block = llvm_basic_block_new(c, "after_check"); - // TODO optimize load. - LLVMValueRef error_value = gencontext_emit_load(c, type_error, value->failable); + BEValue error_value; + llvm_value_set_address(&error_value, value->failable, type_error); BEValue comp; - llvm_value_set_bool(&comp, llvm_emit_is_no_error(c, error_value)); + llvm_value_set_bool(&comp, llvm_emit_is_no_error_value(c, &error_value)); if (c->error_var) { LLVMBasicBlockRef error_block = llvm_basic_block_new(c, "error"); llvm_emit_cond_br(c, &comp, after_block, error_block); llvm_emit_block(c, error_block); - llvm_store_aligned(c, c->error_var, error_value, type_abi_alignment(type_usize)); + llvm_store_bevalue_dest_aligned(c, c->error_var, &error_value); llvm_emit_br(c, c->catch_block); } else @@ -863,16 +873,16 @@ static void gencontext_emit_decl(GenContext *context, Decl *decl) } } -void llvm_codegen(void *context) +const char *llvm_codegen(void *context) { - GenContext *gen_context = context; - LLVMModuleRef module = gen_context->module; + GenContext *c = context; + LLVMModuleRef module = c->module; // Starting from here we could potentially thread this: LLVMPassManagerBuilderRef pass_manager_builder = LLVMPassManagerBuilderCreate(); - LLVMPassManagerBuilderSetOptLevel(pass_manager_builder, build_options.optimization_level); - LLVMPassManagerBuilderSetSizeLevel(pass_manager_builder, build_options.size_optimization_level); - LLVMPassManagerBuilderSetDisableUnrollLoops(pass_manager_builder, build_options.optimization_level == OPTIMIZATION_NONE); - LLVMPassManagerBuilderUseInlinerWithThreshold(pass_manager_builder, get_inlining_threshold()); + LLVMPassManagerBuilderSetOptLevel(pass_manager_builder, c->build_target->optimization_level); + LLVMPassManagerBuilderSetSizeLevel(pass_manager_builder, c->build_target->size_optimization_level); + LLVMPassManagerBuilderSetDisableUnrollLoops(pass_manager_builder, c->build_target->optimization_level == OPTIMIZATION_NONE); + LLVMPassManagerBuilderUseInlinerWithThreshold(pass_manager_builder, get_inlining_threshold(c->build_target)); LLVMPassManagerRef pass_manager = LLVMCreatePassManager(); LLVMPassManagerRef function_pass_manager = LLVMCreateFunctionPassManagerForModule(module); LLVMAddAnalysisPasses(target_machine(), function_pass_manager); @@ -902,19 +912,25 @@ void llvm_codegen(void *context) LLVMDisposePassManager(pass_manager); // Serialize the LLVM IR, if requested, also verify the IR in this case - if (build_options.emit_llvm) + if (c->build_target->emit_llvm) { - gencontext_print_llvm_ir(gen_context); - gencontext_verify_ir(gen_context); + gencontext_print_llvm_ir(c); + gencontext_verify_ir(c); } - if (build_options.emit_bitcode) gencontext_emit_object_file(gen_context); - - gencontext_end_module(gen_context); - gencontext_destroy(gen_context); + const char *object_name = NULL; + if (c->build_target->emit_object_files) + { + gencontext_emit_object_file(c); + object_name = c->object_filename; + } + gencontext_end_module(c); + gencontext_destroy(c); + return object_name; } + void *llvm_gen(Context *context) { assert(intrinsics_setup); @@ -960,7 +976,7 @@ void *llvm_gen(Context *context) if (llvm_use_debug(gen_context)) LLVMDIBuilderFinalize(gen_context->debug.builder); // If it's in test, then we want to serialize the IR before it is optimized. - if (build_options.test_mode) + if (diagnostics.test_mode) { gencontext_print_llvm_ir(gen_context); gencontext_verify_ir(gen_context); diff --git a/src/compiler/llvm_codegen_debug_info.c b/src/compiler/llvm_codegen_debug_info.c index 4b44ff244..83692d5b8 100644 --- a/src/compiler/llvm_codegen_debug_info.c +++ b/src/compiler/llvm_codegen_debug_info.c @@ -123,7 +123,7 @@ void llvm_emit_debug_function(GenContext *c, Decl *decl) true, loc->line, flags, - build_options.optimization_level != OPTIMIZATION_NONE); + c->build_target->optimization_level != OPTIMIZATION_NONE); LLVMSetSubprogram(decl->backend_ref, c->debug.function); } @@ -139,7 +139,7 @@ void llvm_emit_debug_local_var(GenContext *c, Decl *decl) c->debug.file, location->line, llvm_get_debug_type(c, decl->type), - build_options.optimization_level != OPTIMIZATION_NONE, + c->build_target->optimization_level != OPTIMIZATION_NONE, LLVMDIFlagZero, decl->alignment); decl->var.backend_debug_ref = var; @@ -214,7 +214,7 @@ static LLVMMetadataRef llvm_debug_forward_comp(GenContext *c, Type *type, const type->name, strlen(type->name), scope, c->debug.file, loc ? loc->line : 0, - build_options.version, + 1 /* version TODO */, type_size(type) * 8, type_abi_alignment(type) * 8, flags, @@ -371,6 +371,54 @@ static LLVMMetadataRef llvm_debug_subarray_type(GenContext *c, Type *type) return llvm_get_debug_struct(c, type, type->name, elements, 2, NULL, NULL, LLVMDIFlagZero); } +static LLVMMetadataRef llvm_debug_vararray_type(GenContext *c, Type *type) +{ + LLVMMetadataRef forward = llvm_debug_forward_comp(c, type, type->name, NULL, NULL, LLVMDIFlagZero); + type->backend_debug_type = forward; + Type *element = type->array.base; + + LLVMMetadataRef array = LLVMDIBuilderCreateArrayType( + c->debug.builder, + type_size(type), + type_abi_alignment(element), + llvm_get_debug_type(c, element), + NULL, 0); + + LLVMMetadataRef array_member = LLVMDIBuilderCreateMemberType( + c->debug.builder, + forward, + "array", strlen("array"), + NULL, + 0, 0, + type_abi_alignment(element) * 8, + type_size(type_usize) * 2 * 8, LLVMDIFlagZero, array); + + LLVMMetadataRef elements[3] = { + llvm_get_debug_member(c, type_get_ptr(type->array.base), "len", 0, NULL, forward, LLVMDIFlagZero), + llvm_get_debug_member(c, type_usize, "capacity", type_size(type_usize), NULL, forward, LLVMDIFlagZero), + array_member + }; + unsigned strukt_size = type_size(type_usize) * 2 * 8; + unsigned alignment = type_abi_alignment(type_usize) * 8; + LLVMMetadataRef strukt = LLVMDIBuilderCreateStructType(c->debug.builder, + NULL, + "", 0, NULL, 0, strukt_size, + alignment, + LLVMDIFlagZero, NULL, + elements, 3, + c->debug.runtime_version, + NULL, // VTable + "", 0); + LLVMMetadataRef real = LLVMDIBuilderCreatePointerType(c->debug.builder, + strukt, + strukt_size, + alignment, 0, + type->name, strlen(type->name)); + + LLVMMetadataReplaceAllUsesWith(forward, real); + return real; +} + static LLVMMetadataRef llvm_debug_errunion_type(GenContext *c, Type *type) { LLVMMetadataRef forward = llvm_debug_forward_comp(c, type, type->name, NULL, NULL, LLVMDIFlagZero); @@ -422,6 +470,8 @@ static LLVMMetadataRef llvm_debug_typedef_type(GenContext *c, Type *type) NULL, 0, NULL, 0); } + Type *original_type = type->type_kind == TYPE_TYPEDEF ? decl->typedef_decl.type_info->type : decl->distinct_decl.base_type; + SourceLocation *location = TOKLOC(decl->span.loc); // Use forward references in case we haven't resolved the original type, since we could have this: if (!type->canonical->backend_debug_type) @@ -429,7 +479,7 @@ static LLVMMetadataRef llvm_debug_typedef_type(GenContext *c, Type *type) type->backend_debug_type = llvm_debug_forward_comp(c, type, type->name, location, NULL, LLVMDIFlagZero); } LLVMMetadataRef real = LLVMDIBuilderCreateTypedef(c->debug.builder, - llvm_get_debug_type(c, decl->typedef_decl.type_info->type), + llvm_get_debug_type(c, original_type), decl->name, TOKLEN(decl->name_token), c->debug.file, location->line, c->debug.file, type_abi_alignment(type)); @@ -488,6 +538,7 @@ static inline LLVMMetadataRef llvm_get_debug_type_internal(GenContext *c, Type * case TYPE_TYPEINFO: case TYPE_MEMBER: case TYPE_INFERRED_ARRAY: + case TYPE_STRLIT: UNREACHABLE case TYPE_BOOL: return llvm_debug_simple_type(c, type, DW_ATE_boolean); @@ -527,15 +578,12 @@ static inline LLVMMetadataRef llvm_get_debug_type_internal(GenContext *c, Type * case TYPE_UNION: return type->backend_debug_type = llvm_debug_structlike_type(c, type, scope); case TYPE_DISTINCT: - TODO case TYPE_TYPEDEF: return type->backend_debug_type = llvm_debug_typedef_type(c, type); - case TYPE_STRLIT: - TODO case TYPE_ARRAY: return type->backend_debug_type = llvm_debug_array_type(c, type); case TYPE_VARARRAY: - TODO + return type->backend_debug_type = llvm_debug_vararray_type(c, type); case TYPE_SUBARRAY: return type->backend_debug_type = llvm_debug_subarray_type(c, type); case TYPE_ERR_UNION: diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index b56d832bc..256426f27 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -14,10 +14,12 @@ static void llvm_emit_post_unary_expr(GenContext *context, BEValue *be_value, Ex static inline LLVMValueRef llvm_emit_subscript_addr_with_base_new(GenContext *c, BEValue *parent, BEValue *index); static void llvm_emit_initialize_designated(GenContext *c, BEValue *ref, uint64_t offset, DesignatorElement** current, DesignatorElement **last, Expr *expr, BEValue *emitted_value); -LLVMValueRef llvm_emit_is_no_error(GenContext *c, LLVMValueRef error) +LLVMValueRef llvm_emit_is_no_error_value(GenContext *c, BEValue *value) { - LLVMValueRef domain = LLVMBuildExtractValue(c->builder, error, 0, ""); - return LLVMBuildICmp(c->builder, LLVMIntEQ, domain, llvm_get_zero(c, type_usize), "noerr"); + assert(value->kind == BE_ADDRESS); + LLVMValueRef val = LLVMBuildStructGEP2(c->builder, llvm_get_type(c, value->type), value->value, 0, "err_domain"); + LLVMValueRef domain = llvm_emit_load_aligned(c, llvm_get_type(c, type_uptr), val, value->alignment, ""); + return LLVMBuildICmp(c->builder, LLVMIntEQ, domain, llvm_get_zero(c, type_uptr), "not_err"); } LLVMTypeRef llvm_const_padding_type(GenContext *c, ByteSize size) @@ -32,29 +34,29 @@ LLVMValueRef llvm_emit_const_padding(GenContext *c, ByteSize size) return LLVMGetUndef(llvm_const_padding_type(c, size)); } -static inline LLVMValueRef llvm_emit_add_int(GenContext *context, Type *type, LLVMValueRef left, LLVMValueRef right) +static inline LLVMValueRef llvm_emit_add_int(GenContext *c, Type *type, LLVMValueRef left, LLVMValueRef right) { - if (build_options.trap_wrapping) + if (c->build_target->feature.trap_on_wrap) { - LLVMTypeRef type_to_use = llvm_get_type(context, type->canonical); + LLVMTypeRef type_to_use = llvm_get_type(c, type->canonical); LLVMValueRef args[2] = { left, right }; assert(type->canonical == type); LLVMValueRef add_res; if (type_is_unsigned(type)) { - add_res = llvm_emit_call_intrinsic(context, intrinsic_id_uadd_overflow, &type_to_use, 1, args, 2); + add_res = llvm_emit_call_intrinsic(c, intrinsic_id_uadd_overflow, &type_to_use, 1, args, 2); } else { - add_res = llvm_emit_call_intrinsic(context, intrinsic_id_sadd_overflow, &type_to_use, 1, args, 2); + add_res = llvm_emit_call_intrinsic(c, intrinsic_id_sadd_overflow, &type_to_use, 1, args, 2); } - LLVMValueRef result = LLVMBuildExtractValue(context->builder, add_res, 0, ""); - LLVMValueRef ok = LLVMBuildExtractValue(context->builder, add_res, 1, ""); - llvm_emit_panic_on_true(context, ok, "Addition overflow"); + LLVMValueRef result = LLVMBuildExtractValue(c->builder, add_res, 0, ""); + LLVMValueRef ok = LLVMBuildExtractValue(c->builder, add_res, 1, ""); + llvm_emit_panic_on_true(c, ok, "Addition overflow"); return result; } - return LLVMBuildAdd(context->builder, left, right, "add"); + return LLVMBuildAdd(c->builder, left, right, "add"); } LLVMValueRef llvm_emit_coerce(GenContext *context, LLVMTypeRef coerced, BEValue *value, Type *original_type) @@ -84,39 +86,39 @@ LLVMValueRef llvm_emit_coerce(GenContext *context, LLVMTypeRef coerced, BEValue return llvm_emit_load_aligned(context, coerced, cast, max_align, "coerced"); } -LLVMValueRef llvm_emit_convert_value_from_coerced(GenContext *context, LLVMTypeRef coerced, LLVMValueRef value, Type *original_type) +void llvm_emit_convert_value_from_coerced(GenContext *context, BEValue *result, LLVMTypeRef coerced, LLVMValueRef value, Type *original_type) { unsigned max_align = MAX(llvm_abi_alignment(coerced), type_abi_alignment(original_type)); LLVMValueRef temp = llvm_emit_alloca(context, coerced, max_align, "coerce_temp"); llvm_store_aligned(context, temp, value, max_align); temp = LLVMBuildBitCast(context->builder, temp, llvm_get_type(context, type_get_ptr(original_type)), ""); - return llvm_emit_load_aligned(context, llvm_get_type(context, original_type), temp, max_align, "coerced"); + llvm_value_set_address_align(result, temp, original_type, max_align); } static inline LLVMValueRef -llvm_emit_sub_int(GenContext *context, Type *type, LLVMValueRef left, LLVMValueRef right) +llvm_emit_sub_int(GenContext *c, Type *type, LLVMValueRef left, LLVMValueRef right) { - if (build_options.trap_wrapping) + if (c->build_target->feature.trap_on_wrap) { - LLVMTypeRef type_to_use = llvm_get_type(context, type); + LLVMTypeRef type_to_use = llvm_get_type(c, type); LLVMValueRef args[2] = { left, right }; assert(type->canonical == type); LLVMValueRef add_res; if (type_is_unsigned(type)) { - add_res = llvm_emit_call_intrinsic(context, intrinsic_id_usub_overflow, &type_to_use, 1, args, 2); + add_res = llvm_emit_call_intrinsic(c, intrinsic_id_usub_overflow, &type_to_use, 1, args, 2); } else { - add_res = llvm_emit_call_intrinsic(context, intrinsic_id_ssub_overflow, &type_to_use, 1, args, 2); + add_res = llvm_emit_call_intrinsic(c, intrinsic_id_ssub_overflow, &type_to_use, 1, args, 2); } - LLVMValueRef result = LLVMBuildExtractValue(context->builder, add_res, 0, ""); - LLVMValueRef ok = LLVMBuildExtractValue(context->builder, add_res, 1, ""); - llvm_emit_panic_on_true(context, ok, "Subtraction overflow"); + LLVMValueRef result = LLVMBuildExtractValue(c->builder, add_res, 0, ""); + LLVMValueRef ok = LLVMBuildExtractValue(c->builder, add_res, 1, ""); + llvm_emit_panic_on_true(c, ok, "Subtraction overflow"); return result; } - return LLVMBuildSub(context->builder, left, right, "sub"); + return LLVMBuildSub(c->builder, left, right, "sub"); } static inline void llvm_emit_subscript_addr_base(GenContext *context, BEValue *value, Expr *parent) @@ -188,7 +190,7 @@ static inline LLVMValueRef llvm_emit_subscript_addr_with_base_new(GenContext *c, return LLVMBuildInBoundsGEP(c->builder, parent->value, &index->value, 1, "ptridx"); case TYPE_ARRAY: { - if (build_options.debug_mode) + if (c->build_target->feature.safe_mode) { llvm_emit_array_bounds_check(c, index, llvm_const_int(c, index->type, type->array.len)); } @@ -203,7 +205,7 @@ static inline LLVMValueRef llvm_emit_subscript_addr_with_base_new(GenContext *c, } case TYPE_SUBARRAY: { - if (build_options.debug_mode) + if (c->build_target->feature.safe_mode) { // TODO insert trap on overflow. } @@ -1076,7 +1078,7 @@ static void gencontext_emit_unary_expr(GenContext *c, BEValue *value, Expr *expr assert(!type_is_unsigned(type)); { LLVMValueRef zero = llvm_get_zero(c, expr->unary_expr.expr->type); - if (build_options.trap_wrapping) + if (c->build_target->feature.trap_on_wrap) { // TODO LLVMTypeRef type_to_use = llvm_get_type(c, type->canonical); @@ -1137,9 +1139,33 @@ void llvm_emit_len_for_expr(GenContext *c, BEValue *be_value, BEValue *expr_to_l case TYPE_STRLIT: TODO break; - case TYPE_VARARRAY: - TODO + { + llvm_value_rvalue(c, be_value); + LLVMValueRef check = LLVMBuildIsNull(c->builder, be_value->value, "checknull"); + BEValue bool_value; + llvm_value_set_bool(&bool_value, check); + LLVMBasicBlockRef null_block = llvm_basic_block_new(c, "lennull"); + LLVMBasicBlockRef non_null_block = llvm_basic_block_new(c, "lennormal"); + LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "lenend"); + llvm_emit_cond_br(c, &bool_value, null_block, non_null_block); + llvm_emit_block(c, null_block); + LLVMValueRef result_null = llvm_get_zero(c, type_usize); + llvm_emit_br(c, exit_block); + llvm_emit_block(c, non_null_block); + LLVMTypeRef struct_type = be_value->type->backend_aux_type; + LLVMValueRef len_addr = LLVMBuildStructGEP2(c->builder, struct_type, be_value->value, 0, ""); + llvm_value_set_address(be_value, len_addr, type_usize); + LLVMValueRef result = llvm_value_rvalue_store(c, be_value); + llvm_emit_br(c, exit_block); + llvm_emit_block(c, exit_block); + LLVMValueRef total = LLVMBuildPhi(c->builder, llvm_get_type(c, type_usize), ""); + LLVMValueRef logic_values[2] = { result_null, result }; + LLVMBasicBlockRef blocks[2] = { null_block, non_null_block }; + LLVMAddIncoming(total, logic_values, blocks, 2); + llvm_value_set(be_value, total, type_usize); + return; + } default: UNREACHABLE } @@ -1150,39 +1176,39 @@ static void llvm_emit_len(GenContext *c, BEValue *be_value, Expr *expr) llvm_emit_len_for_expr(c, be_value, be_value); } -static void gencontext_emit_trap_negative(GenContext *context, Expr *expr, LLVMValueRef value, const char *error) +static void llvm_emit_trap_negative(GenContext *c, Expr *expr, LLVMValueRef value, const char *error) { - if (!build_options.debug_mode) return; + if (!c->build_target->feature.safe_mode) return; if (type_is_integer_unsigned(expr->type->canonical)) return; - LLVMValueRef zero = llvm_const_int(context, expr->type, 0); - LLVMValueRef ok = LLVMBuildICmp(context->builder, LLVMIntSLT, value, zero, "underflow"); - llvm_emit_panic_on_true(context, ok, error); + LLVMValueRef zero = llvm_const_int(c, expr->type, 0); + LLVMValueRef ok = LLVMBuildICmp(c->builder, LLVMIntSLT, value, zero, "underflow"); + llvm_emit_panic_on_true(c, ok, error); } static void -gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_type_ref, LLVMValueRef *parent_base_ref, - Type **start_type_ref, LLVMValueRef *start_index_ref, Type **end_type_ref, - LLVMValueRef *end_index_ref) +llvm_emit_slice_values(GenContext *c, Expr *slice, Type **parent_type_ref, LLVMValueRef *parent_base_ref, + Type **start_type_ref, LLVMValueRef *start_index_ref, Type **end_type_ref, + LLVMValueRef *end_index_ref) { assert(slice->expr_kind == EXPR_SLICE); Expr *parent_expr = slice->slice_expr.expr; Type *parent_type = parent_expr->type->canonical; BEValue parent_addr_x; - llvm_emit_expr(context, &parent_addr_x, parent_expr); - llvm_value_addr(context, &parent_addr_x); + llvm_emit_expr(c, &parent_addr_x, parent_expr); + llvm_value_addr(c, &parent_addr_x); LLVMValueRef parent_addr = parent_addr_x.value; LLVMValueRef parent_load_value; LLVMValueRef parent_base; switch (parent_type->type_kind) { case TYPE_POINTER: - parent_load_value = parent_base = gencontext_emit_load(context, parent_type, parent_addr); + parent_load_value = parent_base = gencontext_emit_load(c, parent_type, parent_addr); break; case TYPE_SUBARRAY: - parent_load_value = gencontext_emit_load(context, parent_type, parent_addr); - parent_base = LLVMBuildExtractValue(context->builder, parent_load_value, 0, ""); + parent_load_value = gencontext_emit_load(c, parent_type, parent_addr); + parent_base = LLVMBuildExtractValue(c->builder, parent_load_value, 0, ""); break; case TYPE_ARRAY: parent_base = parent_addr; @@ -1200,11 +1226,11 @@ gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_typ // Emit the start and end Type *start_type = start->type->canonical; BEValue start_index; - llvm_emit_expr(context, &start_index, start); - llvm_value_rvalue(context, &start_index); + llvm_emit_expr(c, &start_index, start); + llvm_value_rvalue(c, &start_index); LLVMValueRef len; - if (!end || slice->slice_expr.start_from_back || slice->slice_expr.end_from_back || build_options.debug_mode) + if (!end || slice->slice_expr.start_from_back || slice->slice_expr.end_from_back || c->build_target->feature.safe_mode) { switch (parent_type->type_kind) { @@ -1212,10 +1238,10 @@ gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_typ len = NULL; break; case TYPE_SUBARRAY: - len = LLVMBuildExtractValue(context->builder, parent_load_value, 1, ""); + len = LLVMBuildExtractValue(c->builder, parent_load_value, 1, ""); break; case TYPE_ARRAY: - len = llvm_const_int(context, type_usize, parent_type->array.len); + len = llvm_const_int(c, type_usize, parent_type->array.len); break; case TYPE_VARARRAY: case TYPE_STRLIT: @@ -1228,26 +1254,26 @@ gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_typ // Walk from end if it is slice from the back. if (slice->slice_expr.start_from_back) { - start_index.value = llvm_emit_sub_int(context, start_type, len, start_index.value); + start_index.value = llvm_emit_sub_int(c, start_type, len, start_index.value); } // Check that index does not extend beyond the length. - if (parent_type->type_kind != TYPE_POINTER && build_options.debug_mode) + if (parent_type->type_kind != TYPE_POINTER && c->build_target->feature.safe_mode) { - LLVMValueRef exceeds_size = llvm_emit_int_comparison(context, + LLVMValueRef exceeds_size = llvm_emit_int_comparison(c, type_usize, start_type, start_index.value, len, BINARYOP_GE); - llvm_emit_panic_on_true(context, exceeds_size, "Index exceeds array length."); + llvm_emit_panic_on_true(c, exceeds_size, "Index exceeds array length."); } // Insert trap for negative start offset for non pointers. if (parent_type->type_kind != TYPE_POINTER) { - gencontext_emit_trap_negative(context, start, start_index.value, "Negative index"); + llvm_emit_trap_negative(c, start, start_index.value, "Negative index"); } Type *end_type; @@ -1256,37 +1282,37 @@ gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_typ if (end) { // Get the index. - llvm_emit_expr(context, &end_index, end); - llvm_value_rvalue(context, &end_index); + llvm_emit_expr(c, &end_index, end); + llvm_value_rvalue(c, &end_index); end_type = end->type->canonical; // Reverse if it is "from back" if (slice->slice_expr.end_from_back) { - end_index.value = llvm_emit_sub_int(context, end_type, len, end_index.value); - llvm_value_rvalue(context, &end_index); + end_index.value = llvm_emit_sub_int(c, end_type, len, end_index.value); + llvm_value_rvalue(c, &end_index); } // This will trap any bad negative index, so we're fine. - if (build_options.debug_mode) + if (c->build_target->feature.safe_mode) { - LLVMValueRef excess = llvm_emit_int_comparison(context, + LLVMValueRef excess = llvm_emit_int_comparison(c, start_type, end_type, start_index.value, end_index.value, BINARYOP_GT); - llvm_emit_panic_on_true(context, excess, "Negative size"); + llvm_emit_panic_on_true(c, excess, "Negative size"); if (len) { - LLVMValueRef exceeds_size = llvm_emit_int_comparison(context, + LLVMValueRef exceeds_size = llvm_emit_int_comparison(c, type_usize, end_type, len, end_index.value, BINARYOP_LT); - llvm_emit_panic_on_true(context, exceeds_size, "Size exceeds index"); + llvm_emit_panic_on_true(c, exceeds_size, "Size exceeds index"); } } } @@ -1294,7 +1320,7 @@ gencontext_emit_slice_values(GenContext *context, Expr *slice, Type **parent_typ { assert(len && "Pointer should never end up here."); // Otherwise everything is fine and dandy. Our len - 1 is our end index. - end_index.value = LLVMBuildSub(context->builder, len, LLVMConstInt(LLVMTypeOf(len), 1, false), ""); + end_index.value = LLVMBuildSub(c->builder, len, LLVMConstInt(LLVMTypeOf(len), 1, false), ""); end_type = type_usize; } @@ -1315,9 +1341,9 @@ static void gencontext_emit_slice(GenContext *context, BEValue *be_value, Expr * Type *start_type; LLVMValueRef start_index; // Use general function to get all the values we need (a lot!) - gencontext_emit_slice_values(context, expr, &parent_type, - &parent_base, - &start_type, &start_index, &end_type, &end_index); + llvm_emit_slice_values(context, expr, &parent_type, + &parent_base, + &start_type, &start_index, &end_type, &end_index); // Calculate the size @@ -1373,9 +1399,9 @@ static void gencontext_emit_slice_assign(GenContext *c, BEValue *be_value, Expr Type *start_type; LLVMValueRef start_index; // Use general function to get all the values we need (a lot!) - gencontext_emit_slice_values(c, expr->slice_assign_expr.left, &parent_type, - &parent_base, - &start_type, &start_index, &end_type, &end_index); + llvm_emit_slice_values(c, expr->slice_assign_expr.left, &parent_type, + &parent_base, + &start_type, &start_index, &end_type, &end_index); // We will need to iterate for the general case. LLVMBasicBlockRef start_block = c->current_block; @@ -1618,7 +1644,7 @@ static inline LLVMValueRef llvm_fixup_shift_rhs(GenContext *c, LLVMValueRef left static inline LLVMValueRef llvm_emit_mult_int(GenContext *c, Type *type, LLVMValueRef left, LLVMValueRef right) { - if (build_options.trap_wrapping) + if (c->build_target->feature.trap_on_wrap) { LLVMTypeRef type_to_use = llvm_get_type(c, type); LLVMValueRef args[2] = { left, right }; @@ -2450,65 +2476,95 @@ void llvm_emit_parameter(GenContext *context, LLVMValueRef **args, ABIArgInfo *i } } -void llvm_emit_call_expr(GenContext *context, BEValue *be_value, Expr *expr) +void llvm_emit_call_expr(GenContext *c, BEValue *be_value, Expr *expr) { - printf("Optimize call return\n"); + Expr *function = expr->call_expr.function; FunctionSignature *signature; LLVMTypeRef func_type; LLVMValueRef func; + + + // 1. Call through a pointer. if (expr->call_expr.is_pointer_call) { - signature = expr->call_expr.function->type->canonical->pointer->func.signature; + // 1a. Find the pointee type for the function pointer: + Type *type = function->type->canonical->pointer; + + // 1b. Find the type signature using the underlying pointer. + signature = type->func.signature; + + // 1c. Evaluate the pointer expression. BEValue func_value; - llvm_emit_expr(context, &func_value, expr->call_expr.function); - func = llvm_value_rvalue_store(context, &func_value); - func_type = llvm_get_type(context, expr->call_expr.function->type->canonical->pointer); + llvm_emit_expr(c, &func_value, expr->call_expr.function); + + // 1d. Load it as a value + func = llvm_value_rvalue_store(c, &func_value); + + // 1e. Calculate the function type + func_type = llvm_get_type(c, type); } - else if (expr->call_expr.is_struct_function) + else if (expr->call_expr.is_type_method) { + // 2. Call through a type method + + // 2a. Get the function declaration Decl *function_decl = expr->call_expr.function->access_expr.ref; + + // 2b. Set signature, function and function type signature = &function_decl->func.function_signature; func = function_decl->backend_ref; - func_type = llvm_get_type(context, function_decl->type); + func_type = llvm_get_type(c, function_decl->type); } else { + // 3. Call a regular function. Decl *function_decl = expr->call_expr.function->identifier_expr.decl; + + // 3a. This may be an intrinsic, if so generate an intrinsic call instead. if (function_decl->func.is_builtin) { - gencontext_emit_call_intrinsic_expr(context, be_value, expr); + gencontext_emit_call_intrinsic_expr(c, be_value, expr); return; } + + // 3b. Set signature, function and function type signature = &function_decl->func.function_signature; func = function_decl->backend_ref; - func_type = llvm_get_type(context, function_decl->type); + func_type = llvm_get_type(c, function_decl->type); } - LLVMValueRef return_param = NULL; LLVMValueRef *values = NULL; + // 4. Prepare the return abi data. ABIArgInfo *ret_info = signature->ret_abi_info; Type *return_type = signature->rtype->type->canonical; + // 5. In the case of a failable, the error is replacing the regular return abi. + LLVMValueRef error_var = NULL; if (signature->failable) { ret_info = signature->failable_abi_info; return_type = type_error; } + // 6. Generate data for the return value. switch (ret_info->kind) { case ABI_ARG_INDIRECT: - // Create the return parameter - return_param = llvm_emit_alloca(context, llvm_get_type(context, return_type), - ret_info->indirect.realignment, "sretparam"); - // Add the pointer to the list of arguments. - vec_add(values, return_param); - if (ret_info->indirect.realignment) + // 6a. We can use the stored error var if there is no redirect. + if (signature->failable && c->error_var && type_abi_alignment(return_type) < ret_info->indirect.realignment) { - llvm_set_alignment(return_param, ret_info->indirect.realignment); + error_var = c->error_var; + vec_add(values, error_var); + break; } + // 6b. Return true is indirect, in this case we allocate a local, using the desired alignment on the caller side. + AlignSize alignment = ret_info->indirect.realignment ? ret_info->indirect.realignment : type_abi_alignment(return_type); + llvm_value_set_address_align(be_value, llvm_emit_alloca(c, llvm_get_type(c, return_type), alignment, "sretparam"), return_type, alignment); + + // 6c. Add the pointer to the list of arguments. + vec_add(values, be_value->value); break; case ABI_ARG_EXPAND: UNREACHABLE @@ -2519,138 +2575,215 @@ void llvm_emit_call_expr(GenContext *context, BEValue *be_value, Expr *expr) break; } + + // 7. We might have a failable indirect return and a normal return. + // In this case we need to add it by hand. + BEValue synthetic_return_param = {}; if (signature->failable && signature->ret_abi_info) { + // 7b. Create the address to hold the return. Type *actual_return_type = type_lowering(signature->rtype->type); - return_param = llvm_emit_alloca_aligned(context, actual_return_type, "retparam"); - llvm_value_set(be_value, return_param, type_get_ptr(actual_return_type)); - llvm_emit_parameter(context, &values, signature->ret_abi_info, be_value, be_value->type); + llvm_value_set(&synthetic_return_param, llvm_emit_alloca_aligned(c, actual_return_type, "retparam"), type_get_ptr(actual_return_type)); + // 7c. Emit it as a parameter as a pointer (will implicitly add it to the value list) + llvm_emit_parameter(c, &values, signature->ret_abi_info, &synthetic_return_param, synthetic_return_param.type); + // 7d. Update the be_value to actually be an address. + llvm_value_set_address(&synthetic_return_param, synthetic_return_param.value, actual_return_type); } + + + // 8. Add all other arguments. unsigned arguments = vec_size(expr->call_expr.arguments); assert(arguments >= vec_size(signature->params)); VECEACH(signature->params, i) { + // 8a. Evaluate the expression. Expr *arg_expr = expr->call_expr.arguments[i]; - llvm_emit_expr(context, be_value, arg_expr); + llvm_emit_expr(c, be_value, arg_expr); + + // 8b. Emit the parameter according to ABI rules. Decl *param = signature->params[i]; ABIArgInfo *info = param->var.abi_info; - llvm_emit_parameter(context, &values, info, be_value, param->type); + llvm_emit_parameter(c, &values, info, be_value, param->type); } + + // 9. Emit varargs. for (unsigned i = vec_size(signature->params); i < arguments; i++) { Expr *arg_expr = expr->call_expr.arguments[i]; - llvm_emit_expr(context, be_value, arg_expr); + llvm_emit_expr(c, be_value, arg_expr); printf("TODO: varargs should be expanded correctly\n"); - vec_add(values, llvm_value_rvalue_store(context, be_value)); + vec_add(values, llvm_value_rvalue_store(c, be_value)); } - LLVMValueRef call = LLVMBuildCall2(context->builder, func_type, func, values, vec_size(values), ""); + // 10. Create the actual call + LLVMValueRef call_value = LLVMBuildCall2(c->builder, func_type, func, values, vec_size(values), ""); + // 11. Process the return value. switch (ret_info->kind) { case ABI_ARG_EXPAND: UNREACHABLE case ABI_ARG_IGNORE: - // Default behaviour is fine. - break; + // 12. Basically void returns or empty structs. + // Here we know we don't have a failable or any return value that can be used. + assert(!signature->failable && "Failable should have produced a return value."); + *be_value = (BEValue) {}; + return; case ABI_ARG_INDIRECT: - // TODO look at failable - call = llvm_emit_load_aligned(context, - llvm_get_type(context, return_type), - return_param, - ret_info->indirect.realignment, - ""); + + // 13. Indirect, that is passing the result through an out parameter. + + // 13a. In the case of an already present error_var, we don't need to do a load here. + if (error_var) break; + + // 13b. Otherwise it will be contained in a be_value that is an address + // so we don't need to do anything more. + assert(be_value->kind == BE_ADDRESS); + break; case ABI_ARG_DIRECT_PAIR: { - // TODO look at failable - LLVMTypeRef lo = llvm_abi_type(context, ret_info->direct_pair.lo); - LLVMTypeRef hi = llvm_abi_type(context, ret_info->direct_pair.hi); - LLVMTypeRef struct_type = llvm_get_twostruct(context, lo, hi); - call = llvm_emit_convert_value_from_coerced(context, struct_type, call, return_type); + // 14. A direct pair, in this case the data is stored like { lo, hi } + // For example we might have { int, int, short, short, int }, + // this then gets bitcast to { long, long }, so we recover it by loading + // { long, long } into memory, then performing a bitcast to { int, int, short, short, int } + + // 14a. Generate the type. + LLVMTypeRef lo = llvm_abi_type(c, ret_info->direct_pair.lo); + LLVMTypeRef hi = llvm_abi_type(c, ret_info->direct_pair.hi); + LLVMTypeRef struct_type = llvm_get_twostruct(c, lo, hi); + + // 14b. Use the coerce method to go from the struct to the actual type + // by storing the { lo, hi } struct to memory, then loading it + // again using a bitcast. + llvm_emit_convert_value_from_coerced(c, be_value, struct_type, call_value, return_type); break; } case ABI_ARG_EXPAND_COERCE: { - LLVMValueRef ret = llvm_emit_alloca_aligned(context, return_type, ""); - LLVMTypeRef coerce_type = llvm_get_coerce_type(context, ret_info); - LLVMValueRef coerce = LLVMBuildBitCast(context->builder, ret, coerce_type, ""); + // 15. Expand-coerce, this is similar to "direct pair", but looks like this: + // { lo, hi } set into { pad, lo, pad, hi } -> original type. - LLVMTypeRef lo_type = llvm_abi_type(context, ret_info->coerce_expand.lo); + // 15a. Create memory to hold the return type. + LLVMValueRef ret = llvm_emit_alloca_aligned(c, return_type, ""); + llvm_value_set_address(be_value, ret, return_type); - // Find the address to the low value + // 15b. "Convert" this return type pointer in memory to our coerce type which is { pad, lo, pad, hi } + LLVMTypeRef coerce_type = llvm_get_coerce_type(c, ret_info); + LLVMValueRef coerce = LLVMBuildBitCast(c->builder, ret, coerce_type, ""); + + // 15c. Find the type of the "lo" element. + LLVMTypeRef lo_type = llvm_abi_type(c, ret_info->coerce_expand.lo); + + // 15d. Find the address to the low value unsigned alignment; - LLVMValueRef lo = llvm_emit_struct_gep_raw(context, coerce, coerce_type, ret_info->coerce_expand.lo_index, + LLVMValueRef lo = llvm_emit_struct_gep_raw(c, coerce, coerce_type, ret_info->coerce_expand.lo_index, type_abi_alignment(return_type), ret_info->coerce_expand.offset_lo, &alignment); - // If there is only a single field, we simply store the value. + // 15e. If there is only a single field, we simply store the value, + // so { lo } set into { pad, lo, pad } -> original type. if (!ret_info->coerce_expand.hi) { - llvm_store_aligned(context, lo, call, alignment); - call = llvm_emit_load_aligned(context, llvm_get_type(context, return_type), ret, 0, ""); + // Here we do a store to call -> lo (leaving the rest undefined) + llvm_store_aligned(c, lo, call_value, alignment); + break; } - LLVMTypeRef hi_type = llvm_abi_type(context, ret_info->coerce_expand.hi); + // 15f. Calculate the hi type. + LLVMTypeRef hi_type = llvm_abi_type(c, ret_info->coerce_expand.hi); - LLVMValueRef lo_value = LLVMBuildExtractValue(context->builder, call, 0, ""); - LLVMValueRef hi_value = LLVMBuildExtractValue(context->builder, call, 1, ""); + // 15g. We can now extract { lo, hi } to lo_value and hi_value. + LLVMValueRef lo_value = LLVMBuildExtractValue(c->builder, call_value, 0, ""); + LLVMValueRef hi_value = LLVMBuildExtractValue(c->builder, call_value, 1, ""); - // Store the low value. - llvm_store_aligned(context, lo, lo_value, alignment); + // 15h. Store lo_value into the { pad, lo, pad, hi } struct. + llvm_store_aligned(c, lo, lo_value, alignment); - LLVMValueRef hi = llvm_emit_struct_gep_raw(context, coerce, coerce_type, ret_info->coerce_expand.hi_index, + // 15i. Calculate the address to the high value (like for the low in 15d. + LLVMValueRef hi = llvm_emit_struct_gep_raw(c, coerce, coerce_type, ret_info->coerce_expand.hi_index, type_abi_alignment(return_type), ret_info->coerce_expand.offset_hi, &alignment); - // Store the high value. - llvm_store_aligned(context, lo, lo_value, alignment); + // 15h. Store the high value. + llvm_store_aligned(c, hi, hi_value, alignment); - // Now we can get the actual return value. - call = llvm_emit_load_aligned(context, llvm_get_type(context, return_type), ret, 0, ""); break; } case ABI_ARG_DIRECT_COERCE: { - // TODO look at failable - LLVMTypeRef coerce = llvm_get_coerce_type(context, ret_info); - // Default behaviour. - if (!coerce || coerce == llvm_get_type(context, return_type)) break; - assert(!abi_info_should_flatten(ret_info)); - call = llvm_emit_convert_value_from_coerced(context, coerce, call, return_type); + // 16. A direct coerce, this is basically "call result" bitcast return type. + + // 16a. Get the type of the return. + LLVMTypeRef coerce = llvm_get_coerce_type(c, ret_info); + + // 16b. If we don't have any coerce type, or the actual LLVM types are the same, we're done. + if (!coerce || coerce == llvm_get_type(c, return_type)) + { + // 16c. We just set as a value in be_value. + llvm_value_set(be_value, call_value, return_type); + break; + } + // 16c. We use a normal bitcast coerce. + assert(!abi_info_should_flatten(ret_info) && "Did not expect flattening on return types."); + llvm_emit_convert_value_from_coerced(c, be_value, coerce, call_value, return_type); break; } } + // 17. Handle failables. if (signature->failable) { - LLVMBasicBlockRef after_block = llvm_basic_block_new(context, "after_check"); BEValue no_err; - llvm_value_set_bool(&no_err, llvm_emit_is_no_error(context, call)); - if (context->error_var) + + // 17a. If we used the error var as the indirect recipient, then that will hold the error. + // otherwise it's whatever value in be_value. + BEValue error_holder = *be_value; + if (error_var) { - LLVMBasicBlockRef error_block = llvm_basic_block_new(context, "error"); - llvm_emit_cond_br(context, &no_err, after_block, error_block); - llvm_emit_block(context, error_block); - LLVMBuildStore(context->builder, - call, - llvm_emit_bitcast(context, context->error_var, type_get_ptr(type_error))); - llvm_emit_br(context, context->catch_block); + llvm_value_set_address(&error_holder, c->error_var, type_error); + } + // 17b. Generate a boolean switch. + llvm_value_set_bool(&no_err, llvm_emit_is_no_error_value(c, &error_holder)); + + // 17c. If we have an error var, or we aren't interested in the error variable + // - then it's straightforward. We just jump to the catch block. + LLVMBasicBlockRef after_block = llvm_basic_block_new(c, "after_check"); + if (error_var || !c->error_var) + { + llvm_emit_cond_br(c, &no_err, after_block, c->catch_block); } else { - llvm_emit_cond_br(context, &no_err, after_block, context->catch_block); + // 17d. If we have an error var we need to assign, then we need to + // first jump to an error block, where we do the copy. + LLVMBasicBlockRef error_block = llvm_basic_block_new(c, "error"); + llvm_emit_cond_br(c, &no_err, after_block, error_block); + llvm_emit_block(c, error_block); + llvm_store_bevalue_aligned(c, c->error_var, be_value, 0); + // 17e. Then jump to the catch. + llvm_emit_br(c, c->catch_block); } - llvm_emit_block(context, after_block); - // If void, be_value contents should be skipped. - if (!signature->ret_abi_info) return; - llvm_value_set_address(be_value, return_param, expr->type); + // 17f. Emit the "after" block. + llvm_emit_block(c, after_block); + + // 17g. If void, be_value contents should be skipped. + if (!signature->ret_abi_info) + { + *be_value = (BEValue) {}; + return; + } + + // 17h. Assign the return param to be_value. + *be_value = synthetic_return_param; return; } - llvm_value_set(be_value, call, expr->type); + // 17i. The simple case here is where there is a normal return. + // In this case be_value already holds the result + return; } @@ -2786,7 +2919,7 @@ LLVMValueRef llvm_emit_call_intrinsic(GenContext *context, unsigned intrinsic_id BEValue llvm_emit_assign_expr(GenContext *c, BEValue *ref, Expr *expr, LLVMValueRef failable) { - llvm_value_addr(c, ref); + assert(ref->kind == BE_ADDRESS || ref->kind == BE_ADDRESS_FAILABLE); LLVMBasicBlockRef assign_block = NULL; PUSH_ERROR(); diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index ef1e3bc61..1d26ec404 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -631,7 +631,7 @@ void llvm_emit_extern_decl(GenContext *context, Decl *decl) case DECL_UNION: case DECL_ERR: llvm_get_type(context, decl->type); - TODO // Fix typeid + // TODO // Fix typeid break; case DECL_ENUM: TODO diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 0f758c69b..236e6dc7e 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -97,6 +97,7 @@ typedef struct bool current_block_is_target : 1; bool did_call_stack_save : 1; LLVMTypeRef type_data_definitions[TYPE_KINDS]; + BuildTarget *build_target; } GenContext; // LLVM Intrinsics @@ -174,7 +175,7 @@ extern unsigned attribute_sext; // sign extend extern unsigned attribute_byval; // ByVal (param) extern unsigned attribute_inreg; // inreg (param) -void gencontext_begin_module(GenContext *context); +void gencontext_begin_module(GenContext *c); void gencontext_end_module(GenContext *context); // BE value @@ -212,7 +213,7 @@ void llvm_emit_block(GenContext *c, LLVMBasicBlockRef next_block); void llvm_emit_br(GenContext *c, LLVMBasicBlockRef next_block); void llvm_emit_compound_stmt(GenContext *context, Ast *ast); void llvm_emit_and_set_decl_alloca(GenContext *c, Decl *decl); -LLVMValueRef llvm_emit_convert_value_from_coerced(GenContext *context, LLVMTypeRef coerced, LLVMValueRef value, Type *original_type); +void llvm_emit_convert_value_from_coerced(GenContext *context, BEValue *result, LLVMTypeRef coerced, LLVMValueRef value, Type *original_type); void llvm_emit_function_body(GenContext *context, Decl *decl); void llvm_emit_function_decl(GenContext *c, Decl *decl); LLVMValueRef llvm_emit_call_intrinsic(GenContext *c, unsigned intrinsic_id, LLVMTypeRef *types, unsigned type_count, LLVMValueRef *values, unsigned arg_count); @@ -228,7 +229,8 @@ void llvm_emit_extern_decl(GenContext *context, Decl *decl); LLVMValueRef llvm_emit_const_aggregate(GenContext *c, Expr *expr, bool *modified); LLVMValueRef llvm_emit_int_comparison(GenContext *c, Type *lhs_type, Type *rhs_type, LLVMValueRef lhs_value, LLVMValueRef rhs_value, BinaryOp binary_op); -LLVMValueRef llvm_emit_is_no_error(GenContext *c, LLVMValueRef error); + +LLVMValueRef llvm_emit_is_no_error_value(GenContext *c, BEValue *value); void llvm_emit_len_for_expr(GenContext *c, BEValue *be_value, BEValue *expr_to_len); LLVMValueRef llvm_emit_load_aligned(GenContext *context, LLVMTypeRef type, LLVMValueRef pointer, AlignSize alignment, const char *name); void llvm_emit_local_var_alloca(GenContext *c, Decl *decl); diff --git a/src/compiler/llvm_codegen_module.c b/src/compiler/llvm_codegen_module.c index 793695395..755aad075 100644 --- a/src/compiler/llvm_codegen_module.c +++ b/src/compiler/llvm_codegen_module.c @@ -5,65 +5,65 @@ #include "llvm_codegen_internal.h" -void gencontext_begin_module(GenContext *context) +void gencontext_begin_module(GenContext *c) { - assert(!context->module && "Expected no module"); + assert(!c->module && "Expected no module"); - asprintf(&context->ir_filename, "%.*s.ll", (int)strlen(context->ast_context->file->name) - 3, context->ast_context->file->name); - asprintf(&context->object_filename, "%.*s.o", (int)strlen(context->ast_context->file->name) - 3, context->ast_context->file->name); + c->ir_filename = strformat("%.*s.ll", (int)strlen(c->ast_context->file->name) - 3, c->ast_context->file->name); + c->object_filename = strformat("%.*s.o", (int)strlen(c->ast_context->file->name) - 3, c->ast_context->file->name); - const char *full_path = context->ast_context->file->full_path; - char *mangled_module_name = strformat("%s-%s", context->ast_context->module->name->module, context->ast_context->file->name); - context->module = LLVMModuleCreateWithNameInContext(mangled_module_name, context->context); - LLVMSetModuleDataLayout(context->module, target_data_layout()); - LLVMSetSourceFileName(context->module, full_path, strlen(context->ast_context->file->full_path)); - LLVMTypeRef options_type = LLVMInt8TypeInContext(context->context); + const char *full_path = c->ast_context->file->full_path; + char *mangled_module_name = strformat("%s-%s", c->ast_context->module->name->module, c->ast_context->file->name); + c->module = LLVMModuleCreateWithNameInContext(mangled_module_name, c->context); + LLVMSetModuleDataLayout(c->module, target_data_layout()); + LLVMSetSourceFileName(c->module, full_path, strlen(c->ast_context->file->full_path)); + LLVMTypeRef options_type = LLVMInt8TypeInContext(c->context); - if (build_options.pic == PIC_BIG || build_options.pic == PIC_SMALL) + if (c->build_target->pic == PIC_BIG || c->build_target->pic == PIC_SMALL) { static const char *pic_level = "PIC Level"; - LLVMMetadataRef setting = LLVMValueAsMetadata(LLVMConstInt(options_type, build_options.pic, false)); - LLVMAddModuleFlag(context->module, LLVMModuleFlagBehaviorOverride, pic_level, strlen(pic_level), setting); + LLVMMetadataRef setting = LLVMValueAsMetadata(LLVMConstInt(options_type, c->build_target->pic, false)); + LLVMAddModuleFlag(c->module, LLVMModuleFlagBehaviorOverride, pic_level, strlen(pic_level), setting); } - if (build_options.pie == PIE_BIG || build_options.pie == PIE_SMALL) + if (c->build_target->pie == PIE_BIG || c->build_target->pie == PIE_SMALL) { static const char *pie_level = "PIE Level"; - LLVMMetadataRef setting = LLVMValueAsMetadata(LLVMConstInt(options_type, build_options.pie, false)); - LLVMAddModuleFlag(context->module, LLVMModuleFlagBehaviorOverride, pie_level, strlen(pie_level), setting); + LLVMMetadataRef setting = LLVMValueAsMetadata(LLVMConstInt(options_type, c->build_target->pie, false)); + LLVMAddModuleFlag(c->module, LLVMModuleFlagBehaviorOverride, pie_level, strlen(pie_level), setting); } - LLVMSetTarget(context->module, build_target.target_triple); - if (build_options.debug_info != DEBUG_INFO_NONE) + LLVMSetTarget(c->module, build_target.target_triple); + if (c->build_target->debug_info != DEBUG_INFO_NONE) { - const char *filename = context->ast_context->file->name; - const char *dir_path = context->ast_context->file->dir_path; + const char *filename = c->ast_context->file->name; + const char *dir_path = c->ast_context->file->dir_path; // Set runtime version here. - context->debug.runtime_version = 1; - context->debug.builder = LLVMCreateDIBuilder(context->module); - context->debug.file = LLVMDIBuilderCreateFile(context->debug.builder, filename, strlen(filename), dir_path, strlen(dir_path)); + c->debug.runtime_version = 1; + c->debug.builder = LLVMCreateDIBuilder(c->module); + c->debug.file = LLVMDIBuilderCreateFile(c->debug.builder, filename, strlen(filename), dir_path, strlen(dir_path)); - bool is_optimized = build_options.optimization_level != OPTIMIZATION_NONE; + bool is_optimized = c->build_target->optimization_level != OPTIMIZATION_NONE; const char *dwarf_flags = ""; unsigned runtime_version = 1; - LLVMDWARFEmissionKind emission_kind = build_options.debug_info == DEBUG_INFO_FULL ? LLVMDWARFEmissionFull : LLVMDWARFEmissionLineTablesOnly; - context->debug.compile_unit = LLVMDIBuilderCreateCompileUnit(context->debug.builder, LLVMDWARFSourceLanguageC, - context->debug.file, DWARF_PRODUCER_NAME, - strlen(DWARF_PRODUCER_NAME), is_optimized, - dwarf_flags, strlen(dwarf_flags), - runtime_version, "" /* split name */, 0 /* len */, - emission_kind, /* dwo */0, /* inlining */0, + LLVMDWARFEmissionKind emission_kind = c->build_target->debug_info == DEBUG_INFO_FULL ? LLVMDWARFEmissionFull : LLVMDWARFEmissionLineTablesOnly; + c->debug.compile_unit = LLVMDIBuilderCreateCompileUnit(c->debug.builder, LLVMDWARFSourceLanguageC, + c->debug.file, DWARF_PRODUCER_NAME, + strlen(DWARF_PRODUCER_NAME), is_optimized, + dwarf_flags, strlen(dwarf_flags), + runtime_version, "" /* split name */, 0 /* len */, + emission_kind, /* dwo */0, /* inlining */0, /* debug for profiling */0 #if LLVM_VERSION_MAJOR > 10 , "", 0, "", 0 #endif - ); + ); } // Setup all types. Not thread-safe, but at this point in time we can assume a single context. // We need to remove the context from the cache after this. // This would seem to indicate that we should change Type / actual type. - context->block_global_unique_count = 0; - context->ast_alloca_addr_space = target_alloca_addr_space(); + c->block_global_unique_count = 0; + c->ast_alloca_addr_space = target_alloca_addr_space(); VECEACH(compiler.type, i) { diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index 0279d718c..b38da0194 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -64,9 +64,9 @@ static LLVMValueRef llvm_emit_decl(GenContext *c, Ast *ast) } else { - Type *type = decl->type->canonical; + Type *type = type_flatten(decl->type); // Normal case, zero init. - if (type_is_builtin(type->type_kind) || type->type_kind == TYPE_POINTER) + if (type_is_builtin(type->type_kind) || type->type_kind == TYPE_POINTER || type->type_kind == TYPE_VARARRAY) { llvm_emit_store(c, decl, LLVMConstNull(alloc_type)); } @@ -1029,7 +1029,7 @@ static inline void gencontext_emit_assume(GenContext *c, Expr *expr) static inline void gencontext_emit_assert_stmt(GenContext *c, Ast *ast) { - if (build_options.debug_mode) + if (c->build_target->feature.safe_mode) { BEValue value; llvm_emit_expr(c, &value, ast->assert_stmt.expr); @@ -1174,10 +1174,7 @@ void llvm_emit_panic_on_true(GenContext *c, LLVMValueRef value, const char *pani void llvm_emit_stmt(GenContext *c, Ast *ast) { EMIT_LOC(c, ast); - if (c->catch_block == NULL) - { - c->catch_block = llvm_basic_block_new(c, "stmt_catch"); - } + assert(!c->catch_block && "Did not expect a catch block here."); switch (ast->ast_kind) { case AST_DOCS: diff --git a/src/compiler/llvm_codegen_type.c b/src/compiler/llvm_codegen_type.c index de917c127..95c29b780 100644 --- a/src/compiler/llvm_codegen_type.c +++ b/src/compiler/llvm_codegen_type.c @@ -338,7 +338,9 @@ LLVMTypeRef llvm_get_type(GenContext *c, Type *any_type) case TYPE_ERR_UNION: { LLVMTypeRef elements[2] = { llvm_get_type(c, type_usize->canonical), llvm_get_type(c, type_usize->canonical) }; - return any_type->backend_type = LLVMStructTypeInContext(c->context, elements, 2, false); + LLVMTypeRef strukt = LLVMStructCreateNamed(c->context, "error_union"); + LLVMStructSetBody(strukt, elements, 2, false); + return any_type->backend_type = strukt; } case TYPE_STRUCT: case TYPE_UNION: @@ -380,7 +382,17 @@ LLVMTypeRef llvm_get_type(GenContext *c, Type *any_type) return any_type->backend_type = array_type; } case TYPE_VARARRAY: - return any_type->backend_type = llvm_get_type(c, type_get_ptr(any_type->array.base)); + { + LLVMTypeRef size_type = llvm_get_type(c, type_usize); + LLVMTypeRef capacity_type = llvm_get_type(c, type_usize); + LLVMTypeRef func_type = llvm_get_type(c, type_voidptr); + LLVMTypeRef ptr_type = LLVMPointerType(llvm_get_type(c, any_type->array.base), 0); + LLVMTypeRef types[4] = { size_type, capacity_type, func_type, ptr_type }; + LLVMTypeRef array_type = LLVMStructCreateNamed(c->context, any_type->name); + LLVMStructSetBody(array_type, types, 4, false); + any_type->backend_aux_type = array_type; + return any_type->backend_type = LLVMPointerType(array_type, 0); + } case TYPE_VECTOR: return any_type->backend_type = LLVMVectorType(llvm_get_type(c, any_type->vector.base), any_type->vector.len); case TYPE_COMPLEX: diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 1e1898e41..fda8baf43 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -277,6 +277,7 @@ static Expr *parse_post_unary(Context *context, Expr *left) Expr *unary = EXPR_NEW_TOKEN(EXPR_POST_UNARY, context->tok); unary->post_expr.expr = left; unary->post_expr.operator = post_unaryop_from_token(context->tok.type); + unary->span.loc = left->span.loc; advance(context); return unary; } @@ -331,6 +332,7 @@ static Expr *parse_grouping_expr(Context *context, Expr *left) * initializer * : initializer_list * | expr + * | void * ; * * @param context @@ -338,6 +340,14 @@ static Expr *parse_grouping_expr(Context *context, Expr *left) */ Expr *parse_initializer(Context *context) { + if (TOKEN_IS(TOKEN_VOID)) + { + Expr *expr = EXPR_NEW_TOKEN(EXPR_UNDEF, context->tok); + expr->type = type_void; + expr->resolve_status = RESOLVE_DONE; + advance(context); + return expr; + } if (TOKEN_IS(TOKEN_LBRACE)) { return parse_initializer_list(context); diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 7e01ea41d..191ef6b74 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -3,37 +3,60 @@ static Decl *parse_const_declaration(Context *context, Visibility visibility); -static bool context_next_up_is_type_with_path(Context *context) +/** + * Walk forward through the token stream to identify a type on the format: foo::bar::Type + * + * @return true if there is a type at the end. + */ +static bool context_next_is_type_with_path_prefix(Context *context) { + // We assume it's called after "foo::" parsing. + assert(context->tok.type == TOKEN_IDENT && context->next_tok.type == TOKEN_SCOPE); + TokenId current = context->next_tok.id; - TokenType tok = context->next_tok.type; while (1) { + TokenType tok; + + // 1. Step past the '::' and any following comment (doc comments are not allowed here!) while (1) { current.index += 1; tok = TOKITYPE(current); - if (tok == TOKEN_COMMENT || tok == TOKEN_DOC_COMMENT) continue; - if (tok != TOKEN_IDENT) goto DONE_CHECK; - break; + if (tok != TOKEN_COMMENT) break; } + + // 2. Check that we have an ident, otherwise if + // we see a type token, we're done and return true + // on any other + if (tok != TOKEN_IDENT) return tok == TOKEN_TYPE_IDENT; + + // 3. Now we've confirmed that there is an ident, step past it + // and any following comments. while (1) { current.index += 1; tok = TOKITYPE(current); - if (tok == TOKEN_COMMENT || tok == TOKEN_DOC_COMMENT) continue; - if (tok != TOKEN_SCOPE) goto DONE_CHECK; - break; + if (tok != TOKEN_COMMENT) break; } + + // 4. If we don't see '::' after an ident we're done. + // And we know it's not a type. + if (tok != TOKEN_SCOPE) return false; + + // 5. Do another pass } - DONE_CHECK: - return tok == TOKEN_TYPE_IDENT; } /** - * Walk until we find the first top level construct. - * (Note that this is the slow path, so no need to inline) + * Walk until we find the first top level construct, the current heuristic is this: + * public, typedef, struct, import, union, extern, enum, generic, attribute, define + * are *always* sync points. + * + * func, any type, CT_IDENT, CT_TYPE_IDENT, $if, $for, $switch, generic, + * doc comment start, asm, typeof, TYPE_IDENT, local, const, IDENT + * - are sync points only if they appear in the first column. */ void recover_top_level(Context *context) { @@ -43,17 +66,55 @@ void recover_top_level(Context *context) switch (context->tok.type) { case TOKEN_PUBLIC: - case TOKEN_FUNC: - case TOKEN_CONST: case TOKEN_TYPEDEF: case TOKEN_STRUCT: case TOKEN_IMPORT: case TOKEN_UNION: - case TOKEN_MACRO: case TOKEN_EXTERN: case TOKEN_ENUM: - case TOKEN_ERR: + case TOKEN_GENERIC: + case TOKEN_ATTRIBUTE: + case TOKEN_DEFINE: return; + case TOKEN_IDENT: // Incr arrays only + case TOKEN_LOCAL: + case TOKEN_CONST: + case TOKEN_ASM: + case TOKEN_TYPEOF: + case TOKEN_CT_ASSERT: + case TOKEN_CT_TYPE_IDENT: + case TOKEN_DOCS_START: + case TOKEN_TYPE_IDENT: + case TOKEN_CT_IDENT: + case TOKEN_CT_IF: + case TOKEN_CT_FOR: + case TOKEN_CT_SWITCH: + case TOKEN_FUNC: + case TOKEN_VOID: + case TOKEN_BOOL: + case TOKEN_CHAR: + case TOKEN_DOUBLE: + case TOKEN_FLOAT: + case TOKEN_HALF: + case TOKEN_ICHAR: + case TOKEN_INT: + case TOKEN_IPTR: + case TOKEN_IPTRDIFF: + case TOKEN_ISIZE: + case TOKEN_LONG: + case TOKEN_SHORT: + case TOKEN_UINT: + case TOKEN_ULONG: + case TOKEN_UPTR: + case TOKEN_UPTRDIFF: + case TOKEN_USHORT: + case TOKEN_USIZE: + case TOKEN_QUAD: + case TOKEN_TYPEID: + // Only recover if this is in the first col. + if (TOKLOC(context->tok)->col == 1) return; + advance(context); + break; default: advance(context); break; @@ -886,7 +947,7 @@ bool parse_next_is_decl(Context *context) | (next_tok == TOKEN_CONST_IDENT); case TOKEN_IDENT: if (next_tok != TOKEN_SCOPE) return false; - return context_next_up_is_type_with_path(context); + return context_next_is_type_with_path_prefix(context); default: return false; } @@ -918,10 +979,10 @@ bool parse_next_is_case_type(Context *context) case TOKEN_CT_TYPE_IDENT: case TOKEN_ERR: case TOKEN_TYPEID: - return (next_tok == TOKEN_STAR) | (next_tok == TOKEN_LBRACKET) | (next_tok == TOKEN_COLON) | (next_tok == TOKEN_EOS); + return (next_tok == TOKEN_STAR) | (next_tok == TOKEN_LBRACKET) | (next_tok == TOKEN_COMMA) | (next_tok == TOKEN_COLON) | (next_tok == TOKEN_EOS); case TOKEN_IDENT: if (next_tok != TOKEN_SCOPE) return false; - return context_next_up_is_type_with_path(context); + return context_next_is_type_with_path_prefix(context); default: return false; } @@ -1281,14 +1342,16 @@ static inline Decl *parse_generics_declaration(Context *context, Visibility visi advance(context); COMMA_RPAREN_OR(poisoned_decl); } - CONSUME_OR(TOKEN_LBRACE, poisoned_decl); Ast **cases = NULL; - if (!parse_switch_body(context, &cases, TOKEN_CASE, TOKEN_DEFAULT)) return poisoned_decl; + if (!parse_switch_body(context, &cases, TOKEN_CASE, TOKEN_DEFAULT, true)) return poisoned_decl; decl->generic_decl.cases = cases; decl->generic_decl.parameters = parameters; return decl; } +/** + * define_decl ::= DEFINE path? (IDENT | TYPE_IDENT | CONST_IDENT) '(' expr (',' expr)* ')' as (IDENT | TYPE_IDENT | CONST_IDENT) + */ static inline Decl *parse_define(Context *context, Visibility visibility) { if (context->next_tok.type == TOKEN_CT_TYPE_IDENT || context->next_tok.type == TOKEN_CT_IDENT) diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index a29f6f1b8..c82e1e426 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -244,7 +244,7 @@ static inline Ast* parse_catch_stmt(Context *context) if (TOKEN_IS(TOKEN_LBRACE) && (context->next_tok.type == TOKEN_CASE || context->next_tok.type == TOKEN_DEFAULT)) { catch_stmt->catch_stmt.is_switch = true; - if (!parse_switch_body(context, &catch_stmt->catch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT)) return poisoned_ast; + if (!parse_switch_body(context, &catch_stmt->catch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT, false)) return poisoned_ast; return catch_stmt; } @@ -341,7 +341,7 @@ static bool parse_type_or_expr(Context *context, TypeInfo **type_info, Expr **ex * | CAST type ':' cast_stmts * ; */ -static inline Ast* parse_case_stmt(Context *context, TokenType case_type, TokenType default_type) +static inline Ast* parse_case_stmt(Context *context, TokenType case_type, TokenType default_type, bool allow_multiple_values) { Ast *ast = AST_NEW_TOKEN(AST_CASE_STMT, context->tok); advance(context); @@ -357,6 +357,19 @@ static inline Ast* parse_case_stmt(Context *context, TokenType case_type, TokenT { ast->case_stmt.expr = expr; } + if (type && allow_multiple_values && try_consume(context, TOKEN_COMMA)) + { + ast->case_stmt.is_type_list = true; + TypeInfo **type_infos = NULL; + vec_add(type_infos, type); + while (1) + { + type = TRY_TYPE_OR(parse_type(context), false); + vec_add(type_infos, type); + if (!try_consume(context, TOKEN_COMMA)) break; + } + ast->case_stmt.type_infos = type_infos; + } TRY_CONSUME(TOKEN_COLON, "Missing ':' after case"); extend_ast_with_prev_token(context, ast); ast->case_stmt.body = TRY_AST(parse_case_stmts(context, case_type, default_type)); @@ -386,7 +399,8 @@ static inline Ast *parse_default_stmt(Context *context, TokenType case_type, Tok * | default_stmt switch body * ; */ -bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type) +bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type, + bool allow_multiple_values) { CONSUME_OR(TOKEN_LBRACE, false); while (!try_consume(context, TOKEN_RBRACE)) @@ -395,7 +409,7 @@ bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, Toke TokenType next = context->tok.type; if (next == case_type) { - result = TRY_AST_OR(parse_case_stmt(context, case_type, default_type), false); + result = TRY_AST_OR(parse_case_stmt(context, case_type, default_type, allow_multiple_values), false); } else if (next == default_type) { @@ -425,7 +439,7 @@ static inline Ast* parse_switch_stmt(Context *context) switch_ast->switch_stmt.cond = TRY_EXPR_OR(parse_decl_expr_list(context), poisoned_ast); CONSUME_OR(TOKEN_RPAREN, poisoned_ast); - if (!parse_switch_body(context, &switch_ast->switch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT)) return poisoned_ast; + if (!parse_switch_body(context, &switch_ast->switch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT, false)) return poisoned_ast; return switch_ast; } @@ -892,7 +906,7 @@ static inline Ast* parse_ct_switch_stmt(Context *context) TokenType next = context->tok.type; if (next == TOKEN_CT_CASE) { - result = TRY_AST_OR(parse_case_stmt(context, TOKEN_CT_CASE, TOKEN_CT_DEFAULT), poisoned_ast); + result = TRY_AST_OR(parse_case_stmt(context, TOKEN_CT_CASE, TOKEN_CT_DEFAULT, true), poisoned_ast); } else if (next == TOKEN_CT_DEFAULT) { diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index fb545dea7..8a2a6330c 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -47,7 +47,9 @@ void recover_top_level(Context *context); Expr *parse_decl_expr_list(Context *context); Ast* parse_compound_stmt(Context *context); Ast *parse_jump_stmt_no_eos(Context *context); -bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type); + +bool parse_switch_body(Context *context, Ast ***cases, TokenType case_type, TokenType default_type, + bool allow_multiple_values); Expr *parse_expression_list(Context *context); Decl *parse_decl_after_type(Context *context, bool local, TypeInfo *type); bool parse_param_list(Context *context, Expr ***result, bool allow_type, TokenType end_type); diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index eaf67bc6c..f099eeaf3 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -5,8 +5,6 @@ #include "compiler_internal.h" #include "bigint.h" -#define EXIT_T_MISMATCH() return sema_type_mismatch(context, left, canonical, cast_type) - #define FLOAT32_LIMIT 340282346638528859811704183484516925440.0000000000000000 #define FLOAT64_LIMIT 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0000000000000000 #define FLOAT16_LIMIT 65504 @@ -30,7 +28,7 @@ static inline bool insert_runtime_cast_unless_const(Expr *expr, CastKind kind, T return insert_cast(expr, kind, type); } -static bool sema_type_mismatch(Context *context, Expr *expr, Type *type, CastType cast_type) +static bool sema_type_mismatch(Expr *expr, Type *type, CastType cast_type) { Type *expr_type = expr->type; if (expr_type == type_typeinfo) @@ -453,13 +451,6 @@ CastKind cast_to_bool_kind(Type *type) UNREACHABLE } -bool cast_to_bool_implicit(Expr *expr) -{ - assert(expr->resolve_status == RESOLVE_DONE); - if (cast_to_bool_kind(expr->type) == CAST_ERROR) return false; - return cast(expr, type_bool); -} - bool cast_may_explicit(Type *from_type, Type *to_type) { // 1. We flatten the distinct types, since they should be freely convertible @@ -546,6 +537,9 @@ bool cast_may_explicit(Type *from_type, Type *to_type) UNREACHABLE } +/** + * Can the conversion occur implicitly? + */ bool cast_may_implicit(Type *from_type, Type *to_type) { Type *from = from_type->canonical; @@ -564,8 +558,6 @@ bool cast_may_implicit(Type *from_type, Type *to_type) if (type_is_float(from)) { // This works because the type_size of FXX = 0 - unsigned a = type_size(to); - unsigned b = type_size(from); return type_size(to) >= type_size(from); } return false; @@ -654,12 +646,17 @@ bool cast_may_implicit(Type *from_type, Type *to_type) } + // 8. Check if we may cast this to bool. It is safe for many types. if (to->type_kind == TYPE_BOOL) { return cast_to_bool_kind(from) != CAST_ERROR; } - // TODO struct embedding + // 9. Substruct cast, if the first member is inline, see if we can cast to this member. + if (type_is_substruct(from)) + { + return cast_may_implicit(from->decl->strukt.members[0]->type, to); + } return false; } @@ -796,14 +793,6 @@ bool cast(Expr *expr, Type *to_type) if (canonical->type_kind == TYPE_ERRTYPE) return insert_cast(expr, CAST_EUER, to_type); break; case TYPE_IXX: - // Compile time integers may convert into ints, floats, bools - if (expr->expr_kind != EXPR_CONST && !expr->reeval) - { - expr->resolve_status = RESOLVE_NOT_DONE; - expr->reeval = true; - TODO - // return sema_analyse_expr(context, to_type, expr); - } if (type_is_integer(canonical)) return int_literal_to_int(expr, canonical, to_type); if (type_is_float(canonical)) return int_literal_to_float(expr, canonical, to_type); if (canonical == type_bool) return int_literal_to_bool(expr, to_type); @@ -826,14 +815,6 @@ bool cast(Expr *expr, Type *to_type) if (canonical->type_kind == TYPE_POINTER) return int_to_pointer(expr, to_type); break; case ALL_FLOATS: - // Compile time integers may convert into ints, floats, bools - if (from_type->type_kind == TYPE_FXX && expr->expr_kind != EXPR_CONST && !expr->reeval) - { - expr->resolve_status = RESOLVE_NOT_DONE; - expr->reeval = true; - TODO -// return sema_analyse_expr(context, to_type, expr); - } if (type_is_integer(canonical)) return float_to_integer(expr, canonical, to_type); if (canonical == type_bool) return float_to_bool(expr, to_type); if (type_is_float(canonical)) return float_to_float(expr, canonical, to_type); @@ -860,9 +841,8 @@ bool cast(Expr *expr, Type *to_type) if (canonical->type_kind == TYPE_ARRAY || canonical->type_kind == TYPE_STRUCT || canonical->type_kind == TYPE_UNION) { return insert_cast(expr, CAST_STST, to_type); - } + } // Starting in a little while... break; - UNREACHABLE case TYPE_STRLIT: canonical = type_flatten(canonical); if (canonical->type_kind == TYPE_POINTER) return insert_cast(expr, CAST_STRPTR, to_type); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 5dd22f2cd..038adf181 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -951,10 +951,64 @@ static inline bool sema_analyse_global(Context *context, Decl *decl) static inline bool sema_analyse_generic(Context *context, Decl *decl) { - TODO + // 1. If it has a return type, make sure it resolves. + if (decl->generic_decl.rtype && !sema_resolve_type_info(context, decl->generic_decl.rtype)) return false; + + unsigned param_count = vec_size(decl->generic_decl.parameters); + if (param_count < 1) + { + SEMA_ERROR(decl, "A generic function needs at least 1 parameter."); + return false; + } + Ast **cases = decl->generic_decl.cases; + + bool default_has_been_found = false; + VECEACH(cases, i) + { + Ast *generic_case = cases[i]; + if (generic_case->ast_kind == AST_CASE_STMT) + { + if (!generic_case->case_stmt.is_type) + { + SEMA_ERROR(generic_case->case_stmt.expr, "Expected a type as the argument."); + return false; + } + if (!generic_case->case_stmt.is_type_list) + { + TypeInfo **type_infos = VECNEW(TypeInfo *, 2); + vec_add(type_infos, generic_case->case_stmt.type_info); + generic_case->case_stmt.type_infos = type_infos; + generic_case->case_stmt.is_type_list = true; + } + TypeInfo **type_infos = generic_case->case_stmt.type_infos; + unsigned args = vec_size(type_infos); + for (unsigned j = 0; j < args; j++) + { + if (!sema_resolve_type_info(context, type_infos[j])) return false; + } + if (args != param_count) + { + if (param_count == 1) + { + SEMA_ERROR(type_infos[1], "Expected a single type as the argument."); + return false; + } + SEMA_ERROR(type_infos[args - 1], "Expected %d types in the case statement.", param_count); + return false; + } + continue; + } + assert(generic_case->ast_kind == AST_DEFAULT_STMT); + if (default_has_been_found) + { + SEMA_ERROR(generic_case, "More than one default statement found."); + return false; + } + } return true; } + static inline bool sema_analyse_define(Context *context, Decl *decl) { Path *path = decl->generic_decl.path; @@ -963,17 +1017,24 @@ static inline bool sema_analyse_define(Context *context, Decl *decl) } - -static inline bool sema_analyse_error(Context *context __unused, Decl *decl) +/** + * Semantic analysis on an error first checks the internals as if it was + * a struct, then checks that the size is not exceeded and adds padding. + */ +static inline bool sema_analyse_error(Context *context, Decl *decl) { + // 1. Step one is to analyze the error as it it was a regular struct. if (!sema_analyse_struct_union(context, decl)) return false; - ByteSize error_full_size = type_size(type_voidptr); + + // 2. Because an error is always pointer sized, we check so that it isn't exceeded. + ByteSize error_full_size = type_size(type_uptr); if (decl->strukt.size > error_full_size) { SEMA_ERROR(decl, "Error type may not exceed pointer size (%d bytes) it was %d bytes.", error_full_size, decl->strukt.size); return false; } + // 3. If the size is smaller than than pointer sized, we add padding. if (decl->strukt.size < error_full_size) { decl->strukt.padding = error_full_size - decl->strukt.size; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index e765e0a8d..df8f9efd0 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -191,7 +191,6 @@ static inline bool sema_cast_ident_rvalue(Context *context, Type *to, Expr *expr case DECL_VAR: break; case DECL_DISTINCT: - TODO case DECL_TYPEDEF: UNREACHABLE case DECL_POISONED: @@ -273,7 +272,7 @@ static ExprFailableStatus expr_is_failable(Expr *expr) } -static inline bool sema_type_error_on_binop(Context *context, Expr *expr) +static inline bool sema_type_error_on_binop(Expr *expr) { const char *c = token_type_to_string(binaryop_to_token(expr->binary_expr.operator)); SEMA_ERROR(expr, "%s is not defined in the expression '%s' %s '%s'.", @@ -932,7 +931,22 @@ static inline bool sema_expr_analyse_var_call(Context *context, Type *to, Expr * } -static inline bool sema_expr_analyse_generic_call(Context *context, Type *to, Expr *expr) { TODO }; +static inline bool sema_expr_analyse_generic_call(Context *context, Type *to, Decl *decl, Expr *expr) +{ + Expr **arguments = expr->call_expr.arguments; + TokenId *parameter_list = decl->generic_decl.parameters; + if (vec_size(parameter_list) != vec_size(arguments)) + { + SEMA_ERROR(expr, "Expected %d parameter(s) to the generic function.", vec_size(parameter_list)); + return false; + } + VECEACH(arguments, i) + { + if (!sema_analyse_expr(context, NULL, arguments[i])) return false; + } + + TODO +}; @@ -1244,7 +1258,7 @@ static inline bool sema_expr_analyse_call(Context *context, Type *to, Expr *expr decl = func_expr->access_expr.ref; if (decl->decl_kind == DECL_FUNC) { - expr->call_expr.is_struct_function = true; + expr->call_expr.is_type_method = true; struct_var = expr_new(EXPR_UNARY, func_expr->access_expr.parent->span); struct_var->unary_expr.expr = func_expr->access_expr.parent; struct_var->unary_expr.operator = UNARYOP_ADDR; @@ -1279,7 +1293,7 @@ static inline bool sema_expr_analyse_call(Context *context, Type *to, Expr *expr case DECL_MACRO: UNREACHABLE case DECL_GENERIC: - return sema_expr_analyse_generic_call(context, to, expr); + return sema_expr_analyse_generic_call(context, to, decl, expr); case DECL_POISONED: return false; default: @@ -1855,7 +1869,8 @@ static inline bool sema_expr_analyse_member_access(Context *context, Expr *expr) } if (name == kw_nameof) { - TODO + expr_rewrite_to_string(expr, ref->name); + return true; } if (name == kw_qnameof) { @@ -1942,6 +1957,7 @@ CHECK_DEEPER: switch (type->type_kind) { case TYPE_SUBARRAY: + case TYPE_VARARRAY: if (kw == kw_sizeof) { expr_rewrite_to_int_const(expr, type_usize, type_size(type)); @@ -1975,7 +1991,7 @@ CHECK_DEEPER: break; default: NO_MATCH: - SEMA_ERROR(expr, "Cannot access '%s' on '%s'", TOKSTR(expr->access_expr.sub_element), type_to_error_string(parent_type)); + SEMA_ERROR(expr, "There is no member or method '%s' on '%s'", TOKSTR(expr->access_expr.sub_element), type_to_error_string(parent_type)); return false; } @@ -3183,7 +3199,7 @@ static bool binary_arithmetic_promotion(Context *context, Expr *left, Expr *righ { if (!error_message) { - return sema_type_error_on_binop(context, parent); + return sema_type_error_on_binop(parent); } SEMA_ERROR(parent, error_message, type_to_error_string(left_type), type_to_error_string(right_type)); return false; @@ -3191,7 +3207,7 @@ static bool binary_arithmetic_promotion(Context *context, Expr *left, Expr *righ return cast_implicit(left, max) & cast_implicit(right, max); } -static bool sema_check_int_type_fit(Context *context, Expr *expr, Type *target_type) +static bool sema_check_int_type_fit(Expr *expr, Type *target_type) { if (!target_type) return true; Type *type = expr->type->canonical; @@ -3236,7 +3252,7 @@ static bool sema_expr_analyse_sub(Context *context, Type *to, Expr *expr, Expr * expr_unify_binary_properties(expr, left, right); // 3d. Otherwise check against assigned type so for example short x = &p - &q is an error. - return sema_check_int_type_fit(context, expr, to); + return sema_check_int_type_fit(expr, to); } right_type = right->type->canonical; @@ -3543,7 +3559,7 @@ static bool sema_expr_analyse_bit(Context *context, Type *to, Expr *expr, Expr * // 2. Check that both are integers. if (!both_any_integer(left, right)) { - return sema_type_error_on_binop(context, expr); + return sema_type_error_on_binop(expr); } // 3. Promote to the same type. @@ -3596,7 +3612,7 @@ static bool sema_expr_analyse_shift(Context *context, Type *to, Expr *expr, Expr // 3. Only integers may be shifted. if (!both_any_integer(left, right)) { - return sema_type_error_on_binop(context, expr); + return sema_type_error_on_binop(expr); } // 4. Promote lhs using the usual numeric promotion. @@ -3697,7 +3713,7 @@ static bool sema_expr_analyse_shift_assign(Context *context, Expr *expr, Expr *l } // 3. Only integers may be shifted. - if (!both_any_integer(left, right)) return sema_type_error_on_binop(context, expr); + if (!both_any_integer(left, right)) return sema_type_error_on_binop(expr); // 4. For a constant right hand side we will make a series of checks. if (is_const(right)) @@ -4673,7 +4689,6 @@ static Expr *expr_copy_from_macro(Context *context, Expr *source_expr) MACRO_COPY_TYPE(expr->typeid_expr); return expr; case EXPR_IDENTIFIER: - // TODO return expr; case EXPR_CALL: MACRO_COPY_EXPR(expr->call_expr.function); @@ -4909,25 +4924,8 @@ static Ast *ast_copy_from_macro(Context *context, Ast *source) -static inline bool sema_expr_analyse_macro_ident(Context *context, Type *to, Expr *expr) -{ - return false; - /* - Expr *inner = expr->macro_ident; - switch (inner->expr_kind) - { - case EXPR_CALL: - return sema_expr_analyse_macro_call(context, to, expr, inner); - case EXPR_ACCESS: - case EXPR_IDENTIFIER: - // Allow @f unrolling? - default: - SEMA_ERROR(expr, "Expected a macro name after '@'"); - return false; - }*/ -} -static inline bool sema_expr_analyse_type(Context *context, Type *to, Expr *expr) +static inline bool sema_expr_analyse_type(Context *context, Expr *expr) { if (!sema_resolve_type_info(context, expr->typeid_expr)) { @@ -5121,7 +5119,7 @@ static inline bool sema_analyse_expr_dispatch(Context *context, Type *to, Expr * if (expr->expr_kind == EXPR_UNARY) expr->failable = expr->unary_expr.expr->failable; return true; case EXPR_TYPEID: - return sema_expr_analyse_type(context, to, expr); + return sema_expr_analyse_type(context, expr); case EXPR_CONST_IDENTIFIER: case EXPR_IDENTIFIER: case EXPR_MACRO_IDENTIFIER: diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 242372f94..74828b545 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -154,6 +154,10 @@ void sema_analysis_pass_decls(Context *context) { sema_analyse_decl(context, context->macros[i]); } + VECEACH(context->generics, i) + { + sema_analyse_decl(context, context->generics[i]); + } VECEACH(context->methods, i) { sema_analyse_decl(context, context->methods[i]); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index b68450e80..100ec9bbb 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -141,6 +141,18 @@ static inline bool sema_analyse_block_return_stmt(Context *context, Ast *stateme return true; } +/** + * We have the following possibilities: + * 1. return + * 2. return + * 3. return + * + * If we are in a block or a macro expansion we need to handle it differently. + * + * @param context + * @param statement + * @return + */ static inline bool sema_analyse_return_stmt(Context *context, Ast *statement) { // This might be a return in a function block or a macro which must be treated differently. @@ -148,18 +160,18 @@ static inline bool sema_analyse_return_stmt(Context *context, Ast *statement) { return sema_analyse_block_return_stmt(context, statement); } + // 1. We mark that the current scope ends with a jump. context->current_scope->jump_end = true; + Type *expected_rtype = context->rtype; + assert(expected_rtype && "We should always have known type from a function return."); + Expr *return_expr = statement->return_stmt.expr; statement->return_stmt.defer = context->current_scope->defer_last; + + // 2. First handle the plain return. if (return_expr == NULL) { - if (!expected_rtype) - { - assert(context->evaluating_macro); - context->rtype = type_void; - return true; - } if (expected_rtype->canonical != type_void) { SEMA_ERROR(statement, "Expected to return a result of type %s.", type_to_error_string(expected_rtype)); @@ -167,55 +179,62 @@ static inline bool sema_analyse_return_stmt(Context *context, Ast *statement) } return true; } - if (expected_rtype == type_void && !context->failable_return) - { - SEMA_ERROR(statement, "You can't return a value from a void function, you need to add a return type."); - return false; - } + + // 3. Evaluate the return value to be the expected return type. if (!sema_analyse_expr_of_required_type(context, expected_rtype, return_expr, context->failable_return)) return false; - if (!expected_rtype) - { - assert(context->evaluating_macro); - context->rtype = type_void; - context->active_function_for_analysis->func.function_signature.rtype->type->canonical = statement->return_stmt.expr->type->canonical; - return true; - } + assert(statement->return_stmt.expr->type->canonical == expected_rtype->canonical); + return true; } -static inline bool sema_analyse_unreachable_stmt(Context *context, Ast *statement) +static inline bool sema_analyse_unreachable_stmt(Context *context) { context->current_scope->jump_end = true; return true; } +/** + * An decl-expr-list is a list of a mixture of declarations and expressions. + * The last declaration or expression is propagated. So for example: + * + * int a = 3, b = 4, float c = 4.0 + * + * In this case the final value is 4.0 and the type is float. + */ static inline bool sema_analyse_decl_expr_list(Context *context, Expr *expr) { assert(expr->expr_kind == EXPR_DECL_LIST); Ast **dexprs = expr->dexpr_list_expr; unsigned entries = vec_size(dexprs); - for (unsigned i = 0; i < entries; i++) - { - Ast *ast = dexprs[i]; - if (!sema_analyse_statement(context, ast)) - { - return false; - } - } + + // 1. Special case, there are no entries, so the type is void if (entries == 0) { expr_set_type(expr, type_void); return true; } + + // 2. Walk through each of our declarations / expressions as if they were regular statements. + for (unsigned i = 0; i < entries; i++) + { + if (!sema_analyse_statement(context, dexprs[i])) return false; + } + + // 3. We now look at the final expression and copy its type as the type of the entire list. + // There is a subtle difference here. The expression might have a different original type + // than the expression's type. In most (all?) cases this is isn't something one uses, + // but this makes sure that analysis is correct. Ast *last = dexprs[entries - 1]; switch (last->ast_kind) { case AST_DECLARE_STMT: + // In the declaration case, the value is the type of the declaration. expr_set_type(expr, last->declare_stmt->type); break; case AST_EXPR_STMT: + // In the expression case, *copy* the expression's types. expr_copy_types(expr, last->expr_stmt); break; default: @@ -224,29 +243,50 @@ static inline bool sema_analyse_decl_expr_list(Context *context, Expr *expr) return true; } - +/** + * Analyse a conditional expression: + * + * 1. The middle statement in a for loop + * 2. The expression in an if, while + * 3. The expression in a switch + * + * @param context the current context + * @param expr the conditional to evaluate + * @param cast_to_bool if the result is to be cast to bool after + * @return true if it passes analysis. + */ static inline bool sema_analyse_cond(Context *context, Expr *expr, bool cast_to_bool) { - assert(expr->expr_kind == EXPR_DECL_LIST); + assert(expr->expr_kind == EXPR_DECL_LIST && "Conditional expressions should always be of type EXPR_DECL_LIST"); + size_t size = vec_size(expr->dexpr_list_expr); - if (!size) + + // 1. Analyse the declaration list. + if (!sema_analyse_decl_expr_list(context, expr)) return false; + + // 2. If we get "void", either through a void call or an empty list, + // signal that. + if (expr->type->type_kind == TYPE_VOID) { - SEMA_ERROR(expr, "Expected a boolean expression"); + SEMA_ERROR(expr, cast_to_bool ? "Expected a boolean expression." : "Expected an expression resulting in a value."); return false; } - if (!sema_analyse_decl_expr_list(context, expr)) return false; + // 3. We look at the last element (which is guaranteed to exist because + // the type was not void. + Ast *last = VECLAST(expr->dexpr_list_expr); - Ast *last = expr->dexpr_list_expr[size - 1]; switch (last->ast_kind) { case AST_EXPR_STMT: + // 3a. Check for failables in case of an expression. if (last->expr_stmt->failable) { SEMA_ERROR(last, "'%s!' cannot be converted into '%s'.", type_to_error_string(last->expr_stmt->type), - cast_to_bool ? "bool" : type_to_error_string(last->expr_stmt->type)); + cast_to_bool ? "bool" : type_to_error_string(last->expr_stmt->type)); } + // 3b. Cast to bool if that is needed if (cast_to_bool) { if (!cast_implicit(last->expr_stmt, type_bool)) return false; @@ -254,19 +294,23 @@ static inline bool sema_analyse_cond(Context *context, Expr *expr, bool cast_to_ return true; case AST_DECLARE_STMT: { + // 3c. The declaration case Decl *decl = last->declare_stmt; Expr *init = decl->var.init_expr; + // 3d. We expect an initialization for the last declaration. if (!init) { SEMA_ERROR(last, "Expected a declaration with initializer."); return false; } + // 3e. Expect that it isn't a failable if (init->failable && !decl->var.unwrap) { SEMA_ERROR(last, "'%s!' cannot be converted into '%s'.", type_to_error_string(last->expr_stmt->type), cast_to_bool ? "bool" : type_to_error_string(init->type)); } + // TODO document if (!decl->var.unwrap && cast_to_bool && cast_to_bool_kind(decl->var.type_info->type) == CAST_ERROR) { SEMA_ERROR(last->declare_stmt->var.init_expr, "The expression needs to be convertible to a boolean."); @@ -279,72 +323,146 @@ static inline bool sema_analyse_cond(Context *context, Expr *expr, bool cast_to_ } } +static inline bool sema_analyse_stmt_placement(Expr *cond, Ast *stmt) +{ + if (stmt->ast_kind == AST_COMPOUND_STMT) return true; + SourceLocation *end_of_cond = TOKILOC(cond->span.end_loc); + SourceLocation *start_of_then = TOKILOC(stmt->span.loc); + return end_of_cond->line == start_of_then->line; +} + +/** + * Check "while" statement, including end of line placement of a single statement. + */ static inline bool sema_analyse_while_stmt(Context *context, Ast *statement) { Expr *cond = statement->while_stmt.cond; Ast *body = statement->while_stmt.body; - context_push_scope(context); - bool success = sema_analyse_cond(context, cond, true); + + // 1. Begin our scope, this is relevant in case we have something like + // while (File *f = @getFileAndClose!()) where the macro pushes a defer into the scope. context_push_scope_with_label(context, statement->while_stmt.flow.label); + // 2. Analyze the condition + if (!sema_analyse_cond(context, cond, true)) + { + // 2a. In case of error, pop context and exit. + context_pop_scope_error(context); + return false; + } + + // 4. Push break / continue - which is independent of the scope. PUSH_BREAKCONT(statement); - success = success && sema_analyse_statement(context, body); - context_pop_defers_and_replace_ast(context, body); + + // 5. Analyse the statement + bool success = sema_analyse_statement(context, body); + + // 6. Pop break / continue POP_BREAKCONT(); + // 7. Check placement, in case of a single statement, it must be placed on the same line. + if (success && !sema_analyse_stmt_placement(cond, body)) + { + SEMA_ERROR(body, "A single statement after 'while' must be placed on the same line, or be enclosed in {}."); + context_pop_scope_error(context); + return false; + } + + // 8. Pop defers attach them to the statement if needed + context_pop_defers_and_replace_ast(context, body); + + // 9. Pop the while scope. context_pop_scope(context); - context_pop_defers_and_replace_ast(context, statement); - context_pop_scope(context); - if (!success) return false; + return success; } +/** + * Check the do ... while (...) statement. + */ static inline bool sema_analyse_do_stmt(Context *context, Ast *statement) { Expr *expr = statement->do_stmt.expr; Ast *body = statement->do_stmt.body; bool success; + + // 1. Begin pushing the scope and break / continue. context_push_scope_with_label(context, statement->do_stmt.flow.label); PUSH_BREAKCONT(statement); + + // 2. We analyze the statement. success = sema_analyse_statement(context, body); - context_pop_defers_and_replace_ast(context, body); + + // 3. Pop break / continue POP_BREAKCONT(); + + // 4. Pop any defers + context_pop_defers_and_replace_ast(context, body); + + // 5. If the current scope ended in a jump, then + // the do statement has no exit. statement->do_stmt.flow.no_exit = context->current_scope->jump_end; + + // 6. Pop the scope context_pop_scope(context); + + // 7. We can now exit if there was an error further up. if (!success) return false; - if (!statement->do_stmt.expr) return success; + // 8. Handle the do { } expression + if (!statement->do_stmt.expr) return true; + + // 9. We next handle the while test. This is its own scope. context_push_scope(context); - success = sema_analyse_expr_of_required_type(context, type_bool, expr, false); + + // 10. Try to evaluate and implicitly cast to boolean. + if (!sema_analyse_expr_of_required_type(context, type_bool, expr, false)) + { + // 10a. On failure, pop and return false. + context_pop_scope_error(context); + return false; + } + + // 11. Pop any defers in the expression. statement->do_stmt.expr = context_pop_defers_and_wrap_expr(context, expr); + + // 12. Pop the scope itself. context_pop_scope(context); - // while (1) with no break means that we've reached a statement which ends with a jump. + // 13. Check for infinite loops using do ... while (1). + // If it has no break then that means we've reached a statement which ends with a jump. if (statement->do_stmt.expr->expr_kind == EXPR_CONST && statement->do_stmt.expr->const_expr.b) { - if (statement->do_stmt.flow.no_exit && !statement->do_stmt.flow.has_break) - { - context->current_scope->jump_end = true; - } + // Unless there is a break, this won't ever exit. + context->current_scope->jump_end = !statement->do_stmt.flow.has_break; } - return success; + return true; } +/** + * Analyse a regular local declaration, e.g. int x = 123 + */ static inline bool sema_analyse_local_decl(Context *context, Decl *decl) { - assert(decl->decl_kind == DECL_VAR); + assert(decl->decl_kind == DECL_VAR && "Unexpected declaration type"); + + // TODO unify with global decl analysis + // Add a local to the current context, will throw error on shadowing. if (!sema_add_local(context, decl)) return decl_poison(decl); + + // 1. Local constants: const int FOO = 123. if (decl->var.kind == VARDECL_CONST) { Expr *init_expr = decl->var.init_expr; + // 1a. We require an init expression. if (!init_expr) { SEMA_ERROR(decl, "Constants need to have an initial value."); return false; } - if (init_expr->expr_kind == EXPR_TYPEINFO && init_expr->type_expr->resolve_status == RESOLVE_DONE - && init_expr->type_expr->type->type_kind == TYPE_VOID) + // 1b. We require defined constants + if (init_expr->expr_kind == EXPR_UNDEF) { SEMA_ERROR(decl, "Constants cannot be undefined."); return false; @@ -364,18 +482,16 @@ static inline bool sema_analyse_local_decl(Context *context, Decl *decl) bool type_is_inferred = decl->type->type_kind == TYPE_INFERRED_ARRAY; Expr *init = decl->var.init_expr; // Handle explicit undef - if (init->expr_kind == EXPR_TYPEINFO && init->type_expr->resolve_status == RESOLVE_DONE - && init->type_expr->type->type_kind == TYPE_VOID) + if (init->expr_kind == EXPR_UNDEF) { if (type_is_inferred) { SEMA_ERROR(decl->var.type_info, "Size of the array cannot be inferred with explicit undef."); return false; } - init->expr_kind = EXPR_UNDEF; - init->resolve_status = RESOLVE_DONE; goto EXIT_OK; } + if (!sema_expr_analyse_assign_right_side(context, NULL, decl->type, init, decl->var.failable || decl->var.unwrap ? FAILABLE_YES : FAILABLE_NO)) return decl_poison(decl); if (decl->type) @@ -389,6 +505,7 @@ static inline bool sema_analyse_local_decl(Context *context, Decl *decl) assert(right_side_type->type_kind == TYPE_ARRAY); decl->type = type_get_array(decl->type->array.base, right_side_type->array.len); } + if (decl->var.unwrap && !init->failable) { SEMA_ERROR(decl->var.init_expr, "A failable expression was expected here."); @@ -1770,7 +1887,7 @@ static inline bool sema_analyse_statement_inner(Context *context, Ast *statement case AST_NEXT_STMT: return sema_analyse_next_stmt(context, statement); case AST_UNREACHABLE_STMT: - return sema_analyse_unreachable_stmt(context, statement); + return sema_analyse_unreachable_stmt(context); case AST_VOLATILE_STMT: return sema_analyse_volatile_stmt(context, statement); case AST_WHILE_STMT: diff --git a/src/compiler/target.c b/src/compiler/target.c index 3909e3310..34f66d669 100644 --- a/src/compiler/target.c +++ b/src/compiler/target.c @@ -18,6 +18,8 @@ unsigned os_target_supports_float16(OsType os, ArchType arch); unsigned os_target_supports_float128(OsType os, ArchType arch); unsigned os_target_supports_vec(OsType os, ArchType arch, int bits, bool is_int); + + Target build_target = {}; int target_alloca_addr_space() @@ -34,6 +36,8 @@ static void type_dump(LLVMTargetDataRef llvm_target_data, LLVMTypeRef type) printf(" | %-3d %-3d %-3d", size, abialign, prefalign); } + + void llvm_dump(void) { static char* archs[] = { @@ -287,7 +291,7 @@ static inline void target_setup_arm_abi(void) UNREACHABLE } -static inline void target_setup_x86_abi(void) +static inline void target_setup_x86_abi(BuildTarget *target) { build_target.abi = ABI_X86; build_target.x86.is_win_api = build_target.os == OS_TYPE_WIN32; @@ -297,8 +301,10 @@ static inline void target_setup_x86_abi(void) } build_target.x86.use_soft_float = build_target.float_abi == FLOAT_ABI_SOFT; // Build target override. - if (build_options.feature.soft_float) build_target.x86.use_soft_float = true; - if (build_options.feature.no_soft_float) build_target.x86.use_soft_float = false; + if (target->feature.soft_float != SOFT_FLOAT_DEFAULT) + { + build_target.x86.use_soft_float = target->feature.soft_float == SOFT_FLOAT_YES; + } build_target.x86.is_win32_float_struct_abi = build_target.os == OS_TYPE_WIN32; build_target.x86.is_mcu_api = build_target.os == OS_TYPE_ELFIAMCU; @@ -324,8 +330,10 @@ static inline void target_setup_x86_abi(void) default: break; } - if (build_options.feature.reg_struct_return) build_target.x86.return_small_struct_in_reg_abi = true; - if (build_options.feature.stack_struct_return) build_target.x86.return_small_struct_in_reg_abi = false; + if (target->feature.struct_return != STRUCT_RETURN_DEFAULT) + { + build_target.x86.return_small_struct_in_reg_abi = target->feature.struct_return == STRUCT_RETURN_REG; + } } @@ -364,7 +372,7 @@ static char *arch_to_target_triple[ARCH_OS_TARGET_LAST + 1] = { }; -void target_setup(void) +void target_setup(BuildTarget *target) { assert(!build_target.target); @@ -377,13 +385,13 @@ void target_setup(void) build_target.target = NULL; const char *triple; - if (build_options.arch_os_target == ARCH_OS_TARGET_DEFAULT) + if (target->arch_os_target == ARCH_OS_TARGET_DEFAULT) { triple = LLVMGetDefaultTargetTriple(); } else { - triple = arch_to_target_triple[build_options.arch_os_target]; + triple = arch_to_target_triple[target->arch_os_target]; } char *err = NULL; @@ -400,7 +408,7 @@ void target_setup(void) LLVMCodeGenOptLevel level; LLVMRelocMode reloc_mode = LLVMRelocDefault; - switch (build_options.optimization_level) + switch (target->optimization_level) { case OPTIMIZATION_NOT_SET: UNREACHABLE; @@ -419,19 +427,15 @@ void target_setup(void) default: UNREACHABLE; } - if (build_options.pic == PIC_BIG || build_options.pic == PIC_SMALL || build_options.generate_lib) + if (target->pic == PIC_BIG || target->pic == PIC_SMALL || (target->type != TARGET_TYPE_EXECUTABLE && target->type != TARGET_TYPE_TEST)) { reloc_mode = LLVMRelocPIC; } - if (build_options.pic == PIC_NONE) + if (target->pic == PIC_NONE) { reloc_mode = LLVMRelocStatic; } - if (!build_options.cpu) - { - build_options.cpu = "generic"; - } /* if (!opt->features) { @@ -594,7 +598,7 @@ void target_setup(void) build_target.abi = ABI_RISCV; TODO case ARCH_TYPE_X86: - target_setup_x86_abi(); + target_setup_x86_abi(target); break; case ARCH_TYPE_X86_64: target_setup_x64_abi(); diff --git a/src/compiler/target.h b/src/compiler/target.h index 826a187b3..b66111def 100644 --- a/src/compiler/target.h +++ b/src/compiler/target.h @@ -345,4 +345,5 @@ typedef struct } Target; + extern Target build_target; \ No newline at end of file diff --git a/src/compiler/types.c b/src/compiler/types.c index cb5652632..8db82640e 100644 --- a/src/compiler/types.c +++ b/src/compiler/types.c @@ -11,7 +11,7 @@ static Type t_usz, t_isz, t_uptr, t_iptr, t_uptrdiff, t_iptrdiff; static Type t_cus, t_cui, t_cul, t_cull; static Type t_cs, t_ci, t_cl, t_cll; static Type t_voidstar, t_typeid, t_error, t_typeinfo; -static Type t_str; +static Type t_str, t_varheader; Type *type_bool = &t_u1; Type *type_void = &t_u0; @@ -50,6 +50,7 @@ Type *type_c_uint = &t_cui; Type *type_c_ulong = &t_cul; Type *type_c_ulonglong = &t_cull; Type *type_error = &t_error; +Type *type_varheader = &t_varheader; static unsigned size_subarray; static unsigned alignment_subarray; @@ -129,8 +130,22 @@ const char *type_to_error_string(Type *type) return strcat_arena(buffer, ")"); } case TYPE_COMPLEX: + switch (type->complex->type_kind) + { + case TYPE_F16: + return "complex16"; + case TYPE_F32: + return "complex32"; + case TYPE_F64: + return "complex64"; + case TYPE_F128: + return "complex128"; + default: + UNREACHABLE + } case TYPE_VECTOR: - TODO + asprintf(&buffer, "%s[<%llu>]", type_to_error_string(type->array.base), (unsigned long long)type->array.len); + return buffer; case TYPE_MEMBER: return "member"; case TYPE_TYPEINFO: @@ -800,7 +815,7 @@ Type *type_get_inferred_array(Type *arr_type) Type *type_get_vararray(Type *arr_type) { - return type_generate_subarray(arr_type, false); + return type_generate_vararray(arr_type, false); } static inline bool array_structurally_equivalent_to_struct(Type *array, Type *type) diff --git a/src/compiler_tests/tests.c b/src/compiler_tests/tests.c index 6864ec05b..5c1e0b57a 100644 --- a/src/compiler_tests/tests.c +++ b/src/compiler_tests/tests.c @@ -155,6 +155,8 @@ void test_file(void) } void compiler_tests(void) { + symtab_init(0x100000); + test_file(); test_lexer(); test_compiler(); diff --git a/src/main.c b/src/main.c index 0424091a3..a1469509a 100644 --- a/src/main.c +++ b/src/main.c @@ -18,39 +18,39 @@ int main(int argc, const char *argv[]) compiler_init(); // Parse arguments. - parse_arguments(argc, argv); + BuildOptions build_options = parse_arguments(argc, argv); - // Now we set up the symtab. - symtab_init(build_options.symtab_size); + BuildTarget build_target = {}; switch (build_options.command) { case COMMAND_INIT: - create_project(); + create_project(&build_options); break; case COMMAND_UNIT_TEST: compiler_tests(); break; case COMMAND_GENERATE_HEADERS: - build_options.compile_option = COMPILE_OUTPUT_HEADERS; - compile_files(NULL); - break; case COMMAND_COMPILE: - compile_files(NULL); + case COMMAND_COMPILE_RUN: + init_default_build_target(&build_target, &build_options, "foo.out"); + compile_files(&build_target); break; case COMMAND_BUILD: - build(); - break; - case COMMAND_COMPILE_RUN: - case COMMAND_MISSING: case COMMAND_RUN: case COMMAND_CLEAN_RUN: case COMMAND_CLEAN: case COMMAND_DIST: case COMMAND_DOCS: case COMMAND_BENCH: + init_build_target(&build_target, &build_options); + compile_files(&build_target); + break; + case COMMAND_MISSING: printf("TODO\n"); } + + print_arena_status(); free_arena(); return 0; diff --git a/test/test_suite/define/common.c3 b/test/test_suite/define/common.c3 new file mode 100644 index 000000000..5147e7197 --- /dev/null +++ b/test/test_suite/define/common.c3 @@ -0,0 +1,109 @@ +// #skip + + + +struct FooInt = Foo(int); +func void someFunctionIntBool = someFunction(int, bool); +// const int A_CONST_INT = A_CONST(int); DOESN'T WORK +// void* standard_foo = __stdin; DOESN'T WORK + + +// Define as +define __stdin as standard_foo; +define Foo(int) as FooInt; +define void someFunction(int, bool) as someFunctionIntBool; +define A_CONST(int) as A_CONST_INT; + + +// aliasof +void* standard_foo aliasof __stdin; +func void someFunctionIntBool aliasof someFunction(int, bool); +struct FooInt aliasof Foo(int); +A_CONST_INT aliasof A_CONST(int); + + +// define = +define standard_foo = __stdin; +define someFunctionIntBool = someFunction(int, bool); +define FooInt = Foo(int); +define A_CONST_INT = A_CONST(int); + +// :: +void* standard_foo :: __stdin; +func void someFunctionIntBool :: someFunction(int, bool); +struct FooInt :: Foo(int); +const int A_CONST_INT :: A_CONST(int); + + + +struct IntArray :: VarArray(int); +func IntArray* newIntArray :: var_array::newArray(int) +const int MAX_INT_CAPACITY ::MAX_INT_CAPACITY(int); + +func void foo() +{ + IntArray* array = newIntArray(); + array.append(10); +} + + +struct IntArray :: VarArray(int); +func IntArray* newIntArray :: var_array::newArray(int) +const int MAX_INT_CAPACITY ::MAX_INT_CAPACITY(int); + +func void foo() +{ + IntArray* array = newIntArray(); + array.append(10); +} + + + +struct IntArray :: VarArray(int); +func IntArray* newIntArray(int capacity) :: var_array::newArray(int) +const int MAX_INT_CAPACITY :: var_array::MAX_CAPACITY(int); + +func void foo(int max_elements) +{ + assert(max_elements <= MAX_INT_CAPACITY) + IntArray* array = newIntArray(max_elements); + array.append(10); +} + + + +define IntArray = VarArray(int); +define newIntArray = var_array::newArray; +define MAX_INT_CAPACITY = var_array::MAX_INT_CAPACITY(int); +define FOOBAR = 123; + +func void foo(int max_elements) +{ + assert(max_elements <= MAX_INT_CAPACITY) + IntArray* array = newIntArray(max_elements); + array.append(10); +} + + +void* standard_foo aliasof __stdin; +func void someFunctionIntBool aliasof someFunction(int, bool); +struct FooInt aliasof Foo(int); +A_CONST_INT aliasof A_CONST(int); + +func void foo(int max_elements) +{ + assert(max_elements <= MAX_INT_CAPACITY) + IntArray* array = newIntArray(max_elements); + array.append(10); +} + +struct IntArray = VarArray(int); +func newIntArray = var_array::newArray; +const MAX_INT_CAPACITY = var_array::MAX_INT_CAPACITY(int); + +func void foo(int max_elements) +{ + assert(max_elements <= MAX_INT_CAPACITY) + IntArray* array = newIntArray(max_elements); + array.append(10); +} diff --git a/test/test_suite/errors/rethrow.c3t b/test/test_suite/errors/rethrow.c3t index 3cf17c852..d29d1bf2e 100644 --- a/test/test_suite/errors/rethrow.c3t +++ b/test/test_suite/errors/rethrow.c3t @@ -7,23 +7,35 @@ func void! test() // #expect: rethrow.ll - %i = alloca i32 - %i1 = alloca { i64, i64 }, align 8 - %error_var = alloca { i64, i64 }, align 8 - store { i64, i64 } zeroinitializer, { i64, i64 }* %i1 - store i32 0, i32* %i - %0 = load { i64, i64 }, { i64, i64 }* %i1 - %1 = extractvalue { i64, i64 } %0, 0 - %noerr = icmp eq i64 %1, 0 - br i1 %noerr, label %after_check, label %error +entry: + %i = alloca i32, align 4 + %i1 = alloca %error_union, align 8 + %error_var = alloca %error_union, align 8 + %coerce = alloca { i64, i64 }, align 8 + store %error_union zeroinitializer, %error_union* %i1, align 8 + store i32 0, i32* %i, align 4 + %err_domain = getelementptr inbounds %error_union, %error_union* %i1, i32 0, i32 0 + %0 = load i64, i64* %err_domain, align 8 + %not_err = icmp eq i64 %0, 0 + br i1 %not_err, label %after_check, label %error + error: - store { i64, i64 } %0, { i64, i64 }* %error_var - br label %guard_block + %1 = bitcast %error_union* %error_var to i8* + %2 = bitcast %error_union* %i1 to i8* + call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 8 %1, i8* align 8 %2, i32 16, i1 false) + br label %guard_block + after_check: - %2 = load i32, i32* %i, align 4 - br label %noerr_block + %3 = load i32, i32* %i, align 4 + br label %noerr_block + guard_block: - %3 = load { i64, i64 }, { i64, i64 }* %error_var - ret { i64, i64 } %3 + %4 = bitcast %error_union* %error_var to { i64, i64 }* + %coerced = load { i64, i64 }, { i64, i64 }* %4, align 8 + ret { i64, i64 } %coerced + noerr_block: - ret { i64, i64 } zeroinitializer + %5 = bitcast { i64, i64 }* %coerce to %error_union* + store %error_union zeroinitializer, %error_union* %5, align 8 + %coerced2 = load { i64, i64 }, { i64, i64 }* %coerce, align 8 + ret { i64, i64 } %coerced2 diff --git a/test/test_suite/examples/gameoflife.c3 b/test/test_suite/examples/gameoflife.c3 new file mode 100644 index 000000000..913ba4373 --- /dev/null +++ b/test/test_suite/examples/gameoflife.c3 @@ -0,0 +1,90 @@ +module game_of_life; + +extern func void printf(char *fmt, ...); +extern func int atoi(char *val); +extern void *__stdoutp; +extern func void fflush(void *std); +extern func int rand(); +extern func void* malloc(usize size); +extern func void usleep(int time); + + +struct GameBoard +{ + int h; + int w; + char* world; + char* temp; +} + +func void GameBoard.show(GameBoard *board) +{ + + printf("\e[H"); + char* current = board.world; + for (int y = 0; y < board.h; y++) + { + for (int x = 0; x < board.w; x++) + { + printf(*current ? "\e[07m \e[m" : " "); + current++; + } + printf("\e[E"); + } + fflush(__stdoutp); +} + +func void GameBoard.evolve(GameBoard *board) +{ + for (int y = 0; y < board.h; y++) + { + for (int x = 0; x < board.w; x++) + { + int n = 0; + for (int y1 = y - 1; y1 <= y + 1; y1++) + { + for (int x1 = x - 1; x1 <= x + 1; x1++) + { + int actualX = (x1 + board.w) % board.w; + int actualY = (y1 + board.h) % board.h; + if (board.world[actualX + actualY * board.w]) n++; + } + } + if (board.world[x + y * board.w]) n--; + board.temp[x + y * board.w] = cast(n == 3 || (n == 2 && board.world[x + y * board.w]) as char); + } + } + for (int i = 0; i < board.w * board.h; i++) + { + board.world[i] = board.temp[i]; + } +} + + +func int main(int c, char** v) +{ + int w = 0; + int h = 0; + if (c > 1) w = atoi(v[1]); + if (c > 2) h = atoi(v[2]); + if (w <= 0) w = 30; + if (h <= 0) h = 30; + + GameBoard board; + board.w = w; + board.h = h; + board.world = malloc(h * w); + board.temp = malloc(h * w); + + for (int i = h * w - 1; i >= 0; i--) + { + board.world[i] = rand() % 10 == 0 ? 1 : 0; + } + for (int j = 0; j < 1000; j++) + { + board.show(); + board.evolve(); + usleep(200000); + } + return 1; +} \ No newline at end of file diff --git a/test/test_suite/functions/returning_void.c3t b/test/test_suite/functions/returning_void.c3t new file mode 100644 index 000000000..34e2911d0 --- /dev/null +++ b/test/test_suite/functions/returning_void.c3t @@ -0,0 +1,20 @@ +func void test1() +{ + int x; + if (x == 0) return; + return test1(); +} +// #expect: returning_void.ll + + %x = alloca i32, align 4 + store i32 0, i32* %x, align 4 + %0 = load i32, i32* %x, align 4 + %eq = icmp eq i32 %0, 0 + br i1 %eq, label %if.then, label %if.exit + +if.then: + ret void + +if.exit: + call void @returning_void.test1() + ret void diff --git a/test/test_suite/macros/generic_common.c3 b/test/test_suite/macros/generic_common.c3 new file mode 100644 index 000000000..f776b790b --- /dev/null +++ b/test/test_suite/macros/generic_common.c3 @@ -0,0 +1,41 @@ + + +generic abs(x) +{ + case double: + return fabs(x); + case int: + return abs(x); +} + +generic add(x, y) +{ + case double, double: + return x + y; + case int, double: + return cast(x as double) + y; +} + +generic addBad(x, y) +{ + case double: // #error: Expected 2 types in the case statement + return x + y; + case int, double: + return cast(x as double) + y; +} + +generic addBad2() // #error: generic function needs at least 1 parameter +{ + case double: + return x + y; + case int, double: + return cast(x as double) + y; +} + +generic addBad3(x) +{ + case 123: // #error: Expected a type as the argument + return x + y; + case double: + return cast(x as double) + y; +} diff --git a/test/test_suite/statements/foreach_with_error.c3t b/test/test_suite/statements/foreach_with_error.c3t index 58334a31c..bdbb80936 100644 --- a/test/test_suite/statements/foreach_with_error.c3t +++ b/test/test_suite/statements/foreach_with_error.c3t @@ -15,42 +15,42 @@ func void test() entry: %x = alloca [3 x i32], align 4 - %x1 = alloca { i64, i64 }, align 8 + %x1 = alloca %error_union, align 8 %g = alloca i32, align 4 %idx = alloca i64, align 8 %z = alloca i32, align 4 - store { i64, i64 } zeroinitializer, { i64, i64 }* %x1, align 8 + store %error_union zeroinitializer, %error_union* %x1, align 8 %0 = bitcast [3 x i32]* %x to i8* call void @llvm.memset.p0i8.i64(i8* align 4 %0, i8 0, i64 12, i1 false) store i32 0, i32* %g, align 4 - %1 = load { i64, i64 }, { i64, i64 }* %x1, align 8 - %2 = extractvalue { i64, i64 } %1, 0 - %noerr = icmp eq i64 %2, 0 - br i1 %noerr, label %after_check, label %foreach.exit + %err_domain = getelementptr inbounds %error_union, %error_union* %x1, i32 0, i32 0 + %1 = load i64, i64* %err_domain, align 8 + %not_err = icmp eq i64 %1, 0 + br i1 %not_err, label %after_check, label %foreach.exit after_check: store i64 0, i64* %idx, align 8 br label %foreach.cond foreach.cond: - %3 = load i64, i64* %idx, align 8 - %lt = icmp ult i64 %3, 3 + %2 = load i64, i64* %idx, align 8 + %lt = icmp ult i64 %2, 3 br i1 %lt, label %foreach.body, label %foreach.exit foreach.body: - %4 = getelementptr inbounds i32, [3 x i32]* %x, i64 %3 - %5 = load i32, i32* %4, align 4 - store i32 %5, i32* %z, align 4 - %6 = load i32, i32* %g, align 4 - %7 = load i32, i32* %z, align 4 - %add = add i32 %6, %7 + %3 = getelementptr inbounds i32, [3 x i32]* %x, i64 %2 + %4 = load i32, i32* %3, align 4 + store i32 %4, i32* %z, align 4 + %5 = load i32, i32* %g, align 4 + %6 = load i32, i32* %z, align 4 + %add = add i32 %5, %6 store i32 %add, i32* %g, align 4 - %8 = load { i64, i64 }, { i64, i64 }* %x1, align 8 - %9 = extractvalue { i64, i64 } %8, 0 - %noerr2 = icmp eq i64 %9, 0 - br i1 %noerr2, label %after_check3, label %voiderr + %err_domain2 = getelementptr inbounds %error_union, %error_union* %x1, i32 0, i32 0 + %7 = load i64, i64* %err_domain2, align 8 + %not_err3 = icmp eq i64 %7, 0 + br i1 %not_err3, label %after_check4, label %voiderr -after_check3: +after_check4: ; preds = %foreach.body %arridx = getelementptr inbounds [3 x i32], [3 x i32]* %x, i64 0, i64 0 store i32 1, i32* %arridx, align 4 br label %voiderr @@ -59,9 +59,9 @@ voiderr: br label %foreach.inc foreach.inc: - %10 = load i64, i64* %idx, align 8 - %11 = add i64 %10, 1 - store i64 %11, i64* %idx, align 8 + %8 = load i64, i64* %idx, align 8 + %9 = add i64 %8, 1 + store i64 %9, i64* %idx, align 8 br label %foreach.cond foreach.exit: diff --git a/test/test_suite/statements/while_statement_placement.c3 b/test/test_suite/statements/while_statement_placement.c3 new file mode 100644 index 000000000..db5433899 --- /dev/null +++ b/test/test_suite/statements/while_statement_placement.c3 @@ -0,0 +1,11 @@ +func void test1() +{ + int a; + while (1) a++; + while (1) + { + a++; + } + while (1) + a++; // #error: must be placed on the same line +} \ No newline at end of file diff --git a/test/test_suite/variables/consts.c3 b/test/test_suite/variables/consts.c3 new file mode 100644 index 000000000..c772696e3 --- /dev/null +++ b/test/test_suite/variables/consts.c3 @@ -0,0 +1,7 @@ +const int foo = 3; // #error: must be all upper case + +func void foo(int i) +{ + const int x = i; // #error: must be all upper case + const int Y = i; +} \ No newline at end of file