diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2022-04-27 14:37:15 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2022-04-27 14:37:15 +0200 |
| commit | c4e0d1efa1ec655bae9134b95a0fcd060cc7bbea (patch) | |
| tree | c29bd0b78138e8d67aebe34ac689d13e32d9d15f /src | |
| parent | 6e61abc7d06f22129f93110a9f652c3eec21f0c6 (diff) | |
| parent | 9349dfba8fec53f52f77a0c8928e115ec93ff447 (diff) | |
Merge branch 'master' into xml
Diffstat (limited to 'src')
44 files changed, 7565 insertions, 3953 deletions
diff --git a/src/array.cpp b/src/array.cpp index c41125c6d..d08bd647f 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -77,15 +77,21 @@ template <typename T> Slice<T> slice_from_array(Array<T> const &a); template <typename T> Slice<T> slice_make(gbAllocator const &allocator, isize count) { + GB_ASSERT(count >= 0); Slice<T> s = {}; s.data = gb_alloc_array(allocator, T, count); + GB_ASSERT(s.data != nullptr); s.count = count; return s; } template <typename T> void slice_init(Slice<T> *s, gbAllocator const &allocator, isize count) { + GB_ASSERT(count >= 0); s->data = gb_alloc_array(allocator, T, count); + if (count > 0) { + GB_ASSERT(s->data != nullptr); + } s->count = count; } diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 27e7fcf9a..02a2b1ba2 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -17,6 +17,11 @@ #include <sys/sysctl.h>
#endif
+#if defined(GB_SYSTEM_OPENBSD)
+ #include <sys/sysctl.h>
+ #include <sys/utsname.h>
+#endif
+
/*
NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
*/
@@ -140,7 +145,7 @@ void report_windows_product_type(DWORD ProductType) { break;
default:
- gb_printf("Unknown Edition (%08x)", ProductType);
+ gb_printf("Unknown Edition (%08x)", cast(unsigned)ProductType);
}
}
#endif
@@ -242,6 +247,14 @@ void report_ram_info() { if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
}
+ #elif defined(GB_SYSTEM_OPENBSD)
+ uint64_t ram_amount;
+ size_t val_size = sizeof(ram_amount);
+
+ int sysctls[] = { CTL_HW, HW_PHYSMEM64 };
+ if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+ gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+ }
#else
gb_printf("Unknown.\n");
#endif
@@ -316,14 +329,14 @@ void print_bug_report_help() { }
if (false) {
- gb_printf("dwMajorVersion: %d\n", osvi.dwMajorVersion);
- gb_printf("dwMinorVersion: %d\n", osvi.dwMinorVersion);
- gb_printf("dwBuildNumber: %d\n", osvi.dwBuildNumber);
- gb_printf("dwPlatformId: %d\n", osvi.dwPlatformId);
- gb_printf("wServicePackMajor: %d\n", osvi.wServicePackMajor);
- gb_printf("wServicePackMinor: %d\n", osvi.wServicePackMinor);
- gb_printf("wSuiteMask: %d\n", osvi.wSuiteMask);
- gb_printf("wProductType: %d\n", osvi.wProductType);
+ gb_printf("dwMajorVersion: %u\n", cast(unsigned)osvi.dwMajorVersion);
+ gb_printf("dwMinorVersion: %u\n", cast(unsigned)osvi.dwMinorVersion);
+ gb_printf("dwBuildNumber: %u\n", cast(unsigned)osvi.dwBuildNumber);
+ gb_printf("dwPlatformId: %u\n", cast(unsigned)osvi.dwPlatformId);
+ gb_printf("wServicePackMajor: %u\n", cast(unsigned)osvi.wServicePackMajor);
+ gb_printf("wServicePackMinor: %u\n", cast(unsigned)osvi.wServicePackMinor);
+ gb_printf("wSuiteMask: %u\n", cast(unsigned)osvi.wSuiteMask);
+ gb_printf("wProductType: %u\n", cast(unsigned)osvi.wProductType);
}
gb_printf("Windows ");
@@ -441,18 +454,18 @@ void print_bug_report_help() { TEXT("DisplayVersion"),
RRF_RT_REG_SZ,
ValueType,
- &DisplayVersion,
+ DisplayVersion,
&ValueSize
);
if (status == 0x0) {
- gb_printf(" (version: %s)", &DisplayVersion);
+ gb_printf(" (version: %s)", DisplayVersion);
}
/*
Now print build number.
*/
- gb_printf(", build %d", osvi.dwBuildNumber);
+ gb_printf(", build %u", cast(unsigned)osvi.dwBuildNumber);
ValueSize = sizeof(UBR);
status = RegGetValue(
@@ -466,18 +479,18 @@ void print_bug_report_help() { );
if (status == 0x0) {
- gb_printf(".%d", UBR);
+ gb_printf(".%u", cast(unsigned)UBR);
}
gb_printf("\n");
}
#elif defined(GB_SYSTEM_LINUX)
/*
- Try to parse `/usr/lib/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+ Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
*/
gbAllocator a = heap_allocator();
- gbFileContents release = gb_file_read_contents(a, 1, "/usr/lib/os-release");
+ gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release");
defer (gb_file_free_contents(&release));
b32 found = 0;
@@ -643,6 +656,14 @@ void print_bug_report_help() { } else {
gb_printf("macOS: Unknown\n");
}
+ #elif defined(GB_SYSTEM_OPENBSD)
+ struct utsname un;
+
+ if (uname(&un) != -1) {
+ gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
+ } else {
+ gb_printf("OpenBSD: Unknown\n");
+ }
#else
gb_printf("Unknown\n");
@@ -657,4 +678,4 @@ void print_bug_report_help() { And RAM info.
*/
report_ram_info();
-}
\ No newline at end of file +}
diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 29abd441c..89d370144 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1,14 +1,13 @@ -#if defined(GB_SYSTEM_FREEBSD) +#if defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) #include <sys/types.h> #include <sys/sysctl.h> #endif - // #if defined(GB_SYSTEM_WINDOWS) -#define DEFAULT_TO_THREADED_CHECKER +// #define DEFAULT_TO_THREADED_CHECKER // #endif -enum TargetOsKind { +enum TargetOsKind : u16 { TargetOs_Invalid, TargetOs_windows, @@ -16,6 +15,7 @@ enum TargetOsKind { TargetOs_linux, TargetOs_essence, TargetOs_freebsd, + TargetOs_openbsd, TargetOs_wasi, TargetOs_js, @@ -25,11 +25,11 @@ enum TargetOsKind { TargetOs_COUNT, }; -enum TargetArchKind { +enum TargetArchKind : u16 { TargetArch_Invalid, TargetArch_amd64, - TargetArch_386, + TargetArch_i386, TargetArch_arm64, TargetArch_wasm32, TargetArch_wasm64, @@ -37,7 +37,7 @@ enum TargetArchKind { TargetArch_COUNT, }; -enum TargetEndianKind { +enum TargetEndianKind : u8 { TargetEndian_Invalid, TargetEndian_Little, @@ -46,6 +46,16 @@ enum TargetEndianKind { TargetEndian_COUNT, }; +enum TargetABIKind : u16 { + TargetABI_Default, + + TargetABI_Win64, + TargetABI_SysV, + + TargetABI_COUNT, +}; + + String target_os_names[TargetOs_COUNT] = { str_lit(""), str_lit("windows"), @@ -53,6 +63,7 @@ String target_os_names[TargetOs_COUNT] = { str_lit("linux"), str_lit("essence"), str_lit("freebsd"), + str_lit("openbsd"), str_lit("wasi"), str_lit("js"), @@ -63,7 +74,7 @@ String target_os_names[TargetOs_COUNT] = { String target_arch_names[TargetArch_COUNT] = { str_lit(""), str_lit("amd64"), - str_lit("386"), + str_lit("i386"), str_lit("arm64"), str_lit("wasm32"), str_lit("wasm64"), @@ -75,6 +86,12 @@ String target_endian_names[TargetEndian_COUNT] = { str_lit("big"), }; +String target_abi_names[TargetABI_COUNT] = { + str_lit(""), + str_lit("win64"), + str_lit("sysv"), +}; + TargetEndianKind target_endians[TargetArch_COUNT] = { TargetEndian_Invalid, TargetEndian_Little, @@ -98,6 +115,7 @@ struct TargetMetrics { isize max_align; String target_triplet; String target_data_layout; + TargetABIKind abi; }; @@ -119,6 +137,8 @@ enum BuildModeKind { BuildMode_Object, BuildMode_Assembly, BuildMode_LLVM_IR, + + BuildMode_COUNT, }; enum CommandKind : u32 { @@ -163,19 +183,50 @@ enum TimingsExportFormat : i32 { TimingsExportCSV = 2, }; +enum ErrorPosStyle { + ErrorPosStyle_Default, // path(line:column) msg + ErrorPosStyle_Unix, // path:line:column: msg + + ErrorPosStyle_COUNT +}; + +enum RelocMode : u8 { + RelocMode_Default, + RelocMode_Static, + RelocMode_PIC, + RelocMode_DynamicNoPIC, +}; + +enum BuildPath : u8 { + BuildPath_Main_Package, // Input Path to the package directory (or file) we're building. + BuildPath_RC, // Input Path for .rc file, can be set with `-resource:`. + BuildPath_RES, // Output Path for .res file, generated from previous. + BuildPath_Win_SDK_Root, // windows_sdk_root + BuildPath_Win_SDK_UM_Lib, // windows_sdk_um_library_path + BuildPath_Win_SDK_UCRT_Lib, // windows_sdk_ucrt_library_path + BuildPath_VS_EXE, // vs_exe_path + BuildPath_VS_LIB, // vs_library_path + + BuildPath_Output, // Output Path for .exe, .dll, .so, etc. Can be overridden with `-out:`. + BuildPath_PDB, // Output Path for .pdb file, can be overridden with `-pdb-name:`. + + BuildPathCOUNT, +}; + // This stores the information for the specify architecture of this build struct BuildContext { // Constants String ODIN_OS; // target operating system String ODIN_ARCH; // target architecture - String ODIN_ENDIAN; // target endian String ODIN_VENDOR; // compiler vendor String ODIN_VERSION; // compiler version String ODIN_ROOT; // Odin ROOT - String ODIN_BUILD_MODE; bool ODIN_DEBUG; // Odin in debug mode bool ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not bool ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing) + bool ODIN_FOREIGN_ERROR_PROCEDURES; + + ErrorPosStyle ODIN_ERROR_POS_STYLE; TargetEndianKind endian_kind; @@ -190,14 +241,19 @@ struct BuildContext { bool show_help; + Array<Path> build_paths; // Contains `Path` objects to output filename, pdb, resource and intermediate files. + // BuildPath enum contains the indices of paths we know *before* the work starts. + String out_filepath; String resource_filepath; String pdb_filepath; + bool has_resource; String link_flags; String extra_linker_flags; String extra_assembler_flags; String microarch; + String target_features; BuildModeKind build_mode; bool generate_docs; i32 optimization_level; @@ -243,6 +299,12 @@ struct BuildContext { bool copy_file_contents; + bool disallow_rtti; + + RelocMode reloc_mode; + bool disable_red_zone; + + u32 cmd_doc_flags; Array<String> extra_packages; @@ -254,9 +316,8 @@ struct BuildContext { isize thread_count; PtrMap<char const *, ExactValue> defined_values; -}; - +}; gb_global BuildContext build_context = {0}; @@ -268,9 +329,9 @@ bool global_ignore_warnings(void) { } -gb_global TargetMetrics target_windows_386 = { +gb_global TargetMetrics target_windows_i386 = { TargetOs_windows, - TargetArch_386, + TargetArch_i386, 4, 8, str_lit("i386-pc-windows-msvc"), @@ -284,9 +345,9 @@ gb_global TargetMetrics target_windows_amd64 = { str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; -gb_global TargetMetrics target_linux_386 = { +gb_global TargetMetrics target_linux_i386 = { TargetOs_linux, - TargetArch_386, + TargetArch_i386, 4, 8, str_lit("i386-pc-linux-gnu"), @@ -300,6 +361,14 @@ gb_global TargetMetrics target_linux_amd64 = { str_lit("x86_64-pc-linux-gnu"), str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; +gb_global TargetMetrics target_linux_arm64 = { + TargetOs_linux, + TargetArch_arm64, + 8, + 16, + str_lit("aarch64-linux-elf"), + str_lit("e-m:e-i8:8:32-i16:32-i64:64-i128:128-n32:64-S128"), +}; gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, @@ -319,9 +388,9 @@ gb_global TargetMetrics target_darwin_arm64 = { str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), // TODO(bill): Is this correct? }; -gb_global TargetMetrics target_freebsd_386 = { +gb_global TargetMetrics target_freebsd_i386 = { TargetOs_freebsd, - TargetArch_386, + TargetArch_i386, 4, 8, str_lit("i386-unknown-freebsd-elf"), @@ -336,6 +405,15 @@ gb_global TargetMetrics target_freebsd_amd64 = { str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; +gb_global TargetMetrics target_openbsd_amd64 = { + TargetOs_openbsd, + TargetArch_amd64, + 8, + 16, + str_lit("x86_64-unknown-openbsd-elf"), + str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"), +}; + gb_global TargetMetrics target_essence_amd64 = { TargetOs_essence, TargetArch_amd64, @@ -381,6 +459,16 @@ gb_global TargetMetrics target_wasi_wasm32 = { // str_lit(""), // }; +gb_global TargetMetrics target_freestanding_amd64_sysv = { + TargetOs_freestanding, + TargetArch_amd64, + 8, + 16, + str_lit("x86_64-pc-none-gnu"), + str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), + TargetABI_SysV, +}; + struct NamedTargetMetrics { @@ -392,16 +480,19 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("darwin_amd64"), &target_darwin_amd64 }, { str_lit("darwin_arm64"), &target_darwin_arm64 }, { str_lit("essence_amd64"), &target_essence_amd64 }, - { str_lit("linux_386"), &target_linux_386 }, + { str_lit("linux_i386"), &target_linux_i386 }, { str_lit("linux_amd64"), &target_linux_amd64 }, - { str_lit("windows_386"), &target_windows_386 }, + { str_lit("linux_arm64"), &target_linux_arm64 }, + { str_lit("windows_i386"), &target_windows_i386 }, { str_lit("windows_amd64"), &target_windows_amd64 }, - { str_lit("freebsd_386"), &target_freebsd_386 }, + { str_lit("freebsd_i386"), &target_freebsd_i386 }, { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, + { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, { str_lit("js_wasm32"), &target_js_wasm32 }, - // { str_lit("freestanding_wasm64"), &target_freestanding_wasm64 }, + + { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, }; NamedTargetMetrics *selected_target_metrics; @@ -524,7 +615,6 @@ bool allow_check_foreign_filepath(void) { return true; } - // TODO(bill): OS dependent versions for the BuildContext // join_path // is_dir @@ -703,10 +793,38 @@ String internal_odin_root_dir(void) { len = readlink("/proc/curproc/exe", &path_buf[0], path_buf.count); #elif defined(GB_SYSTEM_DRAGONFLYBSD) len = readlink("/proc/curproc/file", &path_buf[0], path_buf.count); -#else +#elif defined(GB_SYSTEM_LINUX) len = readlink("/proc/self/exe", &path_buf[0], path_buf.count); +#elif defined(GB_SYSTEM_OPENBSD) + int error; + int mib[] = { + CTL_KERN, + KERN_PROC_ARGS, + getpid(), + KERN_PROC_ARGV, + }; + // get argv size + error = sysctl(mib, 4, NULL, (size_t *) &len, NULL, 0); + if (error == -1) { + // sysctl error + return make_string(nullptr, 0); + } + // get argv + char **argv = (char **)gb_malloc(len); + error = sysctl(mib, 4, argv, (size_t *) &len, NULL, 0); + if (error == -1) { + // sysctl error + gb_mfree(argv); + return make_string(nullptr, 0); + } + // copy argv[0] to path_buf + len = gb_strlen(argv[0]); + if(len < path_buf.count) { + gb_memmove(&path_buf[0], argv[0], len); + } + gb_mfree(argv); #endif - if(len == 0) { + if(len == 0 || len == -1) { return make_string(nullptr, 0); } if (len < path_buf.count) { @@ -834,6 +952,21 @@ bool has_asm_extension(String const &path) { return false; } +// temporary +char *token_pos_to_string(TokenPos const &pos) { + gbString s = gb_string_make_reserve(temporary_allocator(), 128); + String file = get_file_path_string(pos.file_id); + switch (build_context.ODIN_ERROR_POS_STYLE) { + default: /*fallthrough*/ + case ErrorPosStyle_Default: + s = gb_string_append_fmt(s, "%.*s(%d:%d)", LIT(file), pos.line, pos.column); + break; + case ErrorPosStyle_Unix: + s = gb_string_append_fmt(s, "%.*s:%d:%d:", LIT(file), pos.line, pos.column); + break; + } + return s; +} void init_build_context(TargetMetrics *cross_target) { BuildContext *bc = &build_context; @@ -846,25 +979,31 @@ void init_build_context(TargetMetrics *cross_target) { bc->ODIN_VENDOR = str_lit("odin"); bc->ODIN_VERSION = ODIN_VERSION; bc->ODIN_ROOT = odin_root_dir(); - switch (bc->build_mode) { - default: - case BuildMode_Executable: - bc->ODIN_BUILD_MODE = str_lit("executable"); - break; - case BuildMode_DynamicLibrary: - bc->ODIN_BUILD_MODE = str_lit("dynamic"); - break; - case BuildMode_Object: - bc->ODIN_BUILD_MODE = str_lit("object"); - break; - case BuildMode_Assembly: - bc->ODIN_BUILD_MODE = str_lit("assembly"); - break; - case BuildMode_LLVM_IR: - bc->ODIN_BUILD_MODE = str_lit("llvm-ir"); - break; + + { + char const *found = gb_get_env("ODIN_ERROR_POS_STYLE", permanent_allocator()); + if (found) { + ErrorPosStyle kind = ErrorPosStyle_Default; + String style = make_string_c(found); + style = string_trim_whitespace(style); + if (style == "" || style == "default" || style == "odin") { + kind = ErrorPosStyle_Default; + } else if (style == "unix" || style == "gcc" || style == "clang" || style == "llvm") { + kind = ErrorPosStyle_Unix; + } else { + gb_printf_err("Invalid ODIN_ERROR_POS_STYLE: got %.*s\n", LIT(style)); + gb_printf_err("Valid formats:\n"); + gb_printf_err("\t\"default\" or \"odin\"\n"); + gb_printf_err("\t\tpath(line:column) message\n"); + gb_printf_err("\t\"unix\"\n"); + gb_printf_err("\t\tpath:line:column: message\n"); + gb_exit(1); + } + + build_context.ODIN_ERROR_POS_STYLE = kind; + } } - + bc->copy_file_contents = true; TargetMetrics *metrics = nullptr; @@ -880,18 +1019,22 @@ void init_build_context(TargetMetrics *cross_target) { #endif #elif defined(GB_SYSTEM_FREEBSD) metrics = &target_freebsd_amd64; + #elif defined(GB_SYSTEM_OPENBSD) + metrics = &target_openbsd_amd64; + #elif defined(GB_CPU_ARM) + metrics = &target_linux_arm64; #else metrics = &target_linux_amd64; #endif #else #if defined(GB_SYSTEM_WINDOWS) - metrics = &target_windows_386; + metrics = &target_windows_i386; #elif defined(GB_SYSTEM_OSX) #error "Build Error: Unsupported architecture" #elif defined(GB_SYSTEM_FREEBSD) - metrics = &target_freebsd_386; + metrics = &target_freebsd_i386; #else - metrics = &target_linux_386; + metrics = &target_linux_i386; #endif #endif @@ -910,7 +1053,6 @@ void init_build_context(TargetMetrics *cross_target) { bc->metrics = *metrics; bc->ODIN_OS = target_os_names[metrics->os]; bc->ODIN_ARCH = target_arch_names[metrics->arch]; - bc->ODIN_ENDIAN = target_endian_names[target_endians[metrics->arch]]; bc->endian_kind = target_endians[metrics->arch]; bc->word_size = metrics->word_size; bc->max_align = metrics->max_align; @@ -920,6 +1062,21 @@ void init_build_context(TargetMetrics *cross_target) { bc->threaded_checker = true; #endif + if (bc->disable_red_zone) { + if (!!is_arch_wasm() && bc->metrics.os == TargetOs_freestanding) { + gb_printf_err("-disable-red-zone is not support for this target"); + gb_exit(1); + } + } + + if (bc->metrics.os == TargetOs_freestanding) { + bc->no_entry_point = true; + } else { + if (bc->disallow_rtti) { + gb_printf_err("-disallow-rtti is only allowed on freestanding targets\n"); + gb_exit(1); + } + } // NOTE(zangent): The linker flags to set the build architecture are different // across OSs. It doesn't make sense to allocate extra data on the heap @@ -937,8 +1094,11 @@ void init_build_context(TargetMetrics *cross_target) { case TargetOs_freebsd: bc->link_flags = str_lit("-arch x86-64 "); break; + case TargetOs_openbsd: + bc->link_flags = str_lit("-arch x86-64 "); + break; } - } else if (bc->metrics.arch == TargetArch_386) { + } else if (bc->metrics.arch == TargetArch_i386) { switch (bc->metrics.os) { case TargetOs_windows: bc->link_flags = str_lit("/machine:x86 "); @@ -959,6 +1119,9 @@ void init_build_context(TargetMetrics *cross_target) { case TargetOs_darwin: bc->link_flags = str_lit("-arch arm64 "); break; + case TargetOs_linux: + bc->link_flags = str_lit("-arch aarch64 "); + break; } } else if (is_arch_wasm()) { gbString link_flags = gb_string_make(heap_allocator(), " "); @@ -968,14 +1131,14 @@ void init_build_context(TargetMetrics *cross_target) { if (bc->metrics.arch == TargetArch_wasm64) { link_flags = gb_string_appendc(link_flags, "-mwas64 "); } - if (bc->metrics.os == TargetOs_freestanding) { + if (bc->no_entry_point) { link_flags = gb_string_appendc(link_flags, "--no-entry "); } bc->link_flags = make_string_c(link_flags); // Disallow on wasm - build_context.use_separate_modules = false; + bc->use_separate_modules = false; } else { gb_printf_err("Compiler Error: Unsupported architecture\n"); gb_exit(1); @@ -986,3 +1149,191 @@ void init_build_context(TargetMetrics *cross_target) { #undef LINK_FLAG_X64 #undef LINK_FLAG_386 } + +#if defined(GB_SYSTEM_WINDOWS) +// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. +// NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`. +#include "microsoft_craziness.h" +#endif + +// NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. +// We've previously called `parse_build_flags`, so `out_filepath` should be set. +bool init_build_paths(String init_filename) { + gbAllocator ha = heap_allocator(); + BuildContext *bc = &build_context; + + // NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index. + array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT); + + // [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path. + bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename); + + bool produces_output_file = false; + if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { + produces_output_file = true; + } else if (bc->command_kind & Command__does_build) { + produces_output_file = true; + } + + if (!produces_output_file) { + // Command doesn't produce output files. We're done. + return true; + } + + #if defined(GB_SYSTEM_WINDOWS) + if (bc->resource_filepath.len > 0) { + bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); + bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + } + + if (bc->pdb_filepath.len > 0) { + bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath); + } + + if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) { + // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. + Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); + + if (find_result.windows_sdk_version == 0) { + gb_printf_err("Windows SDK not found.\n"); + return false; + } + + GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); + GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); + + if (find_result.windows_sdk_root.len > 0) { + bc->build_paths[BuildPath_Win_SDK_Root] = path_from_string(ha, find_result.windows_sdk_root); + } + + if (find_result.windows_sdk_um_library_path.len > 0) { + bc->build_paths[BuildPath_Win_SDK_UM_Lib] = path_from_string(ha, find_result.windows_sdk_um_library_path); + } + + if (find_result.windows_sdk_ucrt_library_path.len > 0) { + bc->build_paths[BuildPath_Win_SDK_UCRT_Lib] = path_from_string(ha, find_result.windows_sdk_ucrt_library_path); + } + + if (find_result.vs_exe_path.len > 0) { + bc->build_paths[BuildPath_VS_EXE] = path_from_string(ha, find_result.vs_exe_path); + } + + if (find_result.vs_library_path.len > 0) { + bc->build_paths[BuildPath_VS_LIB] = path_from_string(ha, find_result.vs_library_path); + } + + gb_free(ha, find_result.windows_sdk_root.text); + gb_free(ha, find_result.windows_sdk_um_library_path.text); + gb_free(ha, find_result.windows_sdk_ucrt_library_path.text); + gb_free(ha, find_result.vs_exe_path.text); + gb_free(ha, find_result.vs_library_path.text); + + } + #endif + + // All the build targets and OSes. + String output_extension; + + if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { + output_extension = STR_LIT("odin-doc"); + } else if (is_arch_wasm()) { + output_extension = STR_LIT("wasm"); + } else if (build_context.build_mode == BuildMode_Executable) { + // By default use a .bin executable extension. + output_extension = STR_LIT("bin"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("exe"); + } else if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { + output_extension = make_string(nullptr, 0); + } + } else if (build_context.build_mode == BuildMode_DynamicLibrary) { + // By default use a .so shared library extension. + output_extension = STR_LIT("so"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("dll"); + } else if (build_context.metrics.os == TargetOs_darwin) { + output_extension = STR_LIT("dylib"); + } + } else if (build_context.build_mode == BuildMode_Object) { + // By default use a .o object extension. + output_extension = STR_LIT("o"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("obj"); + } + } else if (build_context.build_mode == BuildMode_Assembly) { + // By default use a .S asm extension. + output_extension = STR_LIT("S"); + } else if (build_context.build_mode == BuildMode_LLVM_IR) { + output_extension = STR_LIT("ll"); + } else { + GB_PANIC("Unhandled build mode/target combination.\n"); + } + + if (bc->out_filepath.len > 0) { + bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath); + if (build_context.metrics.os == TargetOs_windows) { + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + if (path_is_directory(bc->build_paths[BuildPath_Output])) { + gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); + return false; + } else if (bc->build_paths[BuildPath_Output].ext.len == 0) { + gb_printf_err("Output path %.*s must have an appropriate extension.\n", LIT(output_file)); + return false; + } + } + } else { + Path output_path; + + if (str_eq(init_filename, str_lit("."))) { + // We must name the output file after the current directory. + debugf("Output name will be created from current base name %.*s.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename)); + String last_element = last_path_element(bc->build_paths[BuildPath_Main_Package].basename); + + if (last_element.len == 0) { + gb_printf_err("The output name is created from the last path element. `%.*s` has none. Use `-out:output_name.ext` to set it.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename)); + return false; + } + output_path.basename = copy_string(ha, bc->build_paths[BuildPath_Main_Package].basename); + output_path.name = copy_string(ha, last_element); + + } else { + // Init filename was not 'current path'. + // Contruct the output name from the path elements as usual. + String output_name = remove_directory_from_path(init_filename); + output_name = remove_extension_from_path(output_name); + output_name = copy_string(ha, string_trim_whitespace(output_name)); + output_path = path_from_string(ha, output_name); + + // Replace extension. + if (output_path.ext.len > 0) { + gb_free(ha, output_path.ext.text); + } + } + output_path.ext = copy_string(ha, output_extension); + + bc->build_paths[BuildPath_Output] = output_path; + } + + // Do we have an extension? We might not if the output filename was supplied. + if (bc->build_paths[BuildPath_Output].ext.len == 0) { + if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) { + bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); + } + } + + // Check if output path is a directory. + if (path_is_directory(bc->build_paths[BuildPath_Output])) { + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); + return false; + } + + return true; +}
\ No newline at end of file diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index f93cf9886..6c7972d45 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -143,6 +143,270 @@ void check_or_return_split_types(CheckerContext *c, Operand *x, String const &na } +bool does_require_msgSend_stret(Type *return_type) { + if (return_type == nullptr) { + return false; + } + if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) { + i64 struct_limit = type_size_of(t_uintptr) << 1; + return type_size_of(return_type) > struct_limit; + } + if (build_context.metrics.arch == TargetArch_arm64) { + return false; + } + + // if (build_context.metrics.arch == TargetArch_arm32) { + // i64 struct_limit = type_size_of(t_uintptr); + // // NOTE(bill): This is technically wrong + // return is_type_struct(return_type) && !is_type_raw_union(return_type) && type_size_of(return_type) > struct_limit; + // } + GB_PANIC("unsupported architecture"); + return false; +} + +ObjcMsgKind get_objc_proc_kind(Type *return_type) { + if (return_type == nullptr) { + return ObjcMsg_normal; + } + + if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) { + if (is_type_float(return_type)) { + return ObjcMsg_fpret; + } + if (build_context.metrics.arch == TargetArch_amd64) { + if (is_type_complex(return_type)) { + // URL: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/message.h#L143-L159 + return ObjcMsg_fpret; + } + } + } + if (build_context.metrics.arch != TargetArch_arm64) { + if (does_require_msgSend_stret(return_type)) { + return ObjcMsg_stret; + } + } + return ObjcMsg_normal; +} + +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types) { + ObjcMsgKind kind = get_objc_proc_kind(return_type); + + Scope *scope = create_scope(c->info, nullptr); + + // NOTE(bill, 2022-02-08): the backend's ABI handling should handle this correctly, I hope + Type *params = alloc_type_tuple(); + { + auto variables = array_make<Entity *>(permanent_allocator(), 0, param_types.count); + + for_array(i, param_types) { + Type *type = param_types[i]; + Entity *param = alloc_entity_param(scope, blank_token, type, false, true); + array_add(&variables, param); + } + params->Tuple.variables = slice_from_array(variables); + } + + Type *results = alloc_type_tuple(); + if (return_type) { + auto variables = array_make<Entity *>(permanent_allocator(), 1); + results->Tuple.variables = slice_from_array(variables); + Entity *param = alloc_entity_param(scope, blank_token, return_type, false, true); + results->Tuple.variables[0] = param; + } + + + ObjcMsgData data = {}; + data.kind = kind; + data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl); + + mutex_lock(&c->info->objc_types_mutex); + map_set(&c->info->objc_msgSend_types, call, data); + mutex_unlock(&c->info->objc_types_mutex); + + try_to_add_package_dependency(c, "runtime", "objc_msgSend"); + try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); + try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret"); + try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret"); +} + +bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) { + Operand op = {}; + check_expr(c, &op, expr); + if (op.mode == Addressing_Constant && op.value.kind == ExactValue_String) { + if (name_) *name_ = op.value.value_string; + return true; + } + gbString e = expr_to_string(op.expr); + gbString t = type_to_string(op.type); + error(op.expr, "'%.*s' expected a constant string value, got %s of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; +} + +bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { + String builtin_name = builtin_procs[id].name; + + if (build_context.metrics.os != TargetOs_darwin) { + // allow on doc generation (e.g. Metal stuff) + if (build_context.command_kind != Command_doc && build_context.command_kind != Command_check) { + error(call, "'%.*s' only works on darwin", LIT(builtin_name)); + } + } + + + ast_node(ce, CallExpr, call); + switch (id) { + default: + GB_PANIC("Implement objective built-in procedure: %.*s", LIT(builtin_name)); + return false; + + case BuiltinProc_objc_send: { + Type *return_type = nullptr; + + Operand rt = {}; + check_expr_or_type(c, &rt, ce->args[0]); + if (rt.mode == Addressing_Type) { + return_type = rt.type; + } else if (is_operand_nil(rt)) { + return_type = nullptr; + } else { + gbString e = expr_to_string(rt.expr); + error(rt.expr, "'%.*s' expected a type or nil to define the return type of the Objective-C call, got %s", LIT(builtin_name), e); + gb_string_free(e); + return false; + } + + operand->type = return_type; + operand->mode = return_type ? Addressing_Value : Addressing_NoValue; + + String class_name = {}; + String sel_name = {}; + + Type *sel_type = t_objc_SEL; + Operand self = {}; + check_expr_or_type(c, &self, ce->args[1]); + if (self.mode == Addressing_Type) { + if (!is_type_objc_object(self.type)) { + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + if (!has_type_got_objc_class_attribute(self.type)) { + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=<string>) , got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + sel_type = t_objc_Class; + } else if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } else if (!is_type_pointer(self.type)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } else { + Type *type = type_deref(self.type); + if (!(type->kind == Type_Named && + type->Named.type_name != nullptr && + type->Named.type_name->TypeName.objc_class_name != "")) { + gbString t = type_to_string(type); + error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=<string>) , got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + } + + + if (!is_constant_string(c, builtin_name, ce->args[2], &sel_name)) { + return false; + } + + isize const arg_offset = 1; + auto param_types = slice_make<Type *>(permanent_allocator(), ce->args.count-arg_offset); + param_types[0] = t_objc_id; + param_types[1] = sel_type; + + for (isize i = 2+arg_offset; i < ce->args.count; i++) { + Operand x = {}; + check_expr(c, &x, ce->args[i]); + param_types[i-arg_offset] = x.type; + } + + add_objc_proc_type(c, call, return_type, param_types); + + return true; + } break; + + case BuiltinProc_objc_find_selector: + case BuiltinProc_objc_find_class: + case BuiltinProc_objc_register_selector: + case BuiltinProc_objc_register_class: + { + String sel_name = {}; + if (!is_constant_string(c, builtin_name, ce->args[0], &sel_name)) { + return false; + } + + switch (id) { + case BuiltinProc_objc_find_selector: + case BuiltinProc_objc_register_selector: + operand->type = t_objc_SEL; + break; + case BuiltinProc_objc_find_class: + case BuiltinProc_objc_register_class: + operand->type = t_objc_Class; + break; + + } + operand->mode = Addressing_Value; + + try_to_add_package_dependency(c, "runtime", "objc_lookUpClass"); + try_to_add_package_dependency(c, "runtime", "sel_registerName"); + try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair"); + return true; + } break; + } +} + +bool check_atomic_memory_order_argument(CheckerContext *c, Ast *expr, String const &builtin_name, OdinAtomicMemoryOrder *memory_order_, char const *extra_message = nullptr) { + Operand x = {}; + check_expr_with_type_hint(c, &x, expr, t_atomic_memory_order); + if (x.mode == Addressing_Invalid) { + return false; + } + if (!are_types_identical(x.type, t_atomic_memory_order) || x.mode != Addressing_Constant) { + gbString str = type_to_string(x.type); + if (extra_message) { + error(x.expr, "Expected a constant Atomic_Memory_Order value for the %s of '%.*s', got %s", extra_message, LIT(builtin_name), str); + } else { + error(x.expr, "Expected a constant Atomic_Memory_Order value for '%.*s', got %s", LIT(builtin_name), str); + } + gb_string_free(str); + return false; + } + i64 value = exact_value_to_i64(x.value); + if (value < 0 || value >= OdinAtomicMemoryOrder_COUNT) { + error(x.expr, "Illegal Atomic_Memory_Order value, got %lld", cast(long long)value); + return false; + } + if (memory_order_) { + *memory_order_ = cast(OdinAtomicMemoryOrder)value; + } + + return true; + +} bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { ast_node(ce, CallExpr, call); @@ -179,9 +443,21 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_len: case BuiltinProc_min: case BuiltinProc_max: + case BuiltinProc_type_is_subtype_of: + case BuiltinProc_objc_send: + case BuiltinProc_objc_find_selector: + case BuiltinProc_objc_find_class: + case BuiltinProc_objc_register_selector: + case BuiltinProc_objc_register_class: + case BuiltinProc_atomic_type_is_lock_free: // NOTE(bill): The first arg may be a Type, this will be checked case by case break; + case BuiltinProc_atomic_thread_fence: + case BuiltinProc_atomic_signal_fence: + // NOTE(bill): first type will require a type hint + break; + case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); String name = bd->name.string; @@ -202,7 +478,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } - String builtin_name = builtin_procs[id].name;; + String builtin_name = builtin_procs[id].name; if (ce->args.count > 0) { @@ -219,6 +495,19 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name)); break; + case BuiltinProc_objc_send: + case BuiltinProc_objc_find_selector: + case BuiltinProc_objc_find_class: + case BuiltinProc_objc_register_selector: + case BuiltinProc_objc_register_class: + return check_builtin_objc_procedure(c, operand, call, id, type_hint); + + case BuiltinProc___entry_point: + operand->mode = Addressing_NoValue; + operand->type = nullptr; + mpmc_enqueue(&c->info->intrinsics_entry_point_usage, call); + break; + case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); String name = bd->name.string; @@ -542,8 +831,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } } else if (name == "assert") { - if (ce->args.count != 1) { - error(call, "'#assert' expects 1 argument, got %td", ce->args.count); + if (ce->args.count != 1 && ce->args.count != 2) { + error(call, "'#assert' expects either 1 or 2 arguments, got %td", ce->args.count); return false; } if (!is_type_boolean(operand->type) || operand->mode != Addressing_Constant) { @@ -552,15 +841,37 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 gb_string_free(str); return false; } + if (ce->args.count == 2) { + Ast *arg = unparen_expr(ce->args[1]); + if (arg == nullptr || arg->kind != Ast_BasicLit || arg->BasicLit.token.kind != Token_String) { + gbString str = expr_to_string(arg); + error(call, "'%s' is not a constant string", str); + gb_string_free(str); + return false; + } + } + if (!operand->value.value_bool) { - gbString arg = expr_to_string(ce->args[0]); - error(call, "Compile time assertion: %s", arg); + gbString arg1 = expr_to_string(ce->args[0]); + gbString arg2 = {}; + + if (ce->args.count == 1) { + error(call, "Compile time assertion: %s", arg1); + } else { + arg2 = expr_to_string(ce->args[1]); + error(call, "Compile time assertion: %s (%s)", arg1, arg2); + } + if (c->proc_name != "") { gbString str = type_to_string(c->curr_proc_sig); error_line("\tCalled within '%.*s' :: %s\n", LIT(c->proc_name), str); gb_string_free(str); } - gb_string_free(arg); + + gb_string_free(arg1); + if (ce->args.count == 2) { + gb_string_free(arg2); + } } operand->type = t_untyped_bool; @@ -698,7 +1009,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 mode = Addressing_Constant; value = exact_value_i64(at->EnumeratedArray.count); type = t_untyped_integer; - } else if (is_type_slice(op_type) && id == BuiltinProc_len) { + } else if ((is_type_slice(op_type) || is_type_relative_slice(op_type)) && id == BuiltinProc_len) { mode = Addressing_Value; } else if (is_type_dynamic_array(op_type)) { mode = Addressing_Value; @@ -852,7 +1163,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 Selection sel = lookup_field(type, field_name, false); if (sel.entity == nullptr) { - gbString type_str = type_to_string(type); + gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); gb_string_free(type_str); @@ -864,7 +1175,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } if (sel.indirect) { - gbString type_str = type_to_string(type); + gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "Field '%.*s' is embedded via a pointer in '%s'", LIT(field_name), type_str); gb_string_free(type_str); @@ -925,7 +1236,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 Selection sel = lookup_field(type, field_name, false); if (sel.entity == nullptr) { - gbString type_str = type_to_string(type); + gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); gb_string_free(type_str); @@ -937,7 +1248,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } if (sel.indirect) { - gbString type_str = type_to_string(type); + gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "Field '%.*s' is embedded via a pointer in '%s'", LIT(field_name), type_str); gb_string_free(type_str); @@ -987,6 +1298,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (c->scope->flags&ScopeFlag_Global) { compiler_error("'type_info_of' Cannot be declared within the runtime package due to how the internals of the compiler works"); } + if (build_context.disallow_rtti) { + error(call, "'%.*s' has been disallowed", LIT(builtin_name)); + return false; + } // NOTE(bill): The type information may not be setup yet init_core_type_info(c->checker); @@ -999,9 +1314,9 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 Type *t = o.type; if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(t)) { if (is_type_polymorphic(t)) { - error(ce->args[0], "Invalid argument for 'type_info_of', unspecialized polymorphic type"); + error(ce->args[0], "Invalid argument for '%.*s', unspecialized polymorphic type", LIT(builtin_name)); } else { - error(ce->args[0], "Invalid argument for 'type_info_of'"); + error(ce->args[0], "Invalid argument for '%.*s'", LIT(builtin_name)); } return false; } @@ -1012,7 +1327,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (is_operand_value(o) && is_type_typeid(t)) { add_package_dependency(c, "runtime", "__type_info_of"); } else if (o.mode != Addressing_Type) { - error(expr, "Expected a type or typeid for 'type_info_of'"); + error(expr, "Expected a type or typeid for '%.*s'", LIT(builtin_name)); return false; } @@ -1026,6 +1341,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (c->scope->flags&ScopeFlag_Global) { compiler_error("'typeid_of' Cannot be declared within the runtime package due to how the internals of the compiler works"); } + if (build_context.disallow_rtti) { + error(call, "'%.*s' has been disallowed", LIT(builtin_name)); + return false; + } // NOTE(bill): The type information may not be setup yet init_core_type_info(c->checker); @@ -1037,7 +1356,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } Type *t = o.type; if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(operand->type)) { - error(ce->args[0], "Invalid argument for 'typeid_of'"); + error(ce->args[0], "Invalid argument for '%.*s'", LIT(builtin_name)); return false; } t = default_type(t); @@ -1045,7 +1364,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 add_type_info_type(c, t); if (o.mode != Addressing_Type) { - error(expr, "Expected a type for 'typeid_of'"); + error(expr, "Expected a type for '%.*s'", LIT(builtin_name)); return false; } @@ -1858,7 +2177,14 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 f64 r = operand->value.value_complex->real; f64 i = operand->value.value_complex->imag; operand->value = exact_value_float(gb_sqrt(r*r + i*i)); - + break; + } + case ExactValue_Quaternion: { + f64 r = operand->value.value_quaternion->real; + f64 i = operand->value.value_quaternion->imag; + f64 j = operand->value.value_quaternion->jmag; + f64 k = operand->value.value_quaternion->kmag; + operand->value = exact_value_float(gb_sqrt(r*r + i*i + j*j + k*k)); break; } default: @@ -1877,10 +2203,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } } - if (is_type_complex(operand->type)) { + if (is_type_complex_or_quaternion(operand->type)) { operand->type = base_complex_elem_type(operand->type); } - GB_ASSERT(!is_type_complex(operand->type)); + GB_ASSERT(!is_type_complex_or_quaternion(operand->type)); break; } @@ -2170,9 +2496,43 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } operand->mode = Addressing_Value; - if (is_type_array(t)) { + if (t->kind == Type_Array) { + i32 rank = type_math_rank(t); // Do nothing - operand->type = x.type; + operand->type = x.type; + if (rank > 2) { + gbString s = type_to_string(x.type); + error(call, "'%.*s' expects a matrix or array with a rank of 2, got %s of rank %d", LIT(builtin_name), s, rank); + gb_string_free(s); + return false; + } else if (rank == 2) { + Type *inner = base_type(t->Array.elem); + GB_ASSERT(inner->kind == Type_Array); + Type *elem = inner->Array.elem; + Type *array_inner = alloc_type_array(elem, t->Array.count); + Type *array_outer = alloc_type_array(array_inner, inner->Array.count); + operand->type = array_outer; + + i64 elements = t->Array.count*inner->Array.count; + i64 size = type_size_of(operand->type); + if (!is_type_valid_for_matrix_elems(elem)) { + gbString s = type_to_string(x.type); + error(call, "'%.*s' expects a matrix or array with a base element type of an integer, float, or complex number, got %s", LIT(builtin_name), s); + gb_string_free(s); + } else if (elements > MATRIX_ELEMENT_COUNT_MAX) { + gbString s = type_to_string(x.type); + error(call, "'%.*s' expects a matrix or array with a maximum of %d elements, got %s with %lld elements", LIT(builtin_name), MATRIX_ELEMENT_COUNT_MAX, s, elements); + gb_string_free(s); + } else if (elements > MATRIX_ELEMENT_COUNT_MAX) { + gbString s = type_to_string(x.type); + error(call, "'%.*s' expects a matrix or array with non-zero elements, got %s", LIT(builtin_name), MATRIX_ELEMENT_COUNT_MAX, s); + gb_string_free(s); + } else if (size > MATRIX_ELEMENT_MAX_SIZE) { + gbString s = type_to_string(x.type); + error(call, "Too large of a type for '%.*s', got %s of size %lld, maximum size %d", LIT(builtin_name), s, cast(long long)size, MATRIX_ELEMENT_MAX_SIZE); + gb_string_free(s); + } + } } else { GB_ASSERT(t->kind == Type_Matrix); operand->type = alloc_type_matrix(t->Matrix.elem, t->Matrix.column_count, t->Matrix.row_count); @@ -2895,11 +3255,56 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; - case BuiltinProc_atomic_fence: - case BuiltinProc_atomic_fence_acq: - case BuiltinProc_atomic_fence_rel: - case BuiltinProc_atomic_fence_acqrel: - operand->mode = Addressing_NoValue; + case BuiltinProc_atomic_type_is_lock_free: + { + Ast *expr = ce->args[0]; + Operand o = {}; + check_expr_or_type(c, &o, expr); + + if (o.mode == Addressing_Invalid || o.mode == Addressing_Builtin) { + return false; + } + if (o.type == nullptr || o.type == t_invalid || is_type_asm_proc(o.type)) { + error(o.expr, "Invalid argument to '%.*s'", LIT(builtin_name)); + return false; + } + if (is_type_polymorphic(o.type)) { + error(o.expr, "'%.*s' of polymorphic type cannot be determined", LIT(builtin_name)); + return false; + } + if (is_type_untyped(o.type)) { + error(o.expr, "'%.*s' of untyped type is not allowed", LIT(builtin_name)); + return false; + } + Type *t = o.type; + bool is_lock_free = is_type_lock_free(t); + + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + operand->value = exact_value_bool(is_lock_free); + break; + } + + case BuiltinProc_atomic_thread_fence: + case BuiltinProc_atomic_signal_fence: + { + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[0], builtin_name, &memory_order)) { + return false; + } + switch (memory_order) { + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_release: + case OdinAtomicMemoryOrder_acq_rel: + case OdinAtomicMemoryOrder_seq_cst: + break; + default: + error(ce->args[0], "Illegal memory ordering for '%.*s', got .%s", LIT(builtin_name), OdinAtomicMemoryOrder_strings[memory_order]); + break; + } + + operand->mode = Addressing_NoValue; + } break; case BuiltinProc_volatile_store: @@ -2907,9 +3312,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_unaligned_store: /*fallthrough*/ case BuiltinProc_atomic_store: - case BuiltinProc_atomic_store_rel: - case BuiltinProc_atomic_store_relaxed: - case BuiltinProc_atomic_store_unordered: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -2925,14 +3327,40 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } + case BuiltinProc_atomic_store_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_assignment(c, &x, elem, builtin_name); + + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[2], builtin_name, &memory_order)) { + return false; + } + switch (memory_order) { + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_acq_rel: + error(ce->args[2], "Illegal memory order .%s for '%.*s'", OdinAtomicMemoryOrder_strings[memory_order], LIT(builtin_name)); + break; + } + + operand->type = nullptr; + operand->mode = Addressing_NoValue; + break; + } + + case BuiltinProc_volatile_load: /*fallthrough*/ case BuiltinProc_unaligned_load: /*fallthrough*/ case BuiltinProc_atomic_load: - case BuiltinProc_atomic_load_acq: - case BuiltinProc_atomic_load_relaxed: - case BuiltinProc_atomic_load_unordered: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -2944,41 +3372,75 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } + case BuiltinProc_atomic_load_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[1], builtin_name, &memory_order)) { + return false; + } + + switch (memory_order) { + case OdinAtomicMemoryOrder_release: + case OdinAtomicMemoryOrder_acq_rel: + error(ce->args[1], "Illegal memory order .%s for '%.*s'", OdinAtomicMemoryOrder_strings[memory_order], LIT(builtin_name)); + break; + } + + operand->type = elem; + operand->mode = Addressing_Value; + break; + } + case BuiltinProc_atomic_add: - case BuiltinProc_atomic_add_acq: - case BuiltinProc_atomic_add_rel: - case BuiltinProc_atomic_add_acqrel: - case BuiltinProc_atomic_add_relaxed: case BuiltinProc_atomic_sub: - case BuiltinProc_atomic_sub_acq: - case BuiltinProc_atomic_sub_rel: - case BuiltinProc_atomic_sub_acqrel: - case BuiltinProc_atomic_sub_relaxed: case BuiltinProc_atomic_and: - case BuiltinProc_atomic_and_acq: - case BuiltinProc_atomic_and_rel: - case BuiltinProc_atomic_and_acqrel: - case BuiltinProc_atomic_and_relaxed: case BuiltinProc_atomic_nand: - case BuiltinProc_atomic_nand_acq: - case BuiltinProc_atomic_nand_rel: - case BuiltinProc_atomic_nand_acqrel: - case BuiltinProc_atomic_nand_relaxed: case BuiltinProc_atomic_or: - case BuiltinProc_atomic_or_acq: - case BuiltinProc_atomic_or_rel: - case BuiltinProc_atomic_or_acqrel: - case BuiltinProc_atomic_or_relaxed: case BuiltinProc_atomic_xor: - case BuiltinProc_atomic_xor_acq: - case BuiltinProc_atomic_xor_rel: - case BuiltinProc_atomic_xor_acqrel: - case BuiltinProc_atomic_xor_relaxed: - case BuiltinProc_atomic_xchg: - case BuiltinProc_atomic_xchg_acq: - case BuiltinProc_atomic_xchg_rel: - case BuiltinProc_atomic_xchg_acqrel: - case BuiltinProc_atomic_xchg_relaxed: + case BuiltinProc_atomic_exchange: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_assignment(c, &x, elem, builtin_name); + + Type *t = type_deref(operand->type); + switch (id) { + case BuiltinProc_atomic_add: + case BuiltinProc_atomic_sub: + if (!is_type_numeric(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } else if (is_type_different_to_arch_endianness(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } + } + + operand->type = elem; + operand->mode = Addressing_Value; + break; + } + + case BuiltinProc_atomic_add_explicit: + case BuiltinProc_atomic_sub_explicit: + case BuiltinProc_atomic_and_explicit: + case BuiltinProc_atomic_nand_explicit: + case BuiltinProc_atomic_or_explicit: + case BuiltinProc_atomic_xor_explicit: + case BuiltinProc_atomic_exchange_explicit: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -2989,30 +3451,34 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 check_expr_with_type_hint(c, &x, ce->args[1], elem); check_assignment(c, &x, elem, builtin_name); + + if (!check_atomic_memory_order_argument(c, ce->args[2], builtin_name, nullptr)) { + return false; + } + + Type *t = type_deref(operand->type); + switch (id) { + case BuiltinProc_atomic_add_explicit: + case BuiltinProc_atomic_sub_explicit: + if (!is_type_numeric(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } else if (is_type_different_to_arch_endianness(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } + break; + } + operand->type = elem; operand->mode = Addressing_Value; break; } - case BuiltinProc_atomic_cxchg: - case BuiltinProc_atomic_cxchg_acq: - case BuiltinProc_atomic_cxchg_rel: - case BuiltinProc_atomic_cxchg_acqrel: - case BuiltinProc_atomic_cxchg_relaxed: - case BuiltinProc_atomic_cxchg_failrelaxed: - case BuiltinProc_atomic_cxchg_failacq: - case BuiltinProc_atomic_cxchg_acq_failrelaxed: - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: - - case BuiltinProc_atomic_cxchgweak: - case BuiltinProc_atomic_cxchgweak_acq: - case BuiltinProc_atomic_cxchgweak_rel: - case BuiltinProc_atomic_cxchgweak_acqrel: - case BuiltinProc_atomic_cxchgweak_relaxed: - case BuiltinProc_atomic_cxchgweak_failrelaxed: - case BuiltinProc_atomic_cxchgweak_failacq: - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: + case BuiltinProc_atomic_compare_exchange_strong: + case BuiltinProc_atomic_compare_exchange_weak: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -3026,11 +3492,110 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 check_assignment(c, &x, elem, builtin_name); check_assignment(c, &y, elem, builtin_name); + Type *t = type_deref(operand->type); + if (!is_type_comparable(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a comparable type for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } + + operand->mode = Addressing_OptionalOk; + operand->type = elem; + break; + } + + case BuiltinProc_atomic_compare_exchange_strong_explicit: + case BuiltinProc_atomic_compare_exchange_weak_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + Operand y = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_expr_with_type_hint(c, &y, ce->args[2], elem); + check_assignment(c, &x, elem, builtin_name); + check_assignment(c, &y, elem, builtin_name); + + OdinAtomicMemoryOrder success_memory_order = {}; + OdinAtomicMemoryOrder failure_memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[3], builtin_name, &success_memory_order, "success ordering")) { + return false; + } + if (!check_atomic_memory_order_argument(c, ce->args[4], builtin_name, &failure_memory_order, "failure ordering")) { + return false; + } + + Type *t = type_deref(operand->type); + if (!is_type_comparable(t)) { + gbString str = type_to_string(t); + error(operand->expr, "Expected a comparable type for '%.*s', got %s", LIT(builtin_name), str); + gb_string_free(str); + } + + bool invalid_combination = false; + + switch (success_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_release: + if (failure_memory_order != OdinAtomicMemoryOrder_relaxed) { + invalid_combination = true; + } + break; + case OdinAtomicMemoryOrder_consume: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + break; + default: + invalid_combination = true; + break; + } + break; + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_acq_rel: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + break; + default: + invalid_combination = true; + break; + } + break; + case OdinAtomicMemoryOrder_seq_cst: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_seq_cst: + break; + default: + invalid_combination = true; + break; + } + break; + default: + invalid_combination = true; + break; + } + + + if (invalid_combination) { + error(ce->args[3], "Illegal memory order pairing for '%.*s', success = .%s, failure = .%s", + LIT(builtin_name), + OdinAtomicMemoryOrder_strings[success_memory_order], + OdinAtomicMemoryOrder_strings[failure_memory_order] + ); + } + operand->mode = Addressing_OptionalOk; operand->type = elem; break; } - break; case BuiltinProc_fixed_point_mul: case BuiltinProc_fixed_point_div: @@ -3211,8 +3776,9 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case TargetOs_linux: case TargetOs_essence: case TargetOs_freebsd: + case TargetOs_openbsd: switch (build_context.metrics.arch) { - case TargetArch_386: + case TargetArch_i386: case TargetArch_amd64: case TargetArch_arm64: max_arg_count = 7; @@ -3297,9 +3863,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_type_is_simple_compare: case BuiltinProc_type_is_dereferenceable: case BuiltinProc_type_is_valid_map_key: + case BuiltinProc_type_is_valid_matrix_elements: case BuiltinProc_type_is_named: case BuiltinProc_type_is_pointer: case BuiltinProc_type_is_array: + case BuiltinProc_type_is_enumerated_array: case BuiltinProc_type_is_slice: case BuiltinProc_type_is_dynamic_array: case BuiltinProc_type_is_map: @@ -3307,10 +3875,9 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_type_is_union: case BuiltinProc_type_is_enum: case BuiltinProc_type_is_proc: - case BuiltinProc_type_is_bit_field: - case BuiltinProc_type_is_bit_field_value: case BuiltinProc_type_is_bit_set: case BuiltinProc_type_is_simd_vector: + case BuiltinProc_type_is_matrix: case BuiltinProc_type_is_specialized_polymorphic_record: case BuiltinProc_type_is_unspecialized_polymorphic_record: case BuiltinProc_type_has_nil: @@ -3359,6 +3926,37 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } break; + case BuiltinProc_type_field_type: + { + Operand op = {}; + Type *bt = check_type(c, ce->args[0]); + Type *type = base_type(bt); + if (type == nullptr || type == t_invalid) { + error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + check_expr(c, &x, ce->args[1]); + + if (!is_type_string(x.type) || x.mode != Addressing_Constant || x.value.kind != ExactValue_String) { + error(ce->args[1], "Expected a const string for field argument"); + return false; + } + + String field_name = x.value.value_string; + + Selection sel = lookup_field(type, field_name, false); + if (sel.index.count == 0) { + gbString t = type_to_string(type); + error(ce->args[1], "'%.*s' is not a field of type %s", LIT(field_name), t); + gb_string_free(t); + return false; + } + operand->mode = Addressing_Type; + operand->type = sel.entity->type; + break; + } + break; case BuiltinProc_type_is_specialization_of: { @@ -3678,6 +4276,31 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; + case BuiltinProc_type_is_subtype_of: + { + Operand op_src = {}; + Operand op_dst = {}; + + check_expr_or_type(c, &op_src, ce->args[0]); + if (op_src.mode != Addressing_Type) { + gbString e = expr_to_string(op_src.expr); + error(op_src.expr, "'%.*s' expects a type, got %s", LIT(builtin_name), e); + gb_string_free(e); + return false; + } + check_expr_or_type(c, &op_dst, ce->args[1]); + if (op_dst.mode != Addressing_Type) { + gbString e = expr_to_string(op_dst.expr); + error(op_dst.expr, "'%.*s' expects a type, got %s", LIT(builtin_name), e); + gb_string_free(e); + return false; + } + + operand->value = exact_value_bool(is_type_subtype_of(op_src.type, op_dst.type)); + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + } break; + case BuiltinProc_type_field_index_of: { Operand op = {}; @@ -3767,6 +4390,87 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->type = t_hasher_proc; break; } + + case BuiltinProc_constant_utf16_cstring: + { + String value = {}; + if (!is_constant_string(c, builtin_name, ce->args[0], &value)) { + return false; + } + operand->mode = Addressing_Value; + operand->type = alloc_type_multi_pointer(t_u16); + operand->value = {}; + break; + } + + + case BuiltinProc_wasm_memory_grow: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand index = {}; + Operand delta = {}; + check_expr(c, &index, ce->args[0]); if (index.mode == Addressing_Invalid) return false; + check_expr(c, &delta, ce->args[1]); if (delta.mode == Addressing_Invalid) return false; + + convert_to_typed(c, &index, t_uintptr); if (index.mode == Addressing_Invalid) return false; + convert_to_typed(c, &delta, t_uintptr); if (delta.mode == Addressing_Invalid) return false; + + if (!is_operand_value(index) || !check_is_assignable_to(c, &index, t_uintptr)) { + gbString e = expr_to_string(index.expr); + gbString t = type_to_string(index.type); + error(index.expr, "'%.*s' expected a uintptr for the memory index, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(delta) || !check_is_assignable_to(c, &delta, t_uintptr)) { + gbString e = expr_to_string(delta.expr); + gbString t = type_to_string(delta.type); + error(delta.expr, "'%.*s' expected a uintptr for the memory delta, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_int; + operand->value = {}; + break; + } + break; + case BuiltinProc_wasm_memory_size: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand index = {}; + check_expr(c, &index, ce->args[0]); if (index.mode == Addressing_Invalid) return false; + + convert_to_typed(c, &index, t_uintptr); if (index.mode == Addressing_Invalid) return false; + + if (!is_operand_value(index) || !check_is_assignable_to(c, &index, t_uintptr)) { + gbString e = expr_to_string(index.expr); + gbString t = type_to_string(index.type); + error(index.expr, "'%.*s' expected a uintptr for the memory index, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_int; + operand->value = {}; + break; + } + break; + } return true; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 42f68203c..5acd56097 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -174,6 +174,10 @@ void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) { return; } + if (is_type_proc(e->type)) { + error(e->token, "Illegal declaration of a constant procedure value"); + } + e->parent_proc_decl = ctx->curr_proc_decl; e->Constant.value = operand->value; @@ -238,6 +242,51 @@ isize total_attribute_count(DeclInfo *decl) { return attribute_count; } +Type *clone_enum_type(CheckerContext *ctx, Type *original_enum_type, Type *named_type) { + // NOTE(bill, 2022-02-05): Stupid edge case for `distinct` declarations + // + // X :: enum {A, B, C} + // Y :: distinct X + // + // To make Y be just like X, it will need to copy the elements of X and change their type + // so that they match Y rather than X. + GB_ASSERT(original_enum_type != nullptr); + GB_ASSERT(named_type != nullptr); + GB_ASSERT(original_enum_type->kind == Type_Enum); + GB_ASSERT(named_type->kind == Type_Named); + + Scope *parent = original_enum_type->Enum.scope->parent; + Scope *scope = create_scope(nullptr, parent); + + + Type *et = alloc_type_enum(); + et->Enum.base_type = original_enum_type->Enum.base_type; + et->Enum.min_value = original_enum_type->Enum.min_value; + et->Enum.max_value = original_enum_type->Enum.max_value; + et->Enum.min_value_index = original_enum_type->Enum.min_value_index; + et->Enum.max_value_index = original_enum_type->Enum.max_value_index; + et->Enum.scope = scope; + + auto fields = array_make<Entity *>(permanent_allocator(), original_enum_type->Enum.fields.count); + for_array(i, fields) { + Entity *old = original_enum_type->Enum.fields[i]; + + Entity *e = alloc_entity_constant(scope, old->token, named_type, old->Constant.value); + e->file = old->file; + e->identifier = clone_ast(old->identifier); + e->flags |= EntityFlag_Visited; + e->state = EntityState_Resolved; + e->Constant.flags = old->Constant.flags; + e->Constant.docs = old->Constant.docs; + e->Constant.comment = old->Constant.comment; + + fields[i] = e; + add_entity(ctx, scope, nullptr, e); + add_entity_use(ctx, e->identifier, e); + } + et->Enum.fields = fields; + return et; +} void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def) { GB_ASSERT(e->type == nullptr); @@ -258,7 +307,11 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def) Type *bt = check_type_expr(ctx, te, named); check_type_path_pop(ctx); - named->Named.base = base_type(bt); + Type *base = base_type(bt); + if (is_distinct && bt->kind == Type_Named && base->kind == Type_Enum) { + base = clone_enum_type(ctx, base, named); + } + named->Named.base = base; if (is_distinct && is_type_typeid(e->type)) { error(init_expr, "'distinct' cannot be applied to 'typeid'"); @@ -289,6 +342,13 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def) if (decl != nullptr) { AttributeContext ac = {}; check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac); + if (e->kind == Entity_TypeName && ac.objc_class != "") { + e->TypeName.objc_class_name = ac.objc_class; + + if (type_size_of(e->type) > 0) { + error(e->token, "@(objc_class) marked type must be of zero size"); + } + } } @@ -380,12 +440,56 @@ void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, if (type_expr) { e->type = check_type(ctx, type_expr); + if (are_types_identical(e->type, t_typeid)) { + e->type = nullptr; + e->kind = Entity_TypeName; + check_type_decl(ctx, e, init, named_type); + return; + } } Operand operand = {}; if (init != nullptr) { - Entity *entity = nullptr; + Entity *entity = check_entity_from_ident_or_selector(ctx, init, false); + if (entity != nullptr && entity->kind == Entity_TypeName) { + // @TypeAliasingProblem + // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases + // being "confused" as constants + // + // A :: B + // C :: proc "c" (^A) + // B :: struct {x: C} + // + // A gets evaluated first, and then checks B. + // B then checks C. + // C then tries to check A which is unresolved but thought to be a constant. + // Therefore within C's check, A errs as "not a type". + // + // This is because a const declaration may or may not be a type and this cannot + // be determined from a syntactical standpoint. + // This check allows the compiler to override the entity to be checked as a type. + // + // There is no problem if B is prefixed with the `#type` helper enforcing at + // both a syntax and semantic level that B must be a type. + // + // A :: #type B + // + // This approach is not fool proof and can fail in case such as: + // + // X :: type_of(x) + // X :: Foo(int).Type + // + // Since even these kind of declarations may cause weird checking cycles. + // For the time being, these are going to be treated as an unfortunate error + // until there is a proper delaying system to try declaration again if they + // have failed. + + e->kind = Entity_TypeName; + check_type_decl(ctx, e, init, named_type); + return; + } + entity = nullptr; if (init->kind == Ast_Ident) { entity = check_ident(ctx, &operand, init, nullptr, e->type, true); } else if (init->kind == Ast_SelectorExpr) { @@ -732,6 +836,63 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } e->Procedure.optimization_mode = cast(ProcedureOptimizationMode)ac.optimization_mode; + if (ac.objc_name.len || ac.objc_is_class_method || ac.objc_type) { + if (ac.objc_name.len == 0 && ac.objc_is_class_method) { + error(e->token, "@(objc_name) is required with @(objc_is_class_method)"); + } else if (ac.objc_type == nullptr) { + error(e->token, "@(objc_name) requires that @(objc_type) to be set"); + } else if (ac.objc_name.len == 0 && ac.objc_type) { + error(e->token, "@(objc_name) is required with @(objc_type)"); + } else { + Type *t = ac.objc_type; + if (t->kind == Type_Named) { + Entity *tn = t->Named.type_name; + + GB_ASSERT(tn->kind == Entity_TypeName); + + if (tn->scope != e->scope) { + error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); + } else { + mutex_lock(&global_type_name_objc_metadata_mutex); + defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); + + if (!tn->TypeName.objc_metadata) { + tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); + } + auto *md = tn->TypeName.objc_metadata; + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); + + if (!ac.objc_is_class_method) { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + } + } else { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + } + } + } + } + } + } + switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: @@ -777,21 +938,23 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (e->pkg != nullptr && e->token.string == "main") { - if (pt->param_count != 0 || - pt->result_count != 0) { - gbString str = type_to_string(proc_type); - error(e->token, "Procedure type of 'main' was expected to be 'proc()', got %s", str); - gb_string_free(str); - } - if (pt->calling_convention != default_calling_convention()) { - error(e->token, "Procedure 'main' cannot have a custom calling convention"); - } - pt->calling_convention = default_calling_convention(); - if (e->pkg->kind == Package_Init) { - if (ctx->info->entry_point != nullptr) { - error(e->token, "Redeclaration of the entry pointer procedure 'main'"); - } else { - ctx->info->entry_point = e; + if (e->pkg->kind != Package_Runtime) { + if (pt->param_count != 0 || + pt->result_count != 0) { + gbString str = type_to_string(proc_type); + error(e->token, "Procedure type of 'main' was expected to be 'proc()', got %s", str); + gb_string_free(str); + } + if (pt->calling_convention != default_calling_convention()) { + error(e->token, "Procedure 'main' cannot have a custom calling convention"); + } + pt->calling_convention = default_calling_convention(); + if (e->pkg->kind == Package_Init) { + if (ctx->info->entry_point != nullptr) { + error(e->token, "Redeclaration of the entry pointer procedure 'main'"); + } else { + ctx->info->entry_point = e; + } } } } @@ -924,7 +1087,9 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { "\tother at %s", LIT(name), token_pos_to_string(pos)); } else if (name == "main") { - error(d->proc_lit, "The link name 'main' is reserved for internal use"); + if (d->entity->pkg->kind != Package_Runtime) { + error(d->proc_lit, "The link name 'main' is reserved for internal use"); + } } else { string_map_set(fp, key, e); } @@ -971,6 +1136,12 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, } ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { + e->Variable.thread_local_model.len = 0; + // NOTE(bill): ignore this message for the time begin + // error(e->token, "@(thread_local) is not supported for this target platform"); + } + String context_name = str_lit("variable declaration"); if (type_expr != nullptr) { @@ -1046,6 +1217,8 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, Operand o = {}; check_expr_with_type_hint(ctx, &o, init_expr, e->type); check_init_variable(ctx, e, &o, str_lit("variable declaration")); + + check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed"); } void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, DeclInfo *d) { @@ -1303,7 +1476,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty if (t->kind == Type_Struct) { Scope *scope = t->Struct.scope; GB_ASSERT(scope != nullptr); - for_array(i, scope->elements.entries) { + MUTEX_GUARD_BLOCK(scope->mutex) for_array(i, scope->elements.entries) { Entity *f = scope->elements.entries[i].value; if (f->kind == Entity_Variable) { Entity *uvar = alloc_entity_using_variable(e, f->token, f->type, nullptr); @@ -1321,11 +1494,10 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty } } - - for_array(i, using_entities) { + MUTEX_GUARD_BLOCK(ctx->scope->mutex) for_array(i, using_entities) { Entity *e = using_entities[i].e; Entity *uvar = using_entities[i].uvar; - Entity *prev = scope_insert(ctx->scope, uvar); + Entity *prev = scope_insert(ctx->scope, uvar, false); if (prev != nullptr) { error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string)); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 560e5e607..336a711d4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -119,6 +119,115 @@ void check_or_else_split_types(CheckerContext *c, Operand *x, String const &name void check_or_else_expr_no_value_error(CheckerContext *c, String const &name, Operand const &x, Type *type_hint); void check_or_return_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_); + +void check_did_you_mean_print(DidYouMeanAnswers *d, char const *prefix = "") { + auto results = did_you_mean_results(d); + if (results.count != 0) { + error_line("\tSuggestion: Did you mean?\n"); + for_array(i, results) { + String const &target = results[i].target; + error_line("\t\t%s%.*s\n", prefix, LIT(target)); + // error_line("\t\t%.*s %td\n", LIT(target), results[i].distance); + } + } +} + +void populate_check_did_you_mean_objc_entity(StringSet *set, Entity *e, bool is_type) { + if (e->kind != Entity_TypeName) { + return; + } + if (e->TypeName.objc_metadata == nullptr) { + return; + } + TypeNameObjCMetadata *objc_metadata = e->TypeName.objc_metadata; + Type *t = base_type(e->type); + GB_ASSERT(t->kind == Type_Struct); + + if (is_type) { + for_array(i, objc_metadata->type_entries) { + String name = objc_metadata->type_entries[i].name; + string_set_add(set, name); + } + } else { + for_array(i, objc_metadata->value_entries) { + String name = objc_metadata->value_entries[i].name; + string_set_add(set, name); + } + } + + for_array(i, t->Struct.fields) { + Entity *f = t->Struct.fields[i]; + if (f->flags & EntityFlag_Using && f->type != nullptr) { + if (f->type->kind == Type_Named && f->type->Named.type_name) { + populate_check_did_you_mean_objc_entity(set, f->type->Named.type_name, is_type); + } + } + } +} + + +void check_did_you_mean_objc_entity(String const &name, Entity *e, bool is_type, char const *prefix = "") { + ERROR_BLOCK(); + GB_ASSERT(e->kind == Entity_TypeName); + GB_ASSERT(e->TypeName.objc_metadata != nullptr); + auto *objc_metadata = e->TypeName.objc_metadata; + mutex_lock(objc_metadata->mutex); + defer (mutex_unlock(objc_metadata->mutex)); + + StringSet set = {}; + string_set_init(&set, heap_allocator()); + defer (string_set_destroy(&set)); + populate_check_did_you_mean_objc_entity(&set, e, is_type); + + + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), set.entries.count, name); + defer (did_you_mean_destroy(&d)); + for_array(i, set.entries) { + did_you_mean_append(&d, set.entries[i].value); + } + check_did_you_mean_print(&d, prefix); +} + +void check_did_you_mean_type(String const &name, Array<Entity *> const &fields, char const *prefix = "") { + ERROR_BLOCK(); + + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, fields) { + did_you_mean_append(&d, fields[i]->token.string); + } + check_did_you_mean_print(&d, prefix); +} + + +void check_did_you_mean_type(String const &name, Slice<Entity *> const &fields, char const *prefix = "") { + ERROR_BLOCK(); + + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, fields) { + did_you_mean_append(&d, fields[i]->token.string); + } + check_did_you_mean_print(&d, prefix); +} + +void check_did_you_mean_scope(String const &name, Scope *scope, char const *prefix = "") { + ERROR_BLOCK(); + + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), scope->elements.entries.count, name); + defer (did_you_mean_destroy(&d)); + + mutex_lock(&scope->mutex); + for_array(i, scope->elements.entries) { + Entity *e = scope->elements.entries[i].value; + did_you_mean_append(&d, e->token.string); + } + mutex_unlock(&scope->mutex); + check_did_you_mean_print(&d, prefix); +} + Entity *entity_from_expr(Ast *expr) { expr = unparen_expr(expr); switch (expr->kind) { @@ -176,42 +285,6 @@ void check_scope_decls(CheckerContext *c, Slice<Ast *> const &nodes, isize reser } } - -isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isize level = 0, bool src_is_ptr = false) { - Type *prev_src = src; - src = type_deref(src); - if (!src_is_ptr) { - src_is_ptr = src != prev_src; - } - src = base_type(src); - - if (!is_type_struct(src)) { - return 0; - } - - for_array(i, src->Struct.fields) { - Entity *f = src->Struct.fields[i]; - if (f->kind != Entity_Variable || (f->flags&EntityFlag_Using) == 0) { - continue; - } - - if (are_types_identical(f->type, dst)) { - return level+1; - } - if (src_is_ptr && is_type_pointer(dst)) { - if (are_types_identical(f->type, type_deref(dst))) { - return level+1; - } - } - isize nested_level = check_is_assignable_to_using_subtype(f->type, dst, level+1, src_is_ptr); - if (nested_level > 0) { - return nested_level; - } - } - - return 0; -} - bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, Entity *base_entity, Type *type, Array<Operand> *param_operands, Ast *poly_def_node, PolyProcData *poly_proc_data) { /////////////////////////////////////////////////////////////////////////////// @@ -456,6 +529,10 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type); #define MAXIMUM_TYPE_DISTANCE 10 i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type) { + if (c == nullptr) { + GB_ASSERT(operand->mode == Addressing_Value); + GB_ASSERT(is_type_typed(operand->type)); + } if (operand->mode == Addressing_Invalid || type == t_invalid) { return -1; @@ -621,6 +698,42 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type return 1; } } + + // TODO(bill): Determine which rule is a better on in practice + #if 1 + if (dst->Union.variants.count == 1) { + Type *vt = dst->Union.variants[0]; + i64 score = check_distance_between_types(c, operand, vt); + if (score >= 0) { + return score+2; + } + } + #else + // NOTE(bill): check to see you can assign to it with one of the variants? + i64 prev_lowest_score = -1; + i64 lowest_score = -1; + for_array(i, dst->Union.variants) { + Type *vt = dst->Union.variants[i]; + i64 score = check_distance_between_types(c, operand, vt); + if (score >= 0) { + if (lowest_score < 0) { + lowest_score = score; + } else { + if (prev_lowest_score < 0) { + prev_lowest_score = lowest_score; + } else { + prev_lowest_score = gb_min(prev_lowest_score, lowest_score); + } + lowest_score = gb_min(lowest_score, score); + } + } + } + if (lowest_score >= 0) { + if (prev_lowest_score != lowest_score) { // remove possible ambiguities + return lowest_score+2; + } + } + #endif } if (is_type_relative_pointer(dst)) { @@ -649,6 +762,13 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type return 4; } } + + if (is_type_complex_or_quaternion(dst)) { + Type *elem = base_complex_elem_type(dst); + if (are_types_identical(elem, base_type(src))) { + return 5; + } + } if (is_type_array(dst)) { Type *elem = base_array_type(dst); @@ -723,6 +843,13 @@ bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type) { return check_is_assignable_to_with_score(c, operand, type, &score); } +bool internal_check_is_assignable_to(Type *src, Type *dst) { + Operand x = {}; + x.type = src; + x.mode = Addressing_Value; + return check_is_assignable_to(nullptr, &x, dst); +} + AstPackage *get_package_of_type(Type *type) { for (;;) { if (type == nullptr) { @@ -1227,7 +1354,6 @@ bool check_cycle(CheckerContext *c, Entity *curr, bool report) { return false; } - Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name) { GB_ASSERT(n->kind == Ast_Ident); o->mode = Addressing_Invalid; @@ -1363,8 +1489,12 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ case Entity_TypeName: o->mode = Addressing_Type; if (check_cycle(c, e, true)) { - type = t_invalid; + o->type = t_invalid; + } + if (o->type != nullptr && type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { + o->type = base_type(o->type); } + break; case Entity_ImportName: @@ -3354,7 +3484,16 @@ void convert_untyped_error(CheckerContext *c, Operand *operand, Type *target_typ } } } + ERROR_BLOCK(); + error(operand->expr, "Cannot convert untyped value '%s' to '%s' from '%s'%s", expr_str, type_str, from_type_str, extra_text); + if (operand->value.kind == ExactValue_String) { + String key = operand->value.value_string; + if (is_type_string(operand->type) && is_type_enum(target_type)) { + Type *et = base_type(target_type); + check_did_you_mean_type(key, et->Enum.fields, "."); + } + } gb_string_free(from_type_str); gb_string_free(type_str); @@ -3972,54 +4111,6 @@ ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selecti if (success_) *success_ = true; return empty_exact_value; } -void check_did_you_mean_print(DidYouMeanAnswers *d) { - auto results = did_you_mean_results(d); - if (results.count != 0) { - error_line("\tSuggestion: Did you mean?\n"); - for_array(i, results) { - String const &target = results[i].target; - error_line("\t\t%.*s\n", LIT(target)); - // error_line("\t\t%.*s %td\n", LIT(target), results[i].distance); - } - } -} - -void check_did_you_mean_type(String const &name, Array<Entity *> const &fields) { - ERROR_BLOCK(); - - DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); - defer (did_you_mean_destroy(&d)); - - for_array(i, fields) { - did_you_mean_append(&d, fields[i]->token.string); - } - check_did_you_mean_print(&d); -} - -void check_did_you_mean_type(String const &name, Slice<Entity *> const &fields) { - ERROR_BLOCK(); - - DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); - defer (did_you_mean_destroy(&d)); - - for_array(i, fields) { - did_you_mean_append(&d, fields[i]->token.string); - } - check_did_you_mean_print(&d); -} - -void check_did_you_mean_scope(String const &name, Scope *scope) { - ERROR_BLOCK(); - - DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), scope->elements.entries.count, name); - defer (did_you_mean_destroy(&d)); - - for_array(i, scope->elements.entries) { - Entity *e = scope->elements.entries[i].value; - did_you_mean_append(&d, e->token.string); - } - check_did_you_mean_print(&d); -} Type *determine_swizzle_array_type(Type *original_type, Type *type_hint, isize new_count) { Type *array_type = base_type(type_deref(original_type)); @@ -4044,6 +4135,101 @@ Type *determine_swizzle_array_type(Type *original_type, Type *type_hint, isize n } +bool is_entity_declared_for_selector(Entity *entity, Scope *import_scope, bool *allow_builtin) { + bool is_declared = entity != nullptr; + if (is_declared) { + if (entity->kind == Entity_Builtin) { + // NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy + // This means that we should just ignore the found result through it + *allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope; + } else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) { + is_declared = false; + } + } + return is_declared; +} + +// NOTE(bill, 2022-02-03): see `check_const_decl` for why it exists reasoning +Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast *node, bool ident_only) { + if (node->kind == Ast_Ident) { + String name = node->Ident.token.string; + return scope_lookup(c->scope, name); + } else if (!ident_only) if (node->kind == Ast_SelectorExpr) { + ast_node(se, SelectorExpr, node); + if (se->token.kind == Token_ArrowRight) { + return nullptr; + } + + Ast *op_expr = se->expr; + Ast *selector = unparen_expr(se->selector); + if (selector == nullptr) { + return nullptr; + } + if (selector->kind != Ast_Ident) { + return nullptr; + } + + Entity *entity = nullptr; + Entity *expr_entity = nullptr; + bool check_op_expr = true; + + if (op_expr->kind == Ast_Ident) { + String op_name = op_expr->Ident.token.string; + Entity *e = scope_lookup(c->scope, op_name); + if (e == nullptr) { + return nullptr; + } + add_entity_use(c, op_expr, e); + expr_entity = e; + + if (e != nullptr && e->kind == Entity_ImportName && selector->kind == Ast_Ident) { + // IMPORTANT NOTE(bill): This is very sloppy code but it's also very fragile + // It pretty much needs to be in this order and this way + // If you can clean this up, please do but be really careful + String import_name = op_name; + Scope *import_scope = e->ImportName.scope; + String entity_name = selector->Ident.token.string; + + check_op_expr = false; + entity = scope_lookup_current(import_scope, entity_name); + bool allow_builtin = false; + if (!is_entity_declared_for_selector(entity, import_scope, &allow_builtin)) { + return nullptr; + } + + check_entity_decl(c, entity, nullptr, nullptr); + if (entity->kind == Entity_ProcGroup) { + return entity; + } + GB_ASSERT_MSG(entity->type != nullptr, "%.*s (%.*s)", LIT(entity->token.string), LIT(entity_strings[entity->kind])); + } + } + + Operand operand = {}; + if (check_op_expr) { + check_expr_base(c, &operand, op_expr, nullptr); + if (operand.mode == Addressing_Invalid) { + return nullptr; + } + } + + if (entity == nullptr && selector->kind == Ast_Ident) { + String field_name = selector->Ident.token.string; + if (is_type_dynamic_array(type_deref(operand.type))) { + init_mem_allocator(c->checker); + } + auto sel = lookup_field(operand.type, field_name, operand.mode == Addressing_Type); + entity = sel.entity; + } + + if (entity != nullptr) { + return entity; + } + } + return nullptr; +} + + Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *type_hint) { ast_node(se, SelectorExpr, node); @@ -4092,18 +4278,8 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ check_op_expr = false; entity = scope_lookup_current(import_scope, entity_name); - bool is_declared = entity != nullptr; bool allow_builtin = false; - if (is_declared) { - if (entity->kind == Entity_Builtin) { - // NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy - // This means that we should just ignore the found result through it - allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope; - } else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) { - is_declared = false; - } - } - if (!is_declared) { + if (!is_entity_declared_for_selector(entity, import_scope, &allow_builtin)) { error(op_expr, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name)); operand->mode = Addressing_Invalid; operand->expr = node; @@ -4193,7 +4369,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ } } - if (entity == nullptr && selector->kind == Ast_Ident && is_type_array(type_deref(operand->type))) { + if (entity == nullptr && selector->kind == Ast_Ident && is_type_array(type_deref(operand->type))) { // TODO(bill): Simd_Vector swizzling String field_name = selector->Ident.token.string; @@ -4294,14 +4470,19 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ if (entity == nullptr) { gbString op_str = expr_to_string(op_expr); - gbString type_str = type_to_string(operand->type); + gbString type_str = type_to_string_shorthand(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str); if (operand->type != nullptr && selector->kind == Ast_Ident) { String const &name = selector->Ident.token.string; Type *bt = base_type(operand->type); - if (bt->kind == Type_Struct) { + if (operand->type->kind == Type_Named && + operand->type->Named.type_name && + operand->type->Named.type_name->kind == Entity_TypeName && + operand->type->Named.type_name->TypeName.objc_metadata) { + check_did_you_mean_objc_entity(name, operand->type->Named.type_name, operand->mode == Addressing_Type); + } else if (bt->kind == Type_Struct) { check_did_you_mean_type(name, bt->Struct.fields); } else if (bt->kind == Type_Enum) { check_did_you_mean_type(name, bt->Enum.fields); @@ -4330,7 +4511,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ } gbString op_str = expr_to_string(op_expr); - gbString type_str = type_to_string(operand->type); + gbString type_str = type_to_string_shorthand(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "Cannot access non-constant field '%s' from '%s'", sel_str, op_str); gb_string_free(sel_str); @@ -4355,7 +4536,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ } gbString op_str = expr_to_string(op_expr); - gbString type_str = type_to_string(operand->type); + gbString type_str = type_to_string_shorthand(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "Cannot access non-constant field '%s' from '%s'", sel_str, op_str); gb_string_free(sel_str); @@ -4368,7 +4549,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ if (expr_entity != nullptr && is_type_polymorphic(expr_entity->type)) { gbString op_str = expr_to_string(op_expr); - gbString type_str = type_to_string(operand->type); + gbString type_str = type_to_string_shorthand(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "Cannot access field '%s' from non-specialized polymorphic type '%s'", sel_str, op_str); gb_string_free(sel_str); @@ -4691,25 +4872,16 @@ bool is_expr_constant_zero(Ast *expr) { return false; } - -CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { - ast_node(ce, CallExpr, call); - GB_ASSERT(is_type_proc(proc_type)); - proc_type = base_type(proc_type); - TypeProc *pt = &proc_type->Proc; - +isize get_procedure_param_count_excluding_defaults(Type *pt, isize *param_count_) { + GB_ASSERT(pt != nullptr); + GB_ASSERT(pt->kind == Type_Proc); isize param_count = 0; isize param_count_excluding_defaults = 0; - bool variadic = pt->variadic; - bool vari_expand = (ce->ellipsis.pos.line != 0); - i64 score = 0; - bool show_error = show_error_mode == CallArgumentMode_ShowErrors; - - + bool variadic = pt->Proc.variadic; TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; + if (pt->Proc.params != nullptr) { + param_tuple = &pt->Proc.params->Tuple; param_count = param_tuple->variables.count; if (variadic) { @@ -4749,6 +4921,31 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } } + if (param_count_) *param_count_ = param_count; + return param_count_excluding_defaults; +} + + +CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { + ast_node(ce, CallExpr, call); + GB_ASSERT(is_type_proc(proc_type)); + proc_type = base_type(proc_type); + TypeProc *pt = &proc_type->Proc; + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); + bool variadic = pt->variadic; + bool vari_expand = (ce->ellipsis.pos.line != 0); + i64 score = 0; + bool show_error = show_error_mode == CallArgumentMode_ShowErrors; + + + TypeTuple *param_tuple = nullptr; + if (pt->params != nullptr) { + param_tuple = &pt->params->Tuple; + } + + CallArgumentError err = CallArgumentError_None; Type *final_proc_type = proc_type; Entity *gen_entity = nullptr; @@ -5421,7 +5618,37 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type if (operand->mode == Addressing_ProcGroup) { check_entity_decl(c, operand->proc_group, nullptr, nullptr); - Array<Entity *> procs = proc_group_entities(c, *operand); + auto procs = proc_group_entities_cloned(c, *operand); + + if (procs.count > 1) { + isize max_arg_count = args.count; + for_array(i, args) { + // NOTE(bill): The only thing that may have multiple values + // will be a call expression (assuming `or_return` and `()` will be stripped) + Ast *arg = strip_or_return_expr(args[i]); + if (arg && arg->kind == Ast_CallExpr) { + max_arg_count = ISIZE_MAX; + break; + } + } + + for (isize proc_index = 0; proc_index < procs.count; /**/) { + Entity *proc = procs[proc_index]; + Type *pt = base_type(proc->type); + if (!(pt != nullptr && is_type_proc(pt))) { + continue; + } + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(pt, ¶m_count); + + if (param_count_excluding_defaults > max_arg_count) { + array_unordered_remove(&procs, proc_index); + } else { + proc_index++; + } + } + } if (procs.count == 1) { Ast *ident = operand->expr; @@ -5451,6 +5678,7 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type return data; } + Entity **lhs = nullptr; isize lhs_count = -1; @@ -5735,8 +5963,12 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type ctx.curr_proc_sig = e->type; GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } } return data; } @@ -5749,6 +5981,7 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type Entity *e = entity_of_node(ident); + CallArgumentData data = {}; CallArgumentError err = call_checker(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); gb_unused(err); @@ -5757,7 +5990,6 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type if (entity_to_use != nullptr) { update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); } - if (data.gen_entity != nullptr) { Entity *e = data.gen_entity; DeclInfo *decl = data.gen_entity->decl_info; @@ -5769,8 +6001,12 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type ctx.curr_proc_sig = e->type; GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } } return data; } @@ -6064,7 +6300,8 @@ CallArgumentError check_polymorphic_record_type(CheckerContext *c, Operand *oper } // NOTE(bill): Add type info the parameters - add_type_info_type(c, o->type); + // TODO(bill, 2022-01-23): why was this line added in the first place? I'm commenting it out for the time being + // add_type_info_type(c, o->type); } { @@ -6268,10 +6505,10 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr return builtin_procs[id].kind; } - Entity *e = entity_of_node(operand->expr); + Entity *initial_entity = entity_of_node(operand->expr); - if (e != nullptr && e->kind == Entity_Procedure) { - if (e->Procedure.deferred_procedure.entity != nullptr) { + if (initial_entity != nullptr && initial_entity->kind == Entity_Procedure) { + if (initial_entity->Procedure.deferred_procedure.entity != nullptr) { call->viral_state_flags |= ViralStateFlag_ContainsDeferredProcedure; } } @@ -6839,433 +7076,395 @@ void check_matrix_index_expr(CheckerContext *c, Operand *o, Ast *node, Type *typ } -ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { - u32 prev_state_flags = c->state_flags; - defer (c->state_flags = prev_state_flags); - if (node->state_flags != 0) { - u32 in = node->state_flags; - u32 out = c->state_flags; +struct TypeAndToken { + Type *type; + Token token; +}; - if (in & StateFlag_no_bounds_check) { - out |= StateFlag_no_bounds_check; - out &= ~StateFlag_bounds_check; - } else if (in & StateFlag_bounds_check) { - out |= StateFlag_bounds_check; - out &= ~StateFlag_no_bounds_check; - } +typedef PtrMap<uintptr, TypeAndToken> SeenMap; - c->state_flags = out; +void add_constant_switch_case(CheckerContext *ctx, SeenMap *seen, Operand operand, bool use_expr = true) { + if (operand.mode != Addressing_Constant) { + return; + } + if (operand.value.kind == ExactValue_Invalid) { + return; } - ExprKind kind = Expr_Stmt; - - o->mode = Addressing_Invalid; - o->type = t_invalid; - - switch (node->kind) { - default: - return kind; + uintptr key = hash_exact_value(operand.value); + TypeAndToken *found = map_get(seen, key); + if (found != nullptr) { + isize count = multi_map_count(seen, key); + TypeAndToken *taps = gb_alloc_array(temporary_allocator(), TypeAndToken, count); - case_ast_node(be, BadExpr, node) - return kind; - case_end; + multi_map_get_all(seen, key, taps); + for (isize i = 0; i < count; i++) { + TypeAndToken tap = taps[i]; + if (!are_types_identical(operand.type, tap.type)) { + continue; + } - case_ast_node(i, Implicit, node) - switch (i->kind) { - case Token_context: - { - if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) { - error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl); - return kind; - } - if (unparen_expr(c->assignment_lhs_hint) == node) { - c->scope->flags |= ScopeFlag_ContextDefined; - } + TokenPos pos = tap.token.pos; + if (use_expr) { + gbString expr_str = expr_to_string(operand.expr); + error(operand.expr, + "Duplicate case '%s'\n" + "\tprevious case at %s", + expr_str, + token_pos_to_string(pos)); + gb_string_free(expr_str); + } else { + error(operand.expr, "Duplicate case found with previous case at %s", token_pos_to_string(pos)); + } + return; + } + } - if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { - error(node, "'context' has not been defined within this scope"); - // Continue with value - } + TypeAndToken tap = {operand.type, ast_token(operand.expr)}; + multi_map_insert(seen, key, tap); +} - init_core_context(c->checker); - o->mode = Addressing_Context; - o->type = t_context; - } - break; - default: - error(node, "Illegal implicit name '%.*s'", LIT(i->string)); - return kind; - } - case_end; +void add_to_seen_map(CheckerContext *ctx, SeenMap *seen, TokenKind upper_op, Operand const &x, Operand const &lhs, Operand const &rhs) { + if (is_type_enum(x.type)) { + // TODO(bill): Fix this logic so it's fast!!! - case_ast_node(i, Ident, node); - check_ident(c, o, node, nullptr, type_hint, false); - case_end; + i64 v0 = exact_value_to_i64(lhs.value); + i64 v1 = exact_value_to_i64(rhs.value); + Operand v = {}; + v.mode = Addressing_Constant; + v.type = x.type; + v.expr = x.expr; - case_ast_node(u, Undef, node); - o->mode = Addressing_Value; - o->type = t_untyped_undef; - case_end; + Type *bt = base_type(x.type); + GB_ASSERT(bt->kind == Type_Enum); + for (i64 vi = v0; vi <= v1; vi++) { + if (upper_op != Token_LtEq && vi == v1) { + break; + } + bool found = false; + for_array(j, bt->Enum.fields) { + Entity *f = bt->Enum.fields[j]; + GB_ASSERT(f->kind == Entity_Constant); - case_ast_node(bl, BasicLit, node); - Type *t = t_invalid; - switch (node->tav.value.kind) { - case ExactValue_String: t = t_untyped_string; break; - case ExactValue_Float: t = t_untyped_float; break; - case ExactValue_Complex: t = t_untyped_complex; break; - case ExactValue_Quaternion: t = t_untyped_quaternion; break; - case ExactValue_Integer: - t = t_untyped_integer; - if (bl->token.kind == Token_Rune) { - t = t_untyped_rune; + i64 fv = exact_value_to_i64(f->Constant.value); + if (fv == vi) { + found = true; + break; + } + } + if (found) { + v.value = exact_value_i64(vi); + add_constant_switch_case(ctx, seen, v); } - break; - default: - GB_PANIC("Unhandled value type for basic literal"); - break; } + } else { + add_constant_switch_case(ctx, seen, lhs); + if (upper_op == Token_LtEq) { + add_constant_switch_case(ctx, seen, rhs); + } + } +} +void add_to_seen_map(CheckerContext *ctx, SeenMap *seen, Operand const &x) { + add_constant_switch_case(ctx, seen, x); +} - o->mode = Addressing_Constant; - o->type = t; - o->value = node->tav.value; - case_end; +ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ast_node(bd, BasicDirective, node); - case_ast_node(bd, BasicDirective, node); - o->mode = Addressing_Constant; - String name = bd->name.string; - if (name == "file") { + ExprKind kind = Expr_Expr; + + o->mode = Addressing_Constant; + String name = bd->name.string; + if (name == "file") { + o->type = t_untyped_string; + o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id)); + } else if (name == "line") { + o->type = t_untyped_integer; + o->value = exact_value_i64(bd->token.pos.line); + } else if (name == "procedure") { + if (c->curr_proc_decl == nullptr) { + error(node, "#procedure may only be used within procedures"); o->type = t_untyped_string; - o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id)); - } else if (name == "line") { - o->type = t_untyped_integer; - o->value = exact_value_i64(bd->token.pos.line); - } else if (name == "procedure") { - if (c->curr_proc_decl == nullptr) { - error(node, "#procedure may only be used within procedures"); - o->type = t_untyped_string; - o->value = exact_value_string(str_lit("")); - } else { - o->type = t_untyped_string; - o->value = exact_value_string(c->proc_name); - } - } else if (name == "caller_location") { + o->value = exact_value_string(str_lit("")); + } else { + o->type = t_untyped_string; + o->value = exact_value_string(c->proc_name); + } + } else if (name == "caller_location") { + init_core_source_code_location(c->checker); + error(node, "#caller_location may only be used as a default argument parameter"); + o->type = t_source_code_location; + o->mode = Addressing_Value; + } else { + if (name == "location") { init_core_source_code_location(c->checker); - error(node, "#caller_location may only be used as a default argument parameter"); + error(node, "'#%.*s' must be used in a call expression", LIT(name)); o->type = t_source_code_location; o->mode = Addressing_Value; + } else if ( + name == "assert" || + name == "defined" || + name == "config" || + name == "load" || + name == "load_hash" || + name == "load_or" + ) { + error(node, "'#%.*s' must be used as a call", LIT(name)); + o->type = t_invalid; + o->mode = Addressing_Invalid; } else { - if (name == "location") { - init_core_source_code_location(c->checker); - error(node, "'#%.*s' must be used in a call expression", LIT(name)); - o->type = t_source_code_location; - o->mode = Addressing_Value; - } else if ( - name == "assert" || - name == "defined" || - name == "config" || - name == "load" || - name == "load_hash" || - name == "load_or" - ) { - error(node, "'#%.*s' must be used as a call", LIT(name)); - o->type = t_invalid; - o->mode = Addressing_Invalid; - } else { - error(node, "Unknown directive: #%.*s", LIT(name)); - o->type = t_invalid; - o->mode = Addressing_Invalid; - } - + error(node, "Unknown directive: #%.*s", LIT(name)); + o->type = t_invalid; + o->mode = Addressing_Invalid; } - case_end; - case_ast_node(pg, ProcGroup, node); - error(node, "Illegal use of a procedure group"); - o->mode = Addressing_Invalid; - case_end; + } + return kind; +} - case_ast_node(pl, ProcLit, node); - CheckerContext ctx = *c; +ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Expr; + Operand cond = {Addressing_Invalid}; + ast_node(te, TernaryIfExpr, node); + check_expr(c, &cond, te->cond); + node->viral_state_flags |= te->cond->viral_state_flags; - DeclInfo *decl = nullptr; - Type *type = alloc_type(Type_Proc); - check_open_scope(&ctx, pl->type); - { - decl = make_decl_info(ctx.scope, ctx.decl); - decl->proc_lit = node; - ctx.decl = decl; - defer (ctx.decl = ctx.decl->parent); + if (cond.mode != Addressing_Invalid && !is_type_boolean(cond.type)) { + error(te->cond, "Non-boolean condition in ternary if expression"); + } - if (pl->tags != 0) { - error(node, "A procedure literal cannot have tags"); - pl->tags = 0; // TODO(bill): Should I zero this?! - } + Operand x = {Addressing_Invalid}; + Operand y = {Addressing_Invalid}; + check_expr_or_type(c, &x, te->x, type_hint); + node->viral_state_flags |= te->x->viral_state_flags; - check_procedure_type(&ctx, type, pl->type); - if (!is_type_proc(type)) { - gbString str = expr_to_string(node); - error(node, "Invalid procedure literal '%s'", str); - gb_string_free(str); - check_close_scope(&ctx); - return kind; - } + if (te->y != nullptr) { + check_expr_or_type(c, &y, te->y, type_hint); + node->viral_state_flags |= te->y->viral_state_flags; + } else { + error(node, "A ternary expression must have an else clause"); + return kind; + } - if (pl->body == nullptr) { - error(node, "A procedure literal must have a body"); - return kind; - } + if (x.type == nullptr || x.type == t_invalid || + y.type == nullptr || y.type == t_invalid) { + return kind; + } - pl->decl = decl; - check_procedure_later(&ctx, ctx.file, empty_token, decl, type, pl->body, pl->tags); - } - check_close_scope(&ctx); + convert_to_typed(c, &x, y.type); + if (x.mode == Addressing_Invalid) { + return kind; + } + convert_to_typed(c, &y, x.type); + if (y.mode == Addressing_Invalid) { + x.mode = Addressing_Invalid; + return kind; + } - o->mode = Addressing_Value; - o->type = type; - case_end; + if (!ternary_compare_types(x.type, y.type)) { + gbString its = type_to_string(x.type); + gbString ets = type_to_string(y.type); + error(node, "Mismatched types in ternary if expression, %s vs %s", its, ets); + gb_string_free(ets); + gb_string_free(its); + return kind; + } - case_ast_node(te, TernaryIfExpr, node); - Operand cond = {Addressing_Invalid}; - check_expr(c, &cond, te->cond); - node->viral_state_flags |= te->cond->viral_state_flags; + o->type = x.type; + if (is_type_untyped_nil(o->type) || is_type_untyped_undef(o->type)) { + o->type = y.type; + } - if (cond.mode != Addressing_Invalid && !is_type_boolean(cond.type)) { - error(te->cond, "Non-boolean condition in ternary if expression"); + o->mode = Addressing_Value; + o->expr = node; + if (type_hint != nullptr && is_type_untyped(o->type)) { + if (check_cast_internal(c, &x, type_hint) && + check_cast_internal(c, &y, type_hint)) { + convert_to_typed(c, o, type_hint); + update_untyped_expr_type(c, node, type_hint, !is_type_untyped(type_hint)); } + } + return kind; +} - Operand x = {Addressing_Invalid}; - Operand y = {Addressing_Invalid}; - check_expr_or_type(c, &x, te->x, type_hint); - node->viral_state_flags |= te->x->viral_state_flags; +ExprKind check_ternary_when_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Expr; + Operand cond = {}; + ast_node(te, TernaryWhenExpr, node); + check_expr(c, &cond, te->cond); + node->viral_state_flags |= te->cond->viral_state_flags; + + if (cond.mode != Addressing_Constant || !is_type_boolean(cond.type)) { + error(te->cond, "Expected a constant boolean condition in ternary when expression"); + return kind; + } + if (cond.value.value_bool) { + check_expr_or_type(c, o, te->x, type_hint); + node->viral_state_flags |= te->x->viral_state_flags; + } else { if (te->y != nullptr) { - check_expr_or_type(c, &y, te->y, type_hint); + check_expr_or_type(c, o, te->y, type_hint); node->viral_state_flags |= te->y->viral_state_flags; } else { - error(node, "A ternary expression must have an else clause"); - return kind; - } - - if (x.type == nullptr || x.type == t_invalid || - y.type == nullptr || y.type == t_invalid) { - return kind; - } - - convert_to_typed(c, &x, y.type); - if (x.mode == Addressing_Invalid) { - return kind; - } - convert_to_typed(c, &y, x.type); - if (y.mode == Addressing_Invalid) { - x.mode = Addressing_Invalid; + error(node, "A ternary when expression must have an else clause"); return kind; } + } + return kind; +} - if (!ternary_compare_types(x.type, y.type)) { - gbString its = type_to_string(x.type); - gbString ets = type_to_string(y.type); - error(node, "Mismatched types in ternary if expression, %s vs %s", its, ets); - gb_string_free(ets); - gb_string_free(its); - return kind; - } +ExprKind check_or_else_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ast_node(oe, OrElseExpr, node); - o->type = x.type; - if (is_type_untyped_nil(o->type) || is_type_untyped_undef(o->type)) { - o->type = y.type; - } + String name = oe->token.string; + Ast *arg = oe->x; + Ast *default_value = oe->y; + Operand x = {}; + Operand y = {}; + check_multi_expr_with_type_hint(c, &x, arg, type_hint); + if (x.mode == Addressing_Invalid) { o->mode = Addressing_Value; + o->type = t_invalid; o->expr = node; - if (type_hint != nullptr && is_type_untyped(o->type)) { - if (check_cast_internal(c, &x, type_hint) && - check_cast_internal(c, &y, type_hint)) { - convert_to_typed(c, o, type_hint); - update_untyped_expr_type(c, node, type_hint, !is_type_untyped(type_hint)); - } - } - case_end; - - case_ast_node(te, TernaryWhenExpr, node); - Operand cond = {}; - check_expr(c, &cond, te->cond); - node->viral_state_flags |= te->cond->viral_state_flags; - - if (cond.mode != Addressing_Constant || !is_type_boolean(cond.type)) { - error(te->cond, "Expected a constant boolean condition in ternary when expression"); - return kind; - } - - if (cond.value.value_bool) { - check_expr_or_type(c, o, te->x, type_hint); - node->viral_state_flags |= te->x->viral_state_flags; - } else { - if (te->y != nullptr) { - check_expr_or_type(c, o, te->y, type_hint); - node->viral_state_flags |= te->y->viral_state_flags; - } else { - error(node, "A ternary when expression must have an else clause"); - return kind; - } - } - case_end; + return Expr_Expr; + } - case_ast_node(oe, OrElseExpr, node); - String name = oe->token.string; - Ast *arg = oe->x; - Ast *default_value = oe->y; + check_multi_expr_with_type_hint(c, &y, default_value, x.type); + error_operand_no_value(&y); + if (y.mode == Addressing_Invalid) { + o->mode = Addressing_Value; + o->type = t_invalid; + o->expr = node; + return Expr_Expr; + } - Operand x = {}; - Operand y = {}; - check_multi_expr_with_type_hint(c, &x, arg, type_hint); - if (x.mode == Addressing_Invalid) { - o->mode = Addressing_Value; - o->type = t_invalid; - o->expr = node; - return Expr_Expr; - } + Type *left_type = nullptr; + Type *right_type = nullptr; + check_or_else_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, arg, x.mode, x.type, x.value); - check_multi_expr_with_type_hint(c, &y, default_value, x.type); - error_operand_no_value(&y); - if (y.mode == Addressing_Invalid) { - o->mode = Addressing_Value; - o->type = t_invalid; - o->expr = node; - return Expr_Expr; - } + if (left_type != nullptr) { + check_assignment(c, &y, left_type, name); + } else { + check_or_else_expr_no_value_error(c, name, x, type_hint); + } - Type *left_type = nullptr; - Type *right_type = nullptr; - check_or_else_split_types(c, &x, name, &left_type, &right_type); - add_type_and_value(&c->checker->info, arg, x.mode, x.type, x.value); + if (left_type == nullptr) { + left_type = t_invalid; + } + o->mode = Addressing_Value; + o->type = left_type; + o->expr = node; + return Expr_Expr; +} - if (left_type != nullptr) { - check_assignment(c, &y, left_type, name); - } else { - check_or_else_expr_no_value_error(c, name, x, type_hint); - } +ExprKind check_or_return_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ast_node(re, OrReturnExpr, node); - if (left_type == nullptr) { - left_type = t_invalid; - } + String name = re->token.string; + Operand x = {}; + check_multi_expr_with_type_hint(c, &x, re->expr, type_hint); + if (x.mode == Addressing_Invalid) { o->mode = Addressing_Value; - o->type = left_type; + o->type = t_invalid; o->expr = node; return Expr_Expr; - case_end; - - case_ast_node(re, OrReturnExpr, node); - String name = re->token.string; - Operand x = {}; - check_multi_expr_with_type_hint(c, &x, re->expr, type_hint); - if (x.mode == Addressing_Invalid) { - o->mode = Addressing_Value; - o->type = t_invalid; - o->expr = node; - return Expr_Expr; - } + } - Type *left_type = nullptr; - Type *right_type = nullptr; - check_or_return_split_types(c, &x, name, &left_type, &right_type); - add_type_and_value(&c->checker->info, re->expr, x.mode, x.type, x.value); + Type *left_type = nullptr; + Type *right_type = nullptr; + check_or_return_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, re->expr, x.mode, x.type, x.value); - if (right_type == nullptr) { - check_or_else_expr_no_value_error(c, name, x, type_hint); + if (right_type == nullptr) { + check_or_else_expr_no_value_error(c, name, x, type_hint); + } else { + Type *proc_type = base_type(c->curr_proc_sig); + GB_ASSERT(proc_type->kind == Type_Proc); + Type *result_type = proc_type->Proc.results; + if (result_type == nullptr) { + error(node, "'%.*s' requires the current procedure to have at least one return value", LIT(name)); } else { - Type *proc_type = base_type(c->curr_proc_sig); - GB_ASSERT(proc_type->kind == Type_Proc); - Type *result_type = proc_type->Proc.results; - if (result_type == nullptr) { - error(node, "'%.*s' requires the current procedure to have at least one return value", LIT(name)); - } else { - GB_ASSERT(result_type->kind == Type_Tuple); + GB_ASSERT(result_type->kind == Type_Tuple); - auto const &vars = result_type->Tuple.variables; - Type *end_type = vars[vars.count-1]->type; + auto const &vars = result_type->Tuple.variables; + Type *end_type = vars[vars.count-1]->type; - if (vars.count > 1) { - if (!proc_type->Proc.has_named_results) { - error(node, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name)); - } + if (vars.count > 1) { + if (!proc_type->Proc.has_named_results) { + error(node, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name)); } + } - Operand rhs = {}; - rhs.type = right_type; - rhs.mode = Addressing_Value; + Operand rhs = {}; + rhs.type = right_type; + rhs.mode = Addressing_Value; - // TODO(bill): better error message - if (!check_is_assignable_to(c, &rhs, end_type)) { - gbString a = type_to_string(right_type); - gbString b = type_to_string(end_type); - gbString ret_type = type_to_string(result_type); - error(node, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name)); - if (vars.count == 1) { - error_line("\tProcedure return value type: %s\n", ret_type); - } else { - error_line("\tProcedure return value types: (%s)\n", ret_type); - } - gb_string_free(ret_type); - gb_string_free(b); - gb_string_free(a); + // TODO(bill): better error message + if (!check_is_assignable_to(c, &rhs, end_type)) { + gbString a = type_to_string(right_type); + gbString b = type_to_string(end_type); + gbString ret_type = type_to_string(result_type); + error(node, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name)); + if (vars.count == 1) { + error_line("\tProcedure return value type: %s\n", ret_type); + } else { + error_line("\tProcedure return value types: (%s)\n", ret_type); } + gb_string_free(ret_type); + gb_string_free(b); + gb_string_free(a); } } + } - o->expr = node; - o->type = left_type; - if (left_type != nullptr) { - o->mode = Addressing_Value; - } else { - o->mode = Addressing_NoValue; - } + o->expr = node; + o->type = left_type; + if (left_type != nullptr) { + o->mode = Addressing_Value; + } else { + o->mode = Addressing_NoValue; + } - if (c->curr_proc_sig == nullptr) { - error(node, "'%.*s' can only be used within a procedure", LIT(name)); - } - - if (c->in_defer) { - error(node, "'or_return' cannot be used within a defer statement"); - } + if (c->curr_proc_sig == nullptr) { + error(node, "'%.*s' can only be used within a procedure", LIT(name)); + } - return Expr_Expr; - case_end; + if (c->in_defer) { + error(node, "'or_return' cannot be used within a defer statement"); + } - case_ast_node(cl, CompoundLit, node); - Type *type = type_hint; - if (type != nullptr && is_type_untyped(type)) { - type = nullptr; - } - bool is_to_be_determined_array_count = false; - bool is_constant = true; - if (cl->type != nullptr) { - type = nullptr; - - // [?]Type - if (cl->type->kind == Ast_ArrayType && cl->type->ArrayType.count != nullptr) { - Ast *count = cl->type->ArrayType.count; - if (count->kind == Ast_UnaryExpr && - count->UnaryExpr.op.kind == Token_Question) { - type = alloc_type_array(check_type(c, cl->type->ArrayType.elem), -1); - is_to_be_determined_array_count = true; - } - if (cl->elems.count > 0) { - if (cl->type->ArrayType.tag != nullptr) { - Ast *tag = cl->type->ArrayType.tag; - GB_ASSERT(tag->kind == Ast_BasicDirective); - String name = tag->BasicDirective.name.string; - if (name == "soa") { - error(node, "#soa arrays are not supported for compound literals"); - return kind; - } - } - } - } - if (cl->type->kind == Ast_DynamicArrayType && cl->type->DynamicArrayType.tag != nullptr) { - if (cl->elems.count > 0) { - Ast *tag = cl->type->DynamicArrayType.tag; + return Expr_Expr; +} + +ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Expr; + ast_node(cl, CompoundLit, node); + + Type *type = type_hint; + if (type != nullptr && is_type_untyped(type)) { + type = nullptr; + } + bool is_to_be_determined_array_count = false; + bool is_constant = true; + if (cl->type != nullptr) { + type = nullptr; + + // [?]Type + if (cl->type->kind == Ast_ArrayType && cl->type->ArrayType.count != nullptr) { + Ast *count = cl->type->ArrayType.count; + if (count->kind == Ast_UnaryExpr && + count->UnaryExpr.op.kind == Token_Question) { + type = alloc_type_array(check_type(c, cl->type->ArrayType.elem), -1); + is_to_be_determined_array_count = true; + } + if (cl->elems.count > 0) { + if (cl->type->ArrayType.tag != nullptr) { + Ast *tag = cl->type->ArrayType.tag; GB_ASSERT(tag->kind == Ast_BasicDirective); String name = tag->BasicDirective.name.string; if (name == "soa") { @@ -7274,464 +7473,268 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type } } } - - if (type == nullptr) { - type = check_type(c, cl->type); + } + if (cl->type->kind == Ast_DynamicArrayType && cl->type->DynamicArrayType.tag != nullptr) { + if (cl->elems.count > 0) { + Ast *tag = cl->type->DynamicArrayType.tag; + GB_ASSERT(tag->kind == Ast_BasicDirective); + String name = tag->BasicDirective.name.string; + if (name == "soa") { + error(node, "#soa arrays are not supported for compound literals"); + return kind; + } } } if (type == nullptr) { - error(node, "Missing type in compound literal"); - return kind; + type = check_type(c, cl->type); } + } + if (type == nullptr) { + error(node, "Missing type in compound literal"); + return kind; + } - Type *t = base_type(type); - if (is_type_polymorphic(t)) { - gbString str = type_to_string(type); - error(node, "Cannot use a polymorphic type for a compound literal, got '%s'", str); - o->expr = node; - o->type = type; - gb_string_free(str); - return kind; - } + Type *t = base_type(type); + if (is_type_polymorphic(t)) { + gbString str = type_to_string(type); + error(node, "Cannot use a polymorphic type for a compound literal, got '%s'", str); + o->expr = node; + o->type = type; + gb_string_free(str); + return kind; + } - switch (t->kind) { - case Type_Struct: { - if (cl->elems.count == 0) { - break; // NOTE(bill): No need to init - } - if (t->Struct.is_raw_union) { - if (cl->elems.count > 0) { - // NOTE: unions cannot be constant - is_constant = false; - if (cl->elems[0]->kind != Ast_FieldValue) { + switch (t->kind) { + case Type_Struct: { + if (cl->elems.count == 0) { + break; // NOTE(bill): No need to init + } + if (t->Struct.is_raw_union) { + if (cl->elems.count > 0) { + // NOTE: unions cannot be constant + is_constant = false; + + if (cl->elems[0]->kind != Ast_FieldValue) { + gbString type_str = type_to_string(type); + error(node, "%s ('struct #raw_union') compound literals are only allowed to contain 'field = value' elements", type_str); + gb_string_free(type_str); + } else { + if (cl->elems.count != 1) { gbString type_str = type_to_string(type); - error(node, "%s ('struct #raw_union') compound literals are only allowed to contain 'field = value' elements", type_str); + error(node, "%s ('struct #raw_union') compound literals are only allowed to contain up to 1 'field = value' element, got %td", type_str, cl->elems.count); gb_string_free(type_str); } else { - if (cl->elems.count != 1) { - gbString type_str = type_to_string(type); - error(node, "%s ('struct #raw_union') compound literals are only allowed to contain up to 1 'field = value' element, got %td", type_str, cl->elems.count); - gb_string_free(type_str); - } else { - Ast *elem = cl->elems[0]; - ast_node(fv, FieldValue, elem); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); - error(elem, "Invalid field name '%s' in structure literal", expr_str); - gb_string_free(expr_str); - break; - } - - String name = fv->field->Ident.token.string; - - Selection sel = lookup_field(type, name, o->mode == Addressing_Type); - bool is_unknown = sel.entity == nullptr; - if (is_unknown) { - error(elem, "Unknown field '%.*s' in structure literal", LIT(name)); - break; - } - - if (sel.index.count > 1) { - error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name)); - break; - } - - Entity *field = t->Struct.fields[sel.index[0]]; - add_entity_use(c, fv->field, field); - - Operand o = {}; - check_expr_or_type(c, &o, fv->value, field->type); - - - check_assignment(c, &o, field->type, str_lit("structure literal")); + Ast *elem = cl->elems[0]; + ast_node(fv, FieldValue, elem); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(elem, "Invalid field name '%s' in structure literal", expr_str); + gb_string_free(expr_str); + break; } - } - } - break; - } - - - isize field_count = t->Struct.fields.count; - isize min_field_count = t->Struct.fields.count; - for (isize i = min_field_count-1; i >= 0; i--) { - Entity *e = t->Struct.fields[i]; - GB_ASSERT(e->kind == Entity_Variable); - if (e->Variable.param_value.kind != ParameterValue_Invalid) { - min_field_count--; - } else { - break; - } - } - - if (cl->elems[0]->kind == Ast_FieldValue) { - bool *fields_visited = gb_alloc_array(temporary_allocator(), bool, field_count); - - for_array(i, cl->elems) { - Ast *elem = cl->elems[i]; - if (elem->kind != Ast_FieldValue) { - error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); - continue; - } - ast_node(fv, FieldValue, elem); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); - error(elem, "Invalid field name '%s' in structure literal", expr_str); - gb_string_free(expr_str); - continue; - } - String name = fv->field->Ident.token.string; - - Selection sel = lookup_field(type, name, o->mode == Addressing_Type); - bool is_unknown = sel.entity == nullptr; - if (is_unknown) { - error(elem, "Unknown field '%.*s' in structure literal", LIT(name)); - continue; - } - - if (sel.index.count > 1) { - error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name)); - continue; - } - - Entity *field = t->Struct.fields[sel.index[0]]; - add_entity_use(c, fv->field, field); - - if (fields_visited[sel.index[0]]) { - error(elem, "Duplicate field '%.*s' in structure literal", LIT(name)); - continue; - } - - fields_visited[sel.index[0]] = true; - - Operand o = {}; - check_expr_or_type(c, &o, fv->value, field->type); + String name = fv->field->Ident.token.string; - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { - is_constant = false; - } - if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); - } + Selection sel = lookup_field(type, name, o->mode == Addressing_Type); + bool is_unknown = sel.entity == nullptr; + if (is_unknown) { + error(elem, "Unknown field '%.*s' in structure literal", LIT(name)); + break; + } - check_assignment(c, &o, field->type, str_lit("structure literal")); - } - } else { - bool seen_field_value = false; + if (sel.index.count > 1) { + error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name)); + break; + } - for_array(index, cl->elems) { - Entity *field = nullptr; - Ast *elem = cl->elems[index]; - if (elem->kind == Ast_FieldValue) { - seen_field_value = true; - error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); - continue; - } else if (seen_field_value) { - error(elem, "Value elements cannot be used after a 'field = value'"); - continue; - } - if (index >= field_count) { - error(elem, "Too many values in structure literal, expected %td, got %td", field_count, cl->elems.count); - break; - } + Entity *field = t->Struct.fields[sel.index[0]]; + add_entity_use(c, fv->field, field); - if (field == nullptr) { - field = t->Struct.fields[index]; - } + Operand o = {}; + check_expr_or_type(c, &o, fv->value, field->type); - Operand o = {}; - check_expr_or_type(c, &o, elem, field->type); - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { - is_constant = false; - } - if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); + check_assignment(c, &o, field->type, str_lit("structure literal")); } - check_assignment(c, &o, field->type, str_lit("structure literal")); - } - if (cl->elems.count < field_count) { - if (min_field_count < field_count) { - if (cl->elems.count < min_field_count) { - error(cl->close, "Too few values in structure literal, expected at least %td, got %td", min_field_count, cl->elems.count); - } - } else { - error(cl->close, "Too few values in structure literal, expected %td, got %td", field_count, cl->elems.count); - } } } - break; } - case Type_Slice: - case Type_Array: - case Type_DynamicArray: - case Type_SimdVector: - case Type_Matrix: - { - Type *elem_type = nullptr; - String context_name = {}; - i64 max_type_count = -1; - if (t->kind == Type_Slice) { - elem_type = t->Slice.elem; - context_name = str_lit("slice literal"); - } else if (t->kind == Type_Array) { - elem_type = t->Array.elem; - context_name = str_lit("array literal"); - if (!is_to_be_determined_array_count) { - max_type_count = t->Array.count; - } - } else if (t->kind == Type_DynamicArray) { - elem_type = t->DynamicArray.elem; - context_name = str_lit("dynamic array literal"); - is_constant = false; - if (!build_context.no_dynamic_literals) { - add_package_dependency(c, "runtime", "__dynamic_array_reserve"); - add_package_dependency(c, "runtime", "__dynamic_array_append"); - } - } else if (t->kind == Type_SimdVector) { - elem_type = t->SimdVector.elem; - context_name = str_lit("simd vector literal"); - max_type_count = t->SimdVector.count; - } else if (t->kind == Type_Matrix) { - elem_type = t->Matrix.elem; - context_name = str_lit("matrix literal"); - max_type_count = t->Matrix.row_count*t->Matrix.column_count; + isize field_count = t->Struct.fields.count; + isize min_field_count = t->Struct.fields.count; + for (isize i = min_field_count-1; i >= 0; i--) { + Entity *e = t->Struct.fields[i]; + GB_ASSERT(e->kind == Entity_Variable); + if (e->Variable.param_value.kind != ParameterValue_Invalid) { + min_field_count--; } else { - GB_PANIC("unreachable"); - } - - - i64 max = 0; - - Type *bet = base_type(elem_type); - if (!elem_type_can_be_constant(bet)) { - is_constant = false; - } - - if (bet == t_invalid) { break; } + } - if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { - if (is_type_simd_vector(t)) { - error(cl->elems[0], "'field = value' is not allowed for SIMD vector literals"); - } else { - RangeCache rc = range_cache_make(heap_allocator()); - defer (range_cache_destroy(&rc)); - - for_array(i, cl->elems) { - Ast *elem = cl->elems[i]; - if (elem->kind != Ast_FieldValue) { - error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); - continue; - } - ast_node(fv, FieldValue, elem); - - if (is_ast_range(fv->field)) { - Token op = fv->field->BinaryExpr.op; - - Operand x = {}; - Operand y = {}; - bool ok = check_range(c, fv->field, &x, &y, nullptr); - if (!ok) { - continue; - } - if (x.mode != Addressing_Constant || !is_type_integer(core_type(x.type))) { - error(x.expr, "Expected a constant integer as an array field"); - continue; - } - - if (y.mode != Addressing_Constant || !is_type_integer(core_type(y.type))) { - error(y.expr, "Expected a constant integer as an array field"); - continue; - } - - i64 lo = exact_value_to_i64(x.value); - i64 hi = exact_value_to_i64(y.value); - i64 max_index = hi; - if (op.kind == Token_RangeHalf) { // ..< (exclusive) - hi -= 1; - } else { // .. (inclusive) - max_index += 1; - } - - bool new_range = range_cache_add_range(&rc, lo, hi); - if (!new_range) { - error(elem, "Overlapping field range index %lld %.*s %lld for %.*s", lo, LIT(op.string), hi, LIT(context_name)); - continue; - } - - - if (max_type_count >= 0 && (lo < 0 || lo >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", lo, max_type_count, LIT(context_name)); - continue; - } - if (max_type_count >= 0 && (hi < 0 || hi >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", hi, max_type_count, LIT(context_name)); - continue; - } - - if (max < hi) { - max = max_index; - } - - Operand operand = {}; - check_expr_with_type_hint(c, &operand, fv->value, elem_type); - check_assignment(c, &operand, elem_type, context_name); - - is_constant = is_constant && operand.mode == Addressing_Constant; - } else { - Operand op_index = {}; - check_expr(c, &op_index, fv->field); - - if (op_index.mode != Addressing_Constant || !is_type_integer(core_type(op_index.type))) { - error(elem, "Expected a constant integer as an array field"); - continue; - } - // add_type_and_value(c->info, op_index.expr, op_index.mode, op_index.type, op_index.value); - - i64 index = exact_value_to_i64(op_index.value); - - if (max_type_count >= 0 && (index < 0 || index >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", index, max_type_count, LIT(context_name)); - continue; - } - - bool new_index = range_cache_add_index(&rc, index); - if (!new_index) { - error(elem, "Duplicate field index %lld for %.*s", index, LIT(context_name)); - continue; - } - - if (max < index+1) { - max = index+1; - } + if (cl->elems[0]->kind == Ast_FieldValue) { + bool *fields_visited = gb_alloc_array(temporary_allocator(), bool, field_count); - Operand operand = {}; - check_expr_with_type_hint(c, &operand, fv->value, elem_type); - check_assignment(c, &operand, elem_type, context_name); + for_array(i, cl->elems) { + Ast *elem = cl->elems[i]; + if (elem->kind != Ast_FieldValue) { + error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; + } + ast_node(fv, FieldValue, elem); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(elem, "Invalid field name '%s' in structure literal", expr_str); + gb_string_free(expr_str); + continue; + } + String name = fv->field->Ident.token.string; - is_constant = is_constant && operand.mode == Addressing_Constant; - } - } + Selection sel = lookup_field(type, name, o->mode == Addressing_Type); + bool is_unknown = sel.entity == nullptr; + if (is_unknown) { + error(elem, "Unknown field '%.*s' in structure literal", LIT(name)); + continue; + } - cl->max_count = max; + if (sel.index.count > 1) { + error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name)); + continue; } - } else { - isize index = 0; - for (; index < cl->elems.count; index++) { - Ast *e = cl->elems[index]; - if (e == nullptr) { - error(node, "Invalid literal element"); - continue; - } + Entity *field = t->Struct.fields[sel.index[0]]; + add_entity_use(c, fv->field, field); - if (e->kind == Ast_FieldValue) { - error(e, "Mixture of 'field = value' and value elements in a literal is not allowed"); - continue; - } + if (fields_visited[sel.index[0]]) { + error(elem, "Duplicate field '%.*s' in structure literal", LIT(name)); + continue; + } - if (0 <= max_type_count && max_type_count <= index) { - error(e, "Index %lld is out of bounds (>= %lld) for %.*s", index, max_type_count, LIT(context_name)); - } + fields_visited[sel.index[0]] = true; - Operand operand = {}; - check_expr_with_type_hint(c, &operand, e, elem_type); - check_assignment(c, &operand, elem_type, context_name); + Operand o = {}; + check_expr_or_type(c, &o, fv->value, field->type); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + is_constant = false; } - - if (max < index) { - max = index; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &o); } - } + check_assignment(c, &o, field->type, str_lit("structure literal")); + } + } else { + bool seen_field_value = false; + + for_array(index, cl->elems) { + Entity *field = nullptr; + Ast *elem = cl->elems[index]; + if (elem->kind == Ast_FieldValue) { + seen_field_value = true; + error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; + } else if (seen_field_value) { + error(elem, "Value elements cannot be used after a 'field = value'"); + continue; + } + if (index >= field_count) { + error(elem, "Too many values in structure literal, expected %td, got %td", field_count, cl->elems.count); + break; + } - if (t->kind == Type_Array) { - if (is_to_be_determined_array_count) { - t->Array.count = max; - } else if (cl->elems.count > 0 && cl->elems[0]->kind != Ast_FieldValue) { - if (0 < max && max < t->Array.count) { - error(node, "Expected %lld values for this array literal, got %lld", cast(long long)t->Array.count, cast(long long)max); - } + if (field == nullptr) { + field = t->Struct.fields[index]; } - } + Operand o = {}; + check_expr_or_type(c, &o, elem, field->type); - if (t->kind == Type_SimdVector) { - if (!is_constant) { - error(node, "Expected all constant elements for a simd vector"); + if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + is_constant = false; + } + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &o); } - } - - if (t->kind == Type_DynamicArray) { - if (build_context.no_dynamic_literals && cl->elems.count) { - error(node, "Compound literals of dynamic types have been disabled"); + check_assignment(c, &o, field->type, str_lit("structure literal")); + } + if (cl->elems.count < field_count) { + if (min_field_count < field_count) { + if (cl->elems.count < min_field_count) { + error(cl->close, "Too few values in structure literal, expected at least %td, got %td", min_field_count, cl->elems.count); + } + } else { + error(cl->close, "Too few values in structure literal, expected %td, got %td", field_count, cl->elems.count); } } - - break; } - case Type_EnumeratedArray: - { - Type *elem_type = t->EnumeratedArray.elem; - Type *index_type = t->EnumeratedArray.index; - String context_name = str_lit("enumerated array literal"); - i64 max_type_count = t->EnumeratedArray.count; + break; + } - gbString index_type_str = type_to_string(index_type); - defer (gb_string_free(index_type_str)); + case Type_Slice: + case Type_Array: + case Type_DynamicArray: + case Type_SimdVector: + case Type_Matrix: + { + Type *elem_type = nullptr; + String context_name = {}; + i64 max_type_count = -1; + if (t->kind == Type_Slice) { + elem_type = t->Slice.elem; + context_name = str_lit("slice literal"); + } else if (t->kind == Type_Array) { + elem_type = t->Array.elem; + context_name = str_lit("array literal"); + if (!is_to_be_determined_array_count) { + max_type_count = t->Array.count; + } + } else if (t->kind == Type_DynamicArray) { + elem_type = t->DynamicArray.elem; + context_name = str_lit("dynamic array literal"); + is_constant = false; - i64 total_lo = exact_value_to_i64(*t->EnumeratedArray.min_value); - i64 total_hi = exact_value_to_i64(*t->EnumeratedArray.max_value); + if (!build_context.no_dynamic_literals) { + add_package_dependency(c, "runtime", "__dynamic_array_reserve"); + add_package_dependency(c, "runtime", "__dynamic_array_append"); + } + } else if (t->kind == Type_SimdVector) { + elem_type = t->SimdVector.elem; + context_name = str_lit("simd vector literal"); + max_type_count = t->SimdVector.count; + } else if (t->kind == Type_Matrix) { + elem_type = t->Matrix.elem; + context_name = str_lit("matrix literal"); + max_type_count = t->Matrix.row_count*t->Matrix.column_count; + } else { + GB_PANIC("unreachable"); + } - String total_lo_string = {}; - String total_hi_string = {}; - GB_ASSERT(is_type_enum(index_type)); - { - Type *bt = base_type(index_type); - GB_ASSERT(bt->kind == Type_Enum); - for_array(i, bt->Enum.fields) { - Entity *f = bt->Enum.fields[i]; - if (f->kind != Entity_Constant) { - continue; - } - if (total_lo_string.len == 0 && compare_exact_values(Token_CmpEq, f->Constant.value, *t->EnumeratedArray.min_value)) { - total_lo_string = f->token.string; - } - if (total_hi_string.len == 0 && compare_exact_values(Token_CmpEq, f->Constant.value, *t->EnumeratedArray.max_value)) { - total_hi_string = f->token.string; - } - if (total_lo_string.len != 0 && total_hi_string.len != 0) { - break; - } - } - } - i64 max = 0; + i64 max = 0; - Type *bet = base_type(elem_type); - if (!elem_type_can_be_constant(bet)) { - is_constant = false; - } + Type *bet = base_type(elem_type); + if (!elem_type_can_be_constant(bet)) { + is_constant = false; + } - if (bet == t_invalid) { - break; - } + if (bet == t_invalid) { + break; + } - if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { + if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { + if (is_type_simd_vector(t)) { + error(cl->elems[0], "'field = value' is not allowed for SIMD vector literals"); + } else { RangeCache rc = range_cache_make(heap_allocator()); defer (range_cache_destroy(&rc)); @@ -7748,49 +7751,42 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type Operand x = {}; Operand y = {}; - bool ok = check_range(c, fv->field, &x, &y, nullptr, index_type); + bool ok = check_range(c, fv->field, &x, &y, nullptr); if (!ok) { continue; } - if (x.mode != Addressing_Constant || !are_types_identical(x.type, index_type)) { - error(x.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + if (x.mode != Addressing_Constant || !is_type_integer(core_type(x.type))) { + error(x.expr, "Expected a constant integer as an array field"); continue; } - if (y.mode != Addressing_Constant || !are_types_identical(x.type, index_type)) { - error(y.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + if (y.mode != Addressing_Constant || !is_type_integer(core_type(y.type))) { + error(y.expr, "Expected a constant integer as an array field"); continue; } i64 lo = exact_value_to_i64(x.value); i64 hi = exact_value_to_i64(y.value); i64 max_index = hi; - if (op.kind == Token_RangeHalf) { + if (op.kind == Token_RangeHalf) { // ..< (exclusive) hi -= 1; + } else { // .. (inclusive) + max_index += 1; } bool new_range = range_cache_add_range(&rc, lo, hi); if (!new_range) { - gbString lo_str = expr_to_string(x.expr); - gbString hi_str = expr_to_string(y.expr); - error(elem, "Overlapping field range index %s %.*s %s for %.*s", lo_str, LIT(op.string), hi_str, LIT(context_name)); - gb_string_free(hi_str); - gb_string_free(lo_str); + error(elem, "Overlapping field range index %lld %.*s %lld for %.*s", lo, LIT(op.string), hi, LIT(context_name)); continue; } - // NOTE(bill): These are sanity checks for invalid enum values - if (max_type_count >= 0 && (lo < total_lo || lo > total_hi)) { - gbString lo_str = expr_to_string(x.expr); - error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", lo_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); - gb_string_free(lo_str); + if (max_type_count >= 0 && (lo < 0 || lo >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", lo, max_type_count, LIT(context_name)); continue; } - if (max_type_count >= 0 && (hi < 0 || hi > total_hi)) { - gbString hi_str = expr_to_string(y.expr); - error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", hi_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); - gb_string_free(hi_str); + if (max_type_count >= 0 && (hi < 0 || hi >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", hi, max_type_count, LIT(context_name)); continue; } @@ -7805,27 +7801,24 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type is_constant = is_constant && operand.mode == Addressing_Constant; } else { Operand op_index = {}; - check_expr_with_type_hint(c, &op_index, fv->field, index_type); + check_expr(c, &op_index, fv->field); - if (op_index.mode != Addressing_Constant || !are_types_identical(op_index.type, index_type)) { - error(op_index.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + if (op_index.mode != Addressing_Constant || !is_type_integer(core_type(op_index.type))) { + error(elem, "Expected a constant integer as an array field"); continue; } + // add_type_and_value(c->info, op_index.expr, op_index.mode, op_index.type, op_index.value); i64 index = exact_value_to_i64(op_index.value); - if (max_type_count >= 0 && (index < total_lo || index > total_hi)) { - gbString idx_str = expr_to_string(op_index.expr); - error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", idx_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); - gb_string_free(idx_str); + if (max_type_count >= 0 && (index < 0 || index >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", index, max_type_count, LIT(context_name)); continue; } bool new_index = range_cache_add_index(&rc, index); if (!new_index) { - gbString idx_str = expr_to_string(op_index.expr); - error(elem, "Duplicate field index %s for %.*s", idx_str, LIT(context_name)); - gb_string_free(idx_str); + error(elem, "Duplicate field index %lld for %.*s", index, LIT(context_name)); continue; } @@ -7842,951 +7835,1433 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type } cl->max_count = max; + } - } else { - isize index = 0; - for (; index < cl->elems.count; index++) { - Ast *e = cl->elems[index]; - if (e == nullptr) { - error(node, "Invalid literal element"); - continue; - } + } else { + isize index = 0; + for (; index < cl->elems.count; index++) { + Ast *e = cl->elems[index]; + if (e == nullptr) { + error(node, "Invalid literal element"); + continue; + } - if (e->kind == Ast_FieldValue) { - error(e, "Mixture of 'field = value' and value elements in a literal is not allowed"); - continue; - } + if (e->kind == Ast_FieldValue) { + error(e, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; + } - if (0 <= max_type_count && max_type_count <= index) { - error(e, "Index %lld is out of bounds (>= %lld) for %.*s", index, max_type_count, LIT(context_name)); - } + if (0 <= max_type_count && max_type_count <= index) { + error(e, "Index %lld is out of bounds (>= %lld) for %.*s", index, max_type_count, LIT(context_name)); + } - Operand operand = {}; - check_expr_with_type_hint(c, &operand, e, elem_type); - check_assignment(c, &operand, elem_type, context_name); + Operand operand = {}; + check_expr_with_type_hint(c, &operand, e, elem_type); + check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; - } + is_constant = is_constant && operand.mode == Addressing_Constant; + } + + if (max < index) { + max = index; + } + } - if (max < index) { - max = index; + + if (t->kind == Type_Array) { + if (is_to_be_determined_array_count) { + t->Array.count = max; + } else if (cl->elems.count > 0 && cl->elems[0]->kind != Ast_FieldValue) { + if (0 < max && max < t->Array.count) { + error(node, "Expected %lld values for this array literal, got %lld", cast(long long)t->Array.count, cast(long long)max); } } + } + + if (t->kind == Type_SimdVector) { + if (!is_constant) { + error(node, "Expected all constant elements for a simd vector"); + } + } + + + if (t->kind == Type_DynamicArray) { + if (build_context.no_dynamic_literals && cl->elems.count) { + error(node, "Compound literals of dynamic types have been disabled"); + } + } + + if (t->kind == Type_Matrix) { if (cl->elems.count > 0 && cl->elems[0]->kind != Ast_FieldValue) { - if (0 < max && max < t->EnumeratedArray.count) { - error(node, "Expected %lld values for this enumerated array literal, got %lld", cast(long long)t->EnumeratedArray.count, cast(long long)max); - } else { - error(node, "Enumerated array literals must only have 'field = value' elements, bare elements are not allowed"); + if (0 < max && max < max_type_count) { + error(node, "Expected %lld values for this matrix literal, got %lld", cast(long long)max_type_count, cast(long long)max); + } + } + } + + break; + } + + case Type_EnumeratedArray: + { + Type *elem_type = t->EnumeratedArray.elem; + Type *index_type = t->EnumeratedArray.index; + String context_name = str_lit("enumerated array literal"); + i64 max_type_count = t->EnumeratedArray.count; + + gbString index_type_str = type_to_string(index_type); + defer (gb_string_free(index_type_str)); + + i64 total_lo = exact_value_to_i64(*t->EnumeratedArray.min_value); + i64 total_hi = exact_value_to_i64(*t->EnumeratedArray.max_value); + + String total_lo_string = {}; + String total_hi_string = {}; + GB_ASSERT(is_type_enum(index_type)); + { + Type *bt = base_type(index_type); + GB_ASSERT(bt->kind == Type_Enum); + for_array(i, bt->Enum.fields) { + Entity *f = bt->Enum.fields[i]; + if (f->kind != Entity_Constant) { + continue; + } + if (total_lo_string.len == 0 && compare_exact_values(Token_CmpEq, f->Constant.value, *t->EnumeratedArray.min_value)) { + total_lo_string = f->token.string; + } + if (total_hi_string.len == 0 && compare_exact_values(Token_CmpEq, f->Constant.value, *t->EnumeratedArray.max_value)) { + total_hi_string = f->token.string; + } + if (total_lo_string.len != 0 && total_hi_string.len != 0) { + break; } } + } + + i64 max = 0; + Type *bet = base_type(elem_type); + if (!elem_type_can_be_constant(bet)) { + is_constant = false; + } + + if (bet == t_invalid) { break; } + bool is_partial = cl->tag && (cl->tag->BasicDirective.name.string == "partial"); + + SeenMap seen = {}; // NOTE(bill): Multimap, Key: ExactValue + map_init(&seen, heap_allocator()); + defer (map_destroy(&seen)); + + if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { + RangeCache rc = range_cache_make(heap_allocator()); + defer (range_cache_destroy(&rc)); - case Type_Basic: { - if (!is_type_any(t)) { - if (cl->elems.count != 0) { - error(node, "Illegal compound literal"); + for_array(i, cl->elems) { + Ast *elem = cl->elems[i]; + if (elem->kind != Ast_FieldValue) { + error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; } - break; - } - if (cl->elems.count == 0) { - break; // NOTE(bill): No need to init - } - { // Checker values - Type *field_types[2] = {t_rawptr, t_typeid}; - isize field_count = 2; - if (cl->elems[0]->kind == Ast_FieldValue) { - bool fields_visited[2] = {}; - - for_array(i, cl->elems) { - Ast *elem = cl->elems[i]; - if (elem->kind != Ast_FieldValue) { - error(elem, "Mixture of 'field = value' and value elements in a 'any' literal is not allowed"); - continue; - } - ast_node(fv, FieldValue, elem); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); - error(elem, "Invalid field name '%s' in 'any' literal", expr_str); - gb_string_free(expr_str); - continue; - } - String name = fv->field->Ident.token.string; + ast_node(fv, FieldValue, elem); - Selection sel = lookup_field(type, name, o->mode == Addressing_Type); - if (sel.entity == nullptr) { - error(elem, "Unknown field '%.*s' in 'any' literal", LIT(name)); - continue; - } + if (is_ast_range(fv->field)) { + Token op = fv->field->BinaryExpr.op; - isize index = sel.index[0]; + Operand x = {}; + Operand y = {}; + bool ok = check_range(c, fv->field, &x, &y, nullptr, index_type); + if (!ok) { + continue; + } + if (x.mode != Addressing_Constant || !are_types_identical(x.type, index_type)) { + error(x.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + continue; + } - if (fields_visited[index]) { - error(elem, "Duplicate field '%.*s' in 'any' literal", LIT(name)); - continue; - } + if (y.mode != Addressing_Constant || !are_types_identical(x.type, index_type)) { + error(y.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + continue; + } - fields_visited[index] = true; - check_expr(c, o, fv->value); + i64 lo = exact_value_to_i64(x.value); + i64 hi = exact_value_to_i64(y.value); + i64 max_index = hi; + if (op.kind == Token_RangeHalf) { + hi -= 1; + } + + bool new_range = range_cache_add_range(&rc, lo, hi); + if (!new_range) { + gbString lo_str = expr_to_string(x.expr); + gbString hi_str = expr_to_string(y.expr); + error(elem, "Overlapping field range index %s %.*s %s for %.*s", lo_str, LIT(op.string), hi_str, LIT(context_name)); + gb_string_free(hi_str); + gb_string_free(lo_str); + continue; + } - // NOTE(bill): 'any' literals can never be constant - is_constant = false; - check_assignment(c, o, field_types[index], str_lit("'any' literal")); + // NOTE(bill): These are sanity checks for invalid enum values + if (max_type_count >= 0 && (lo < total_lo || lo > total_hi)) { + gbString lo_str = expr_to_string(x.expr); + error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", lo_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); + gb_string_free(lo_str); + continue; } + if (max_type_count >= 0 && (hi < 0 || hi > total_hi)) { + gbString hi_str = expr_to_string(y.expr); + error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", hi_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); + gb_string_free(hi_str); + continue; + } + + if (max < hi) { + max = max_index; + } + + Operand operand = {}; + check_expr_with_type_hint(c, &operand, fv->value, elem_type); + check_assignment(c, &operand, elem_type, context_name); + + is_constant = is_constant && operand.mode == Addressing_Constant; + + TokenKind upper_op = Token_LtEq; + if (op.kind == Token_RangeHalf) { + upper_op = Token_Lt; + } + add_to_seen_map(c, &seen, upper_op, x, x, y); } else { - for_array(index, cl->elems) { - Ast *elem = cl->elems[index]; - if (elem->kind == Ast_FieldValue) { - error(elem, "Mixture of 'field = value' and value elements in a 'any' literal is not allowed"); - continue; - } + Operand op_index = {}; + check_expr_with_type_hint(c, &op_index, fv->field, index_type); + if (op_index.mode != Addressing_Constant || !are_types_identical(op_index.type, index_type)) { + error(op_index.expr, "Expected a constant enum of type '%s' as an array field", index_type_str); + continue; + } - check_expr(c, o, elem); - if (index >= field_count) { - error(o->expr, "Too many values in 'any' literal, expected %td", field_count); - break; - } + i64 index = exact_value_to_i64(op_index.value); - // NOTE(bill): 'any' literals can never be constant - is_constant = false; + if (max_type_count >= 0 && (index < total_lo || index > total_hi)) { + gbString idx_str = expr_to_string(op_index.expr); + error(elem, "Index %s is out of bounds (%.*s .. %.*s) for %.*s", idx_str, LIT(total_lo_string), LIT(total_hi_string), LIT(context_name)); + gb_string_free(idx_str); + continue; + } - check_assignment(c, o, field_types[index], str_lit("'any' literal")); + bool new_index = range_cache_add_index(&rc, index); + if (!new_index) { + gbString idx_str = expr_to_string(op_index.expr); + error(elem, "Duplicate field index %s for %.*s", idx_str, LIT(context_name)); + gb_string_free(idx_str); + continue; } - if (cl->elems.count < field_count) { - error(cl->close, "Too few values in 'any' literal, expected %td, got %td", field_count, cl->elems.count); + + if (max < index+1) { + max = index+1; } + + Operand operand = {}; + check_expr_with_type_hint(c, &operand, fv->value, elem_type); + check_assignment(c, &operand, elem_type, context_name); + + is_constant = is_constant && operand.mode == Addressing_Constant; + + add_to_seen_map(c, &seen, op_index); } } - break; + cl->max_count = max; + + } else { + isize index = 0; + for (; index < cl->elems.count; index++) { + Ast *e = cl->elems[index]; + if (e == nullptr) { + error(node, "Invalid literal element"); + continue; + } + + if (e->kind == Ast_FieldValue) { + error(e, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; + } + + if (0 <= max_type_count && max_type_count <= index) { + error(e, "Index %lld is out of bounds (>= %lld) for %.*s", index, max_type_count, LIT(context_name)); + } + + Operand operand = {}; + check_expr_with_type_hint(c, &operand, e, elem_type); + check_assignment(c, &operand, elem_type, context_name); + + is_constant = is_constant && operand.mode == Addressing_Constant; + } + + if (max < index) { + max = index; + } } - case Type_Map: { - if (cl->elems.count == 0) { - break; + bool was_error = false; + if (cl->elems.count > 0 && cl->elems[0]->kind != Ast_FieldValue) { + if (0 < max && max < t->EnumeratedArray.count) { + error(node, "Expected %lld values for this enumerated array literal, got %lld", cast(long long)t->EnumeratedArray.count, cast(long long)max); + was_error = true; + } else { + error(node, "Enumerated array literals must only have 'field = value' elements, bare elements are not allowed"); + was_error = true; + } + } + + // NOTE(bill): Check for missing cases when `#partial literal` is not present + if (cl->elems.count > 0 && !was_error && !is_partial) { + Type *et = base_type(index_type); + GB_ASSERT(et->kind == Type_Enum); + auto fields = et->Enum.fields; + + auto unhandled = array_make<Entity *>(temporary_allocator(), 0, fields.count); + + for_array(i, fields) { + Entity *f = fields[i]; + if (f->kind != Entity_Constant) { + continue; + } + ExactValue v = f->Constant.value; + auto found = map_get(&seen, hash_exact_value(v)); + if (!found) { + array_add(&unhandled, f); + } } - is_constant = false; - { // Checker values - bool key_is_typeid = is_type_typeid(t->Map.key); - bool value_is_typeid = is_type_typeid(t->Map.value); + + if (unhandled.count > 0) { + begin_error_block(); + defer (end_error_block()); + + if (unhandled.count == 1) { + error_no_newline(node, "Unhandled enumerated array case: %.*s", LIT(unhandled[0]->token.string)); + } else { + error(node, "Unhandled enumerated array cases:"); + for_array(i, unhandled) { + Entity *f = unhandled[i]; + error_line("\t%.*s\n", LIT(f->token.string)); + } + } + error_line("\n"); + + error_line("\tSuggestion: Was '#partial %s{...}' wanted?\n", type_to_string(type)); + } + } + + break; + } + + case Type_Basic: { + if (!is_type_any(t)) { + if (cl->elems.count != 0) { + gbString s = type_to_string(t); + error(node, "Illegal compound literal, %s cannot be used as a compound literal with fields", s); + gb_string_free(s); + is_constant = false; + } + break; + } + if (cl->elems.count == 0) { + break; // NOTE(bill): No need to init + } + { // Checker values + Type *field_types[2] = {t_rawptr, t_typeid}; + isize field_count = 2; + if (cl->elems[0]->kind == Ast_FieldValue) { + bool fields_visited[2] = {}; for_array(i, cl->elems) { Ast *elem = cl->elems[i]; if (elem->kind != Ast_FieldValue) { - error(elem, "Only 'field = value' elements are allowed in a map literal"); + error(elem, "Mixture of 'field = value' and value elements in a 'any' literal is not allowed"); continue; } ast_node(fv, FieldValue, elem); - - if (key_is_typeid) { - check_expr_or_type(c, o, fv->field, t->Map.key); - } else { - check_expr_with_type_hint(c, o, fv->field, t->Map.key); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(elem, "Invalid field name '%s' in 'any' literal", expr_str); + gb_string_free(expr_str); + continue; } - check_assignment(c, o, t->Map.key, str_lit("map literal")); - if (o->mode == Addressing_Invalid) { + String name = fv->field->Ident.token.string; + + Selection sel = lookup_field(type, name, o->mode == Addressing_Type); + if (sel.entity == nullptr) { + error(elem, "Unknown field '%.*s' in 'any' literal", LIT(name)); continue; } - if (value_is_typeid) { - check_expr_or_type(c, o, fv->value, t->Map.value); - } else { - check_expr_with_type_hint(c, o, fv->value, t->Map.value); + isize index = sel.index[0]; + + if (fields_visited[index]) { + error(elem, "Duplicate field '%.*s' in 'any' literal", LIT(name)); + continue; } - check_assignment(c, o, t->Map.value, str_lit("map literal")); - } - } - if (build_context.no_dynamic_literals && cl->elems.count) { - error(node, "Compound literals of dynamic types have been disabled"); - } else { - add_package_dependency(c, "runtime", "__dynamic_map_reserve"); - add_package_dependency(c, "runtime", "__dynamic_map_set"); - } - break; - } + fields_visited[index] = true; + check_expr(c, o, fv->value); - case Type_BitSet: { - if (cl->elems.count == 0) { - break; // NOTE(bill): No need to init - } - Type *et = base_type(t->BitSet.elem); - isize field_count = 0; - if (et->kind == Type_Enum) { - field_count = et->Enum.fields.count; - } + // NOTE(bill): 'any' literals can never be constant + is_constant = false; - if (cl->elems[0]->kind == Ast_FieldValue) { - error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); - is_constant = false; + check_assignment(c, o, field_types[index], str_lit("'any' literal")); + } } else { for_array(index, cl->elems) { Ast *elem = cl->elems[index]; if (elem->kind == Ast_FieldValue) { - error(elem, "'field = value' in a bit_set a literal is not allowed"); + error(elem, "Mixture of 'field = value' and value elements in a 'any' literal is not allowed"); continue; } - check_expr_with_type_hint(c, o, elem, et); - if (is_constant) { - is_constant = o->mode == Addressing_Constant; + check_expr(c, o, elem); + if (index >= field_count) { + error(o->expr, "Too many values in 'any' literal, expected %td", field_count); + break; } - check_assignment(c, o, t->BitSet.elem, str_lit("bit_set literal")); - if (o->mode == Addressing_Constant) { - i64 lower = t->BitSet.lower; - i64 upper = t->BitSet.upper; - i64 v = exact_value_to_i64(o->value); - if (lower <= v && v <= upper) { - // okay - } else { - error(elem, "Bit field value out of bounds, %lld not in the range %lld .. %lld", v, lower, upper); - continue; - } - } + // NOTE(bill): 'any' literals can never be constant + is_constant = false; + + check_assignment(c, o, field_types[index], str_lit("'any' literal")); + } + if (cl->elems.count < field_count) { + error(cl->close, "Too few values in 'any' literal, expected %td, got %td", field_count, cl->elems.count); } } + } + + break; + } + + case Type_Map: { + if (cl->elems.count == 0) { break; } + is_constant = false; + { // Checker values + bool key_is_typeid = is_type_typeid(t->Map.key); + bool value_is_typeid = is_type_typeid(t->Map.value); - default: { - if (cl->elems.count == 0) { - break; // NOTE(bill): No need to init + for_array(i, cl->elems) { + Ast *elem = cl->elems[i]; + if (elem->kind != Ast_FieldValue) { + error(elem, "Only 'field = value' elements are allowed in a map literal"); + continue; + } + ast_node(fv, FieldValue, elem); + + if (key_is_typeid) { + check_expr_or_type(c, o, fv->field, t->Map.key); + } else { + check_expr_with_type_hint(c, o, fv->field, t->Map.key); + } + check_assignment(c, o, t->Map.key, str_lit("map literal")); + if (o->mode == Addressing_Invalid) { + continue; + } + + if (value_is_typeid) { + check_expr_or_type(c, o, fv->value, t->Map.value); + } else { + check_expr_with_type_hint(c, o, fv->value, t->Map.value); + } + check_assignment(c, o, t->Map.value, str_lit("map literal")); } + } - gbString str = type_to_string(type); - error(node, "Invalid compound literal type '%s'", str); - gb_string_free(str); - return kind; + if (build_context.no_dynamic_literals && cl->elems.count) { + error(node, "Compound literals of dynamic types have been disabled"); + } else { + add_package_dependency(c, "runtime", "__dynamic_map_reserve"); + add_package_dependency(c, "runtime", "__dynamic_map_set"); } + break; + } + + case Type_BitSet: { + if (cl->elems.count == 0) { + break; // NOTE(bill): No need to init + } + Type *et = base_type(t->BitSet.elem); + isize field_count = 0; + if (et->kind == Type_Enum) { + field_count = et->Enum.fields.count; } - if (is_constant) { - o->mode = Addressing_Constant; + if (cl->elems[0]->kind == Ast_FieldValue) { + error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); + is_constant = false; + } else { + for_array(index, cl->elems) { + Ast *elem = cl->elems[index]; + if (elem->kind == Ast_FieldValue) { + error(elem, "'field = value' in a bit_set a literal is not allowed"); + continue; + } - if (is_type_bit_set(type)) { - // NOTE(bill): Encode as an integer + check_expr_with_type_hint(c, o, elem, et); - i64 lower = base_type(type)->BitSet.lower; + if (is_constant) { + is_constant = o->mode == Addressing_Constant; + } - u64 bits = 0; - for_array(index, cl->elems) { - Ast *elem = cl->elems[index]; - GB_ASSERT(elem->kind != Ast_FieldValue); - TypeAndValue tav = elem->tav; - ExactValue i = exact_value_to_integer(tav.value); - if (i.kind != ExactValue_Integer) { + check_assignment(c, o, t->BitSet.elem, str_lit("bit_set literal")); + if (o->mode == Addressing_Constant) { + i64 lower = t->BitSet.lower; + i64 upper = t->BitSet.upper; + i64 v = exact_value_to_i64(o->value); + if (lower <= v && v <= upper) { + // okay + } else { + error(elem, "Bit field value out of bounds, %lld not in the range %lld .. %lld", v, lower, upper); continue; } - i64 val = big_int_to_i64(&i.value_integer); - val -= lower; - u64 bit = u64(1ll<<val); - bits |= bit; - } - o->value = exact_value_u64(bits); - } else if (is_type_constant_type(type) && cl->elems.count == 0) { - ExactValue value = exact_value_compound(node); - Type *bt = core_type(type); - if (bt->kind == Type_Basic) { - if (bt->Basic.flags & BasicFlag_Boolean) { - value = exact_value_bool(false); - } else if (bt->Basic.flags & BasicFlag_Integer) { - value = exact_value_i64(0); - } else if (bt->Basic.flags & BasicFlag_Unsigned) { - value = exact_value_i64(0); - } else if (bt->Basic.flags & BasicFlag_Float) { - value = exact_value_float(0); - } else if (bt->Basic.flags & BasicFlag_Complex) { - value = exact_value_complex(0, 0); - } else if (bt->Basic.flags & BasicFlag_Quaternion) { - value = exact_value_quaternion(0, 0, 0, 0); - } else if (bt->Basic.flags & BasicFlag_Pointer) { - value = exact_value_pointer(0); - } else if (bt->Basic.flags & BasicFlag_String) { - String empty_string = {}; - value = exact_value_string(empty_string); - } else if (bt->Basic.flags & BasicFlag_Rune) { - value = exact_value_i64(0); - } } - - o->value = value; - } else { - o->value = exact_value_compound(node); } + } + break; + } + + default: { + if (cl->elems.count == 0) { + break; // NOTE(bill): No need to init + } + + gbString str = type_to_string(type); + error(node, "Invalid compound literal type '%s'", str); + gb_string_free(str); + return kind; + } + } + + if (is_constant) { + o->mode = Addressing_Constant; + + if (is_type_bit_set(type)) { + // NOTE(bill): Encode as an integer + + i64 lower = base_type(type)->BitSet.lower; + + u64 bits = 0; + for_array(index, cl->elems) { + Ast *elem = cl->elems[index]; + GB_ASSERT(elem->kind != Ast_FieldValue); + TypeAndValue tav = elem->tav; + ExactValue i = exact_value_to_integer(tav.value); + if (i.kind != ExactValue_Integer) { + continue; + } + i64 val = big_int_to_i64(&i.value_integer); + val -= lower; + u64 bit = u64(1ll<<val); + bits |= bit; + } + o->value = exact_value_u64(bits); + } else if (is_type_constant_type(type) && cl->elems.count == 0) { + ExactValue value = exact_value_compound(node); + Type *bt = core_type(type); + if (bt->kind == Type_Basic) { + if (bt->Basic.flags & BasicFlag_Boolean) { + value = exact_value_bool(false); + } else if (bt->Basic.flags & BasicFlag_Integer) { + value = exact_value_i64(0); + } else if (bt->Basic.flags & BasicFlag_Unsigned) { + value = exact_value_i64(0); + } else if (bt->Basic.flags & BasicFlag_Float) { + value = exact_value_float(0); + } else if (bt->Basic.flags & BasicFlag_Complex) { + value = exact_value_complex(0, 0); + } else if (bt->Basic.flags & BasicFlag_Quaternion) { + value = exact_value_quaternion(0, 0, 0, 0); + } else if (bt->Basic.flags & BasicFlag_Pointer) { + value = exact_value_pointer(0); + } else if (bt->Basic.flags & BasicFlag_String) { + String empty_string = {}; + value = exact_value_string(empty_string); + } else if (bt->Basic.flags & BasicFlag_Rune) { + value = exact_value_i64(0); + } + } + + o->value = value; } else { - o->mode = Addressing_Value; + o->value = exact_value_compound(node); } - o->type = type; - case_end; + } else { + o->mode = Addressing_Value; + } + o->type = type; + return kind; +} - case_ast_node(pe, ParenExpr, node); - kind = check_expr_base(c, o, pe->expr, type_hint); - node->viral_state_flags |= pe->expr->viral_state_flags; +ExprKind check_type_assertion(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Expr; + ast_node(ta, TypeAssertion, node); + check_expr(c, o, ta->expr); + node->viral_state_flags |= ta->expr->viral_state_flags; + + if (o->mode == Addressing_Invalid) { o->expr = node; - case_end; + return kind; + } + if (o->mode == Addressing_Constant) { + gbString expr_str = expr_to_string(o->expr); + error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str); + gb_string_free(expr_str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } - case_ast_node(te, TagExpr, node); - String name = te->name.string; - error(node, "Unknown tag expression, #%.*s", LIT(name)); - if (te->expr) { - kind = check_expr_base(c, o, te->expr, type_hint); - node->viral_state_flags |= te->expr->viral_state_flags; - } + if (is_type_untyped(o->type)) { + gbString expr_str = expr_to_string(o->expr); + error(o->expr, "A type assertion cannot be applied to an untyped expression: '%s'", expr_str); + gb_string_free(expr_str); + o->mode = Addressing_Invalid; o->expr = node; - case_end; + return kind; + } - case_ast_node(ta, TypeAssertion, node); - check_expr(c, o, ta->expr); - node->viral_state_flags |= ta->expr->viral_state_flags; + Type *src = type_deref(o->type); + Type *bsrc = base_type(src); - if (o->mode == Addressing_Invalid) { - o->expr = node; - return kind; - } - if (o->mode == Addressing_Constant) { - gbString expr_str = expr_to_string(o->expr); - error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str); - gb_string_free(expr_str); + + if (ta->type != nullptr && ta->type->kind == Ast_UnaryExpr && ta->type->UnaryExpr.op.kind == Token_Question) { + if (!is_type_union(src)) { + gbString str = type_to_string(o->type); + error(o->expr, "Type assertions with .? can only operate on unions, got %s", str); + gb_string_free(str); o->mode = Addressing_Invalid; o->expr = node; return kind; } - if (is_type_untyped(o->type)) { - gbString expr_str = expr_to_string(o->expr); - error(o->expr, "A type assertion cannot be applied to an untyped expression: '%s'", expr_str); - gb_string_free(expr_str); + if (bsrc->Union.variants.count != 1 && type_hint != nullptr) { + bool allowed = false; + for_array(i, bsrc->Union.variants) { + Type *vt = bsrc->Union.variants[i]; + if (are_types_identical(vt, type_hint)) { + allowed = true; + add_type_info_type(c, vt); + break; + } + } + if (allowed) { + add_type_info_type(c, o->type); + o->type = type_hint; + o->mode = Addressing_OptionalOk; + return kind; + } + } + + if (bsrc->Union.variants.count != 1) { + error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %lld", cast(long long)bsrc->Union.variants.count); o->mode = Addressing_Invalid; o->expr = node; return kind; } - Type *src = type_deref(o->type); - Type *bsrc = base_type(src); - - - if (ta->type != nullptr && ta->type->kind == Ast_UnaryExpr && ta->type->UnaryExpr.op.kind == Token_Question) { - if (!is_type_union(src)) { - gbString str = type_to_string(o->type); - error(o->expr, "Type assertions with .? can only operate on unions, got %s", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } + add_type_info_type(c, o->type); + add_type_info_type(c, bsrc->Union.variants[0]); - if (bsrc->Union.variants.count != 1 && type_hint != nullptr) { - bool allowed = false; - for_array(i, bsrc->Union.variants) { - Type *vt = bsrc->Union.variants[i]; - if (are_types_identical(vt, type_hint)) { - allowed = true; - add_type_info_type(c, vt); - break; - } - } - if (allowed) { - add_type_info_type(c, o->type); - o->type = type_hint; - o->mode = Addressing_OptionalOk; - return kind; + o->type = bsrc->Union.variants[0]; + o->mode = Addressing_OptionalOk; + } else { + Type *t = check_type(c, ta->type); + Type *dst = t; + + if (is_type_union(src)) { + bool ok = false; + for_array(i, bsrc->Union.variants) { + Type *vt = bsrc->Union.variants[i]; + if (are_types_identical(vt, dst)) { + ok = true; + break; } } - if (bsrc->Union.variants.count != 1) { - error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %lld", cast(long long)bsrc->Union.variants.count); + if (!ok) { + gbString expr_str = expr_to_string(o->expr); + gbString dst_type_str = type_to_string(t); + defer (gb_string_free(expr_str)); + defer (gb_string_free(dst_type_str)); + if (bsrc->Union.variants.count == 0) { + error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str); + } else { + error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str); + } o->mode = Addressing_Invalid; o->expr = node; return kind; } add_type_info_type(c, o->type); - add_type_info_type(c, bsrc->Union.variants[0]); + add_type_info_type(c, t); - o->type = bsrc->Union.variants[0]; + o->type = t; + o->mode = Addressing_OptionalOk; + } else if (is_type_any(src)) { + o->type = t; o->mode = Addressing_OptionalOk; + + add_type_info_type(c, o->type); + add_type_info_type(c, t); } else { - Type *t = check_type(c, ta->type); - Type *dst = t; + gbString str = type_to_string(o->type); + error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } + } - if (is_type_union(src)) { - bool ok = false; - for_array(i, bsrc->Union.variants) { - Type *vt = bsrc->Union.variants[i]; - if (are_types_identical(vt, dst)) { - ok = true; - break; - } - } + if ((c->state_flags & StateFlag_no_type_assert) == 0) { + add_package_dependency(c, "runtime", "type_assertion_check"); + add_package_dependency(c, "runtime", "type_assertion_check2"); + } + return kind; +} - if (!ok) { - gbString expr_str = expr_to_string(o->expr); - gbString dst_type_str = type_to_string(t); - defer (gb_string_free(expr_str)); - defer (gb_string_free(dst_type_str)); - if (bsrc->Union.variants.count == 0) { - error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str); - } else { - error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str); - } - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } +ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ast_node(se, SelectorCallExpr, node); + // IMPORTANT NOTE(bill, 2020-05-22): This is a complete hack to get a shorthand which is extremely useful for vtables + // COM APIs is a great example of where this kind of thing is extremely useful + // General idea: + // + // x->y(123) == x.y(x, 123) + // + // How this has been implemented at the moment is quite hacky but it's done so to reduce need for huge backend changes + // Just regenerating a new AST aids things + // + // TODO(bill): Is this a good hack or not? + // + // NOTE(bill, 2020-05-22): I'm going to regret this decision, ain't I? + + + if (se->modified_call) { + // Prevent double evaluation + o->expr = node; + o->type = node->tav.type; + o->value = node->tav.value; + o->mode = node->tav.mode; + return Expr_Expr; + } - add_type_info_type(c, o->type); - add_type_info_type(c, t); + bool allow_arrow_right_selector_expr; + allow_arrow_right_selector_expr = c->allow_arrow_right_selector_expr; + c->allow_arrow_right_selector_expr = true; + Operand x = {}; + ExprKind kind = check_expr_base(c, &x, se->expr, nullptr); + c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr; - o->type = t; - o->mode = Addressing_OptionalOk; - } else if (is_type_any(src)) { - o->type = t; - o->mode = Addressing_OptionalOk; + if (x.mode == Addressing_Invalid || x.type == t_invalid) { + o->mode = Addressing_Invalid; + o->type = t_invalid; + o->expr = node; + return kind; + } + if (!is_type_proc(x.type)) { + gbString type_str = type_to_string(x.type); + error(se->call, "Selector call expressions expect a procedure type for the call, got '%s'", type_str); + gb_string_free(type_str); - add_type_info_type(c, o->type); - add_type_info_type(c, t); - } else { - gbString str = type_to_string(o->type); - error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } - } + o->mode = Addressing_Invalid; + o->type = t_invalid; + o->expr = node; + return Expr_Stmt; + } - add_package_dependency(c, "runtime", "type_assertion_check"); - add_package_dependency(c, "runtime", "type_assertion_check2"); - case_end; + ast_node(ce, CallExpr, se->call); - case_ast_node(tc, TypeCast, node); - check_expr_or_type(c, o, tc->type); - if (o->mode != Addressing_Type) { - gbString str = expr_to_string(tc->type); - error(tc->type, "Expected a type, got %s", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - } - if (o->mode == Addressing_Invalid) { - o->expr = node; - return kind; + GB_ASSERT(x.expr->kind == Ast_SelectorExpr); + + Ast *first_arg = x.expr->SelectorExpr.expr; + GB_ASSERT(first_arg != nullptr); + + Type *pt = base_type(x.type); + GB_ASSERT(pt->kind == Type_Proc); + Type *first_type = nullptr; + String first_arg_name = {}; + if (pt->Proc.param_count > 0) { + Entity *f = pt->Proc.params->Tuple.variables[0]; + first_type = f->type; + first_arg_name = f->token.string; + } + if (first_arg_name.len == 0) { + first_arg_name = str_lit("_"); + } + + if (first_type == nullptr) { + error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter"); + o->mode = Addressing_Invalid; + o->type = t_invalid; + o->expr = node; + return Expr_Stmt; + } + + Operand y = {}; + y.mode = first_arg->tav.mode; + y.type = first_arg->tav.type; + y.value = first_arg->tav.value; + if (check_is_assignable_to(c, &y, first_type)) { + // Do nothing, it's valid + } else { + Operand z = y; + z.type = type_deref(y.type); + if (check_is_assignable_to(c, &z, first_type)) { + // NOTE(bill): AST GENERATION HACK! + Token op = {Token_Pointer}; + first_arg = ast_deref_expr(first_arg->file(), first_arg, op); + } else if (y.mode == Addressing_Variable) { + Operand w = y; + w.type = alloc_type_pointer(y.type); + if (check_is_assignable_to(c, &w, first_type)) { + // NOTE(bill): AST GENERATION HACK! + Token op = {Token_And}; + first_arg = ast_unary_expr(first_arg->file(), op, first_arg); + } } - Type *type = o->type; - check_expr_base(c, o, tc->expr, type); - node->viral_state_flags |= tc->expr->viral_state_flags; + } - if (o->mode != Addressing_Invalid) { - switch (tc->token.kind) { - case Token_transmute: - check_transmute(c, node, o, type); - break; - case Token_cast: - check_cast(c, o, type); - break; - default: - error(node, "Invalid AST: Invalid casting expression"); - o->mode = Addressing_Invalid; + if (ce->args.count > 0) { + bool fail = false; + bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue); + for_array(i, ce->args) { + Ast *arg = ce->args[i]; + bool mix = false; + if (first_is_field_value) { + mix = arg->kind != Ast_FieldValue; + } else { + mix = arg->kind == Ast_FieldValue; + } + if (mix) { + fail = true; break; } } - return Expr_Expr; - case_end; + if (!fail && first_is_field_value) { + Token op = {Token_Eq}; + AstFile *f = first_arg->file(); + first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op); + } + } - case_ast_node(ac, AutoCast, node); - check_expr_base(c, o, ac->expr, type_hint); - node->viral_state_flags |= ac->expr->viral_state_flags; - if (o->mode == Addressing_Invalid) { + + auto modified_args = slice_make<Ast *>(heap_allocator(), ce->args.count+1); + modified_args[0] = first_arg; + slice_copy(&modified_args, ce->args, 1); + ce->args = modified_args; + se->modified_call = true; + + allow_arrow_right_selector_expr = c->allow_arrow_right_selector_expr; + c->allow_arrow_right_selector_expr = true; + check_expr_base(c, o, se->call, type_hint); + c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr; + + o->expr = node; + return Expr_Expr; +} + + +ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Expr; + ast_node(ie, IndexExpr, node); + check_expr(c, o, ie->expr); + node->viral_state_flags |= ie->expr->viral_state_flags; + if (o->mode == Addressing_Invalid) { + o->expr = node; + return kind; + } + + Type *t = base_type(type_deref(o->type)); + bool is_ptr = is_type_pointer(o->type); + bool is_const = o->mode == Addressing_Constant; + + if (is_type_map(t)) { + Operand key = {}; + if (is_type_typeid(t->Map.key)) { + check_expr_or_type(c, &key, ie->index, t->Map.key); + } else { + check_expr_with_type_hint(c, &key, ie->index, t->Map.key); + } + check_assignment(c, &key, t->Map.key, str_lit("map index")); + if (key.mode == Addressing_Invalid) { + o->mode = Addressing_Invalid; o->expr = node; return kind; } - if (type_hint) { - Type *type = type_of_expr(ac->expr); - check_cast(c, o, type_hint); - if (is_type_typed(type) && are_types_identical(type, type_hint)) { - if (build_context.vet_extra) { - error(node, "Redundant 'auto_cast' applied to expression"); - } - } - - } + o->mode = Addressing_MapIndex; + o->type = t->Map.value; o->expr = node; + + add_package_dependency(c, "runtime", "__dynamic_map_get"); + add_package_dependency(c, "runtime", "__dynamic_map_set"); return Expr_Expr; - case_end; + } - case_ast_node(ue, UnaryExpr, node); - Type *th = type_hint; - if (ue->op.kind == Token_And) { - th = type_deref(th); + i64 max_count = -1; + bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type); + + if (is_const) { + if (is_type_array(t)) { + // OKay + } else if (is_type_slice(t)) { + // Okay + } else if (is_type_enumerated_array(t)) { + // Okay + } else if (is_type_string(t)) { + // Okay + } else if (is_type_relative_slice(t)) { + // Okay + } else if (is_type_matrix(t)) { + // Okay + } else { + valid = false; } - check_expr_base(c, o, ue->expr, th); - node->viral_state_flags |= ue->expr->viral_state_flags; + } - if (o->mode != Addressing_Invalid) { - check_unary_expr(c, o, ue->op, node); + if (!valid) { + gbString str = expr_to_string(o->expr); + gbString type_str = type_to_string(o->type); + defer (gb_string_free(str)); + defer (gb_string_free(type_str)); + if (is_const) { + error(o->expr, "Cannot index constant '%s' of type '%s'", str, type_str); + } else { + error(o->expr, "Cannot index '%s' of type '%s'", str, type_str); } + o->mode = Addressing_Invalid; o->expr = node; return kind; - case_end; + } + if (ie->index == nullptr) { + gbString str = expr_to_string(o->expr); + error(o->expr, "Missing index for '%s'", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } - case_ast_node(be, BinaryExpr, node); - check_binary_expr(c, o, node, type_hint, true); - if (o->mode == Addressing_Invalid) { + Type *index_type_hint = nullptr; + if (is_type_enumerated_array(t)) { + Type *bt = base_type(t); + GB_ASSERT(bt->kind == Type_EnumeratedArray); + index_type_hint = bt->EnumeratedArray.index; + } + + i64 index = 0; + bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint); + if (is_const) { + if (index < 0) { + gbString str = expr_to_string(o->expr); + error(o->expr, "Cannot index a constant '%s'", str); + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + gb_string_free(str); + o->mode = Addressing_Invalid; o->expr = node; return kind; + } else if (ok) { + ExactValue value = type_and_value_of_expr(ie->expr).value; + o->mode = Addressing_Constant; + bool success = false; + bool finish = false; + o->value = get_constant_field_single(c, value, cast(i32)index, &success, &finish); + if (!success) { + gbString str = expr_to_string(o->expr); + error(o->expr, "Cannot index a constant '%s' with index %lld", str, cast(long long)index); + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } } - case_end; + } - case_ast_node(se, SelectorExpr, node); - check_selector(c, o, node, type_hint); - node->viral_state_flags |= se->expr->viral_state_flags; - case_end; + if (type_hint != nullptr && is_type_matrix(t)) { + // TODO(bill): allow matrix columns to be assignable to other types which are the same internally + // if a type hint exists + } + return kind; +} - case_ast_node(se, SelectorCallExpr, node); - // IMPORTANT NOTE(bill, 2020-05-22): This is a complete hack to get a shorthand which is extremely useful for vtables - // COM APIs is a great example of where this kind of thing is extremely useful - // General idea: - // - // x->y(123) == x.y(x, 123) - // - // How this has been implemented at the moment is quite hacky but it's done so to reduce need for huge backend changes - // Just regenerating a new AST aids things - // - // TODO(bill): Is this a good hack or not? - // - // NOTE(bill, 2020-05-22): I'm going to regret this decision, ain't I? - - - if (se->modified_call) { - // Prevent double evaluation - o->expr = node; - o->type = node->tav.type; - o->value = node->tav.value; - o->mode = node->tav.mode; - return Expr_Expr; - } +ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + ExprKind kind = Expr_Stmt; + ast_node(se, SliceExpr, node); + check_expr(c, o, se->expr); + node->viral_state_flags |= se->expr->viral_state_flags; - bool allow_arrow_right_selector_expr; - allow_arrow_right_selector_expr = c->allow_arrow_right_selector_expr; - c->allow_arrow_right_selector_expr = true; - Operand x = {}; - ExprKind kind = check_expr_base(c, &x, se->expr, nullptr); - c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr; + if (o->mode == Addressing_Invalid) { + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } - if (x.mode == Addressing_Invalid || x.type == t_invalid) { - o->mode = Addressing_Invalid; - o->type = t_invalid; - o->expr = node; - return kind; + bool valid = false; + i64 max_count = -1; + Type *t = base_type(type_deref(o->type)); + switch (t->kind) { + case Type_Basic: + if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { + valid = true; + if (o->mode == Addressing_Constant) { + max_count = o->value.value_string.len; + } + o->type = type_deref(o->type); } - if (!is_type_proc(x.type)) { - gbString type_str = type_to_string(x.type); - error(se->call, "Selector call expressions expect a procedure type for the call, got '%s'", type_str); - gb_string_free(type_str); + break; + case Type_Array: + valid = true; + max_count = t->Array.count; + if (o->mode != Addressing_Variable && !is_type_pointer(o->type)) { + gbString str = expr_to_string(node); + error(node, "Cannot slice array '%s', value is not addressable", str); + gb_string_free(str); o->mode = Addressing_Invalid; - o->type = t_invalid; o->expr = node; - return Expr_Stmt; + return kind; } + o->type = alloc_type_slice(t->Array.elem); + break; - ast_node(ce, CallExpr, se->call); + case Type_MultiPointer: + valid = true; + o->type = type_deref(o->type); + break; - GB_ASSERT(x.expr->kind == Ast_SelectorExpr); + case Type_Slice: + valid = true; + o->type = type_deref(o->type); + break; - Ast *first_arg = x.expr->SelectorExpr.expr; - GB_ASSERT(first_arg != nullptr); + case Type_DynamicArray: + valid = true; + o->type = alloc_type_slice(t->DynamicArray.elem); + break; - Type *pt = base_type(x.type); - GB_ASSERT(pt->kind == Type_Proc); - Type *first_type = nullptr; - String first_arg_name = {}; - if (pt->Proc.param_count > 0) { - Entity *f = pt->Proc.params->Tuple.variables[0]; - first_type = f->type; - first_arg_name = f->token.string; - } - if (first_arg_name.len == 0) { - first_arg_name = str_lit("_"); + case Type_Struct: + if (is_type_soa_struct(t)) { + valid = true; + o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem); } + break; - if (first_type == nullptr) { - error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter"); + case Type_RelativeSlice: + valid = true; + o->type = t->RelativeSlice.slice_type; + if (o->mode != Addressing_Variable) { + gbString str = expr_to_string(node); + error(node, "Cannot relative slice '%s', value is not addressable", str); + gb_string_free(str); o->mode = Addressing_Invalid; - o->type = t_invalid; o->expr = node; - return Expr_Stmt; + return kind; } + break; + } - Operand y = {}; - y.mode = first_arg->tav.mode; - y.type = first_arg->tav.type; - y.value = first_arg->tav.value; - if (check_is_assignable_to(c, &y, first_type)) { - // Do nothing, it's valid - } else { - Operand z = y; - z.type = type_deref(y.type); - if (check_is_assignable_to(c, &z, first_type)) { - // NOTE(bill): AST GENERATION HACK! - Token op = {Token_Pointer}; - first_arg = ast_deref_expr(first_arg->file(), first_arg, op); - } else if (y.mode == Addressing_Variable) { - Operand w = y; - w.type = alloc_type_pointer(y.type); - if (check_is_assignable_to(c, &w, first_type)) { - // NOTE(bill): AST GENERATION HACK! - Token op = {Token_And}; - first_arg = ast_unary_expr(first_arg->file(), op, first_arg); - } + if (!valid) { + gbString str = expr_to_string(o->expr); + gbString type_str = type_to_string(o->type); + error(o->expr, "Cannot slice '%s' of type '%s'", str, type_str); + gb_string_free(type_str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } + + if (se->low == nullptr && se->high != nullptr) { + // It is okay to continue as it will assume the 1st index is zero + } + + i64 indices[2] = {}; + Ast *nodes[2] = {se->low, se->high}; + for (isize i = 0; i < gb_count_of(nodes); i++) { + i64 index = max_count; + if (nodes[i] != nullptr) { + i64 capacity = -1; + if (max_count >= 0) { + capacity = max_count; + } + i64 j = 0; + if (check_index_value(c, t, true, nodes[i], capacity, &j)) { + index = j; } + + node->viral_state_flags |= nodes[i]->viral_state_flags; + } else if (i == 0) { + index = 0; } + indices[i] = index; + } - if (ce->args.count > 0) { - bool fail = false; - bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue); - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - bool mix = false; - if (first_is_field_value) { - mix = arg->kind != Ast_FieldValue; - } else { - mix = arg->kind == Ast_FieldValue; - } - if (mix) { - fail = true; + for (isize i = 0; i < gb_count_of(indices); i++) { + i64 a = indices[i]; + for (isize j = i+1; j < gb_count_of(indices); j++) { + i64 b = indices[j]; + if (a > b && b >= 0) { + error(se->close, "Invalid slice indices: [%td > %td]", a, b); + } + } + } + + if (max_count < 0) { + if (o->mode == Addressing_Constant) { + gbString s = expr_to_string(se->expr); + error(se->expr, "Cannot slice constant value '%s'", s); + gb_string_free(s); + } + } + + if (t->kind == Type_MultiPointer && se->high != nullptr) { + /* + x[:] -> [^]T + x[i:] -> [^]T + x[:n] -> []T + x[i:n] -> []T + */ + o->type = alloc_type_slice(t->MultiPointer.elem); + } + + o->mode = Addressing_Value; + + if (is_type_string(t) && max_count >= 0) { + bool all_constant = true; + for (isize i = 0; i < gb_count_of(nodes); i++) { + if (nodes[i] != nullptr) { + TypeAndValue tav = type_and_value_of_expr(nodes[i]); + if (tav.mode != Addressing_Constant) { + all_constant = false; break; } } - if (!fail && first_is_field_value) { - Token op = {Token_Eq}; - AstFile *f = first_arg->file(); - first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op); - } + } + if (!all_constant) { + gbString str = expr_to_string(o->expr); + error(o->expr, "Cannot slice '%s' with non-constant indices", str); + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + gb_string_free(str); + o->mode = Addressing_Value; // NOTE(bill): Keep subsequent values going without erring + o->expr = node; + return kind; } + String s = {}; + if (o->value.kind == ExactValue_String) { + s = o->value.value_string; + } + o->mode = Addressing_Constant; + o->type = t; + o->value = exact_value_string(substring(s, cast(isize)indices[0], cast(isize)indices[1])); + } + return kind; +} - auto modified_args = slice_make<Ast *>(heap_allocator(), ce->args.count+1); - modified_args[0] = first_arg; - slice_copy(&modified_args, ce->args, 1); - ce->args = modified_args; - se->modified_call = true; +ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + u32 prev_state_flags = c->state_flags; + defer (c->state_flags = prev_state_flags); + if (node->state_flags != 0) { + u32 in = node->state_flags; + u32 out = c->state_flags; - allow_arrow_right_selector_expr = c->allow_arrow_right_selector_expr; - c->allow_arrow_right_selector_expr = true; - check_expr_base(c, o, se->call, type_hint); - c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr; + if (in & StateFlag_no_bounds_check) { + out |= StateFlag_no_bounds_check; + out &= ~StateFlag_bounds_check; + } else if (in & StateFlag_bounds_check) { + out |= StateFlag_bounds_check; + out &= ~StateFlag_no_bounds_check; + } - o->expr = node; - return Expr_Expr; - case_end; + if (in & StateFlag_no_type_assert) { + out |= StateFlag_no_type_assert; + out &= ~StateFlag_type_assert; + } else if (in & StateFlag_type_assert) { + out |= StateFlag_type_assert; + out &= ~StateFlag_no_type_assert; + } + c->state_flags = out; + } - case_ast_node(ise, ImplicitSelectorExpr, node); - return check_implicit_selector_expr(c, o, node, type_hint); - case_end; + ExprKind kind = Expr_Stmt; - case_ast_node(ie, IndexExpr, node); - check_expr(c, o, ie->expr); - node->viral_state_flags |= ie->expr->viral_state_flags; - if (o->mode == Addressing_Invalid) { - o->expr = node; - return kind; - } + o->mode = Addressing_Invalid; + o->type = t_invalid; + o->value = {ExactValue_Invalid}; - Type *t = base_type(type_deref(o->type)); - bool is_ptr = is_type_pointer(o->type); - bool is_const = o->mode == Addressing_Constant; + switch (node->kind) { + default: + return kind; - if (is_type_map(t)) { - Operand key = {}; - if (is_type_typeid(t->Map.key)) { - check_expr_or_type(c, &key, ie->index, t->Map.key); - } else { - check_expr_with_type_hint(c, &key, ie->index, t->Map.key); - } - check_assignment(c, &key, t->Map.key, str_lit("map index")); - if (key.mode == Addressing_Invalid) { - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } - o->mode = Addressing_MapIndex; - o->type = t->Map.value; - o->expr = node; + case_ast_node(be, BadExpr, node) + return kind; + case_end; - add_package_dependency(c, "runtime", "__dynamic_map_get"); - add_package_dependency(c, "runtime", "__dynamic_map_set"); - return Expr_Expr; - } + case_ast_node(i, Implicit, node) + switch (i->kind) { + case Token_context: + { + if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) { + error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl); + return kind; + } + if (unparen_expr(c->assignment_lhs_hint) == node) { + c->scope->flags |= ScopeFlag_ContextDefined; + } - i64 max_count = -1; - bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type); + if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { + error(node, "'context' has not been defined within this scope"); + // Continue with value + } - if (is_const) { - if (is_type_array(t)) { - // OKay - } else if (is_type_slice(t)) { - // Okay - } else if (is_type_enumerated_array(t)) { - // Okay - } else if (is_type_string(t)) { - // Okay - } else if (is_type_relative_slice(t)) { - // Okay - } else if (is_type_matrix(t)) { - // Okay - } else { - valid = false; + init_core_context(c->checker); + o->mode = Addressing_Context; + o->type = t_context; } - } + break; - if (!valid) { - gbString str = expr_to_string(o->expr); - gbString type_str = type_to_string(o->type); - defer (gb_string_free(str)); - defer (gb_string_free(type_str)); - if (is_const) { - error(o->expr, "Cannot index constant '%s' of type '%s'", str, type_str); - } else { - error(o->expr, "Cannot index '%s' of type '%s'", str, type_str); - } - o->mode = Addressing_Invalid; - o->expr = node; + default: + error(node, "Illegal implicit name '%.*s'", LIT(i->string)); return kind; } + case_end; - if (ie->index == nullptr) { - gbString str = expr_to_string(o->expr); - error(o->expr, "Missing index for '%s'", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } + case_ast_node(i, Ident, node); + check_ident(c, o, node, nullptr, type_hint, false); + case_end; - Type *index_type_hint = nullptr; - if (is_type_enumerated_array(t)) { - Type *bt = base_type(t); - GB_ASSERT(bt->kind == Type_EnumeratedArray); - index_type_hint = bt->EnumeratedArray.index; - } + case_ast_node(u, Undef, node); + o->mode = Addressing_Value; + o->type = t_untyped_undef; + case_end; - i64 index = 0; - bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint); - if (is_const) { - if (index < 0) { - gbString str = expr_to_string(o->expr); - error(o->expr, "Cannot index a constant '%s'", str); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } else if (ok) { - ExactValue value = type_and_value_of_expr(ie->expr).value; - o->mode = Addressing_Constant; - bool success = false; - bool finish = false; - o->value = get_constant_field_single(c, value, cast(i32)index, &success, &finish); - if (!success) { - gbString str = expr_to_string(o->expr); - error(o->expr, "Cannot index a constant '%s' with index %lld", str, cast(long long)index); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } + + case_ast_node(bl, BasicLit, node); + Type *t = t_invalid; + switch (node->tav.value.kind) { + case ExactValue_String: t = t_untyped_string; break; + case ExactValue_Float: t = t_untyped_float; break; + case ExactValue_Complex: t = t_untyped_complex; break; + case ExactValue_Quaternion: t = t_untyped_quaternion; break; + case ExactValue_Integer: + t = t_untyped_integer; + if (bl->token.kind == Token_Rune) { + t = t_untyped_rune; } + break; + default: + GB_PANIC("Unhandled value type for basic literal"); + break; } - - if (type_hint != nullptr && is_type_matrix(t)) { - // TODO(bill): allow matrix columns to be assignable to other types which are the same internally - // if a type hint exists - } - + + o->mode = Addressing_Constant; + o->type = t; + o->value = node->tav.value; case_end; - case_ast_node(se, SliceExpr, node); - check_expr(c, o, se->expr); - node->viral_state_flags |= se->expr->viral_state_flags; + case_ast_node(bd, BasicDirective, node); + kind = check_basic_directive_expr(c, o, node, type_hint); + case_end; - if (o->mode == Addressing_Invalid) { - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } + case_ast_node(pg, ProcGroup, node); + error(node, "Illegal use of a procedure group"); + o->mode = Addressing_Invalid; + case_end; - bool valid = false; - i64 max_count = -1; - Type *t = base_type(type_deref(o->type)); - switch (t->kind) { - case Type_Basic: - if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { - valid = true; - if (o->mode == Addressing_Constant) { - max_count = o->value.value_string.len; - } - o->type = type_deref(o->type); + case_ast_node(pl, ProcLit, node); + CheckerContext ctx = *c; + + DeclInfo *decl = nullptr; + Type *type = alloc_type(Type_Proc); + check_open_scope(&ctx, pl->type); + { + decl = make_decl_info(ctx.scope, ctx.decl); + decl->proc_lit = node; + ctx.decl = decl; + defer (ctx.decl = ctx.decl->parent); + + if (pl->tags != 0) { + error(node, "A procedure literal cannot have tags"); + pl->tags = 0; // TODO(bill): Should I zero this?! } - break; - case Type_Array: - valid = true; - max_count = t->Array.count; - if (o->mode != Addressing_Variable && !is_type_pointer(o->type)) { + check_procedure_type(&ctx, type, pl->type); + if (!is_type_proc(type)) { gbString str = expr_to_string(node); - error(node, "Cannot slice array '%s', value is not addressable", str); + error(node, "Invalid procedure literal '%s'", str); gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; + check_close_scope(&ctx); return kind; } - o->type = alloc_type_slice(t->Array.elem); - break; - case Type_MultiPointer: - valid = true; - o->type = type_deref(o->type); - break; + if (pl->body == nullptr) { + error(node, "A procedure literal must have a body"); + return kind; + } - case Type_Slice: - valid = true; - o->type = type_deref(o->type); - break; + pl->decl = decl; + check_procedure_later(&ctx, ctx.file, empty_token, decl, type, pl->body, pl->tags); + } + check_close_scope(&ctx); - case Type_DynamicArray: - valid = true; - o->type = alloc_type_slice(t->DynamicArray.elem); - break; + o->mode = Addressing_Value; + o->type = type; + case_end; - case Type_Struct: - if (is_type_soa_struct(t)) { - valid = true; - o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem); - } - break; + case_ast_node(te, TernaryIfExpr, node); + kind = check_ternary_if_expr(c, o, node, type_hint); + case_end; - case Type_RelativeSlice: - valid = true; - o->type = t->RelativeSlice.slice_type; - if (o->mode != Addressing_Variable) { - gbString str = expr_to_string(node); - error(node, "Cannot relative slice '%s', value is not addressable", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } - break; + case_ast_node(te, TernaryWhenExpr, node); + kind = check_ternary_when_expr(c, o, node, type_hint); + case_end; + + case_ast_node(oe, OrElseExpr, node); + return check_or_else_expr(c, o, node, type_hint); + case_end; + + case_ast_node(re, OrReturnExpr, node); + return check_or_return_expr(c, o, node, type_hint); + case_end; + + case_ast_node(cl, CompoundLit, node); + kind = check_compound_literal(c, o, node, type_hint); + case_end; + + case_ast_node(pe, ParenExpr, node); + kind = check_expr_base(c, o, pe->expr, type_hint); + node->viral_state_flags |= pe->expr->viral_state_flags; + o->expr = node; + case_end; + + case_ast_node(te, TagExpr, node); + String name = te->name.string; + error(node, "Unknown tag expression, #%.*s", LIT(name)); + if (te->expr) { + kind = check_expr_base(c, o, te->expr, type_hint); + node->viral_state_flags |= te->expr->viral_state_flags; } + o->expr = node; + case_end; - if (!valid) { - gbString str = expr_to_string(o->expr); - gbString type_str = type_to_string(o->type); - error(o->expr, "Cannot slice '%s' of type '%s'", str, type_str); - gb_string_free(type_str); + case_ast_node(ta, TypeAssertion, node); + kind = check_type_assertion(c, o, node, type_hint); + case_end; + + case_ast_node(tc, TypeCast, node); + check_expr_or_type(c, o, tc->type); + if (o->mode != Addressing_Type) { + gbString str = expr_to_string(tc->type); + error(tc->type, "Expected a type, got %s", str); gb_string_free(str); o->mode = Addressing_Invalid; + } + if (o->mode == Addressing_Invalid) { o->expr = node; return kind; } + Type *type = o->type; + check_expr_base(c, o, tc->expr, type); + node->viral_state_flags |= tc->expr->viral_state_flags; - if (se->low == nullptr && se->high != nullptr) { - // It is okay to continue as it will assume the 1st index is zero + if (o->mode != Addressing_Invalid) { + switch (tc->token.kind) { + case Token_transmute: + check_transmute(c, node, o, type); + break; + case Token_cast: + check_cast(c, o, type); + break; + default: + error(node, "Invalid AST: Invalid casting expression"); + o->mode = Addressing_Invalid; + break; + } } + return Expr_Expr; + case_end; - i64 indices[2] = {}; - Ast *nodes[2] = {se->low, se->high}; - for (isize i = 0; i < gb_count_of(nodes); i++) { - i64 index = max_count; - if (nodes[i] != nullptr) { - i64 capacity = -1; - if (max_count >= 0) { - capacity = max_count; - } - i64 j = 0; - if (check_index_value(c, t, true, nodes[i], capacity, &j)) { - index = j; - } + case_ast_node(ac, AutoCast, node); + check_expr_base(c, o, ac->expr, type_hint); + node->viral_state_flags |= ac->expr->viral_state_flags; - node->viral_state_flags |= nodes[i]->viral_state_flags; - } else if (i == 0) { - index = 0; - } - indices[i] = index; + if (o->mode == Addressing_Invalid) { + o->expr = node; + return kind; } - - for (isize i = 0; i < gb_count_of(indices); i++) { - i64 a = indices[i]; - for (isize j = i+1; j < gb_count_of(indices); j++) { - i64 b = indices[j]; - if (a > b && b >= 0) { - error(se->close, "Invalid slice indices: [%td > %td]", a, b); + if (type_hint) { + Type *type = type_of_expr(ac->expr); + check_cast(c, o, type_hint); + if (is_type_typed(type) && are_types_identical(type, type_hint)) { + if (build_context.vet_extra) { + error(node, "Redundant 'auto_cast' applied to expression"); } } + } + o->expr = node; + return Expr_Expr; + case_end; - if (max_count < 0) { - if (o->mode == Addressing_Constant) { - gbString s = expr_to_string(se->expr); - error(se->expr, "Cannot slice constant value '%s'", s); - gb_string_free(s); - } + case_ast_node(ue, UnaryExpr, node); + Type *th = type_hint; + if (ue->op.kind == Token_And) { + th = type_deref(th); } + check_expr_base(c, o, ue->expr, th); + node->viral_state_flags |= ue->expr->viral_state_flags; - if (t->kind == Type_MultiPointer && se->high != nullptr) { - /* - x[:] -> [^]T - x[i:] -> [^]T - x[:n] -> []T - x[i:n] -> []T - */ - o->type = alloc_type_slice(t->MultiPointer.elem); + if (o->mode != Addressing_Invalid) { + check_unary_expr(c, o, ue->op, node); } + o->expr = node; + return kind; + case_end; - o->mode = Addressing_Value; - if (is_type_string(t) && max_count >= 0) { - bool all_constant = true; - for (isize i = 0; i < gb_count_of(nodes); i++) { - if (nodes[i] != nullptr) { - TypeAndValue tav = type_and_value_of_expr(nodes[i]); - if (tav.mode != Addressing_Constant) { - all_constant = false; - break; - } - } - } - if (!all_constant) { - gbString str = expr_to_string(o->expr); - error(o->expr, "Cannot slice '%s' with non-constant indices", str); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); - gb_string_free(str); - o->mode = Addressing_Value; // NOTE(bill): Keep subsequent values going without erring - o->expr = node; - return kind; - } + case_ast_node(be, BinaryExpr, node); + check_binary_expr(c, o, node, type_hint, true); + if (o->mode == Addressing_Invalid) { + o->expr = node; + return kind; + } + case_end; - String s = {}; - if (o->value.kind == ExactValue_String) { - s = o->value.value_string; - } + case_ast_node(se, SelectorExpr, node); + check_selector(c, o, node, type_hint); + node->viral_state_flags |= se->expr->viral_state_flags; + case_end; - o->mode = Addressing_Constant; - o->type = t; - o->value = exact_value_string(substring(s, cast(isize)indices[0], cast(isize)indices[1])); - } + case_ast_node(se, SelectorCallExpr, node); + return check_selector_call_expr(c, o, node, type_hint); + case_end; + case_ast_node(ise, ImplicitSelectorExpr, node); + return check_implicit_selector_expr(c, o, node, type_hint); + case_end; + + case_ast_node(ie, IndexExpr, node); + kind = check_index_expr(c, o, node, type_hint); + case_end; + + case_ast_node(se, SliceExpr, node); + kind = check_slice_expr(c, o, node, type_hint); case_end; case_ast_node(mie, MatrixIndexExpr, node); @@ -8911,6 +9386,8 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type return kind; } + + ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { ExprKind kind = check_expr_base_internal(c, o, node, type_hint); if (o->type != nullptr && core_type(o->type) == nullptr) { @@ -8926,6 +9403,8 @@ ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, Type *type_hi if (o->type != nullptr && is_type_untyped(o->type)) { add_untyped(c, node, o->mode, o->type, o->value); } + check_rtti_type_disallowed(node, o->type, "An expression is using a type, %s, which has been disallowed"); + add_type_and_value(c->info, node, o->mode, o->type, o->value); return kind; } @@ -9085,18 +9564,7 @@ gbString string_append_string(gbString str, String string) { gbString string_append_token(gbString str, Token token) { - if (token.kind == Token_String) { - str = gb_string_append_rune(str, '"'); - } else if (token.kind == Token_Rune) { - str = gb_string_append_rune(str, '\''); - } str = string_append_string(str, token.string); - if (token.kind == Token_String) { - str = gb_string_append_rune(str, '"'); - } else if (token.kind == Token_Rune) { - str = gb_string_append_rune(str, '\''); - } - return str; } @@ -9323,6 +9791,13 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { str = gb_string_appendc(str, " = "); str = write_expr_to_string(str, fv->value, shorthand); case_end; + case_ast_node(fv, EnumFieldValue, node); + str = write_expr_to_string(str, fv->name, shorthand); + if (fv->value) { + str = gb_string_appendc(str, " = "); + str = write_expr_to_string(str, fv->value, shorthand); + } + case_end; case_ast_node(ht, HelperType, node); str = gb_string_appendc(str, "#type "); @@ -9414,6 +9889,9 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { if (f->flags&FieldFlag_const) { str = gb_string_appendc(str, "#const "); } + if (f->flags&FieldFlag_subtype) { + str = gb_string_appendc(str, "#subtype "); + } for_array(i, f->names) { Ast *name = f->names[i]; @@ -9572,8 +10050,11 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { str = write_expr_to_string(str, st->polymorphic_params, shorthand); str = gb_string_appendc(str, ") "); } - if (st->no_nil) str = gb_string_appendc(str, "#no_nil "); - if (st->maybe) str = gb_string_appendc(str, "#maybe "); + switch (st->kind) { + case UnionType_maybe: str = gb_string_appendc(str, "#maybe "); break; + case UnionType_no_nil: str = gb_string_appendc(str, "#no_nil "); break; + case UnionType_shared_nil: str = gb_string_appendc(str, "#shared_nil "); break; + } if (st->align) { str = gb_string_appendc(str, "#align "); str = write_expr_to_string(str, st->align, shorthand); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 1a424240c..f2c830c1b 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -490,6 +490,14 @@ void check_stmt(CheckerContext *ctx, Ast *node, u32 flags) { out &= ~StateFlag_no_bounds_check; } + if (in & StateFlag_no_type_assert) { + out |= StateFlag_no_type_assert; + out &= ~StateFlag_type_assert; + } else if (in & StateFlag_type_assert) { + out |= StateFlag_type_assert; + out &= ~StateFlag_no_type_assert; + } + ctx->state_flags = out; } @@ -607,7 +615,7 @@ bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, Ast *expr, b case Entity_ImportName: { Scope *scope = e->ImportName.scope; - for_array(i, scope->elements.entries) { + MUTEX_GUARD_BLOCK(scope->mutex) for_array(i, scope->elements.entries) { String name = scope->elements.entries[i].key.string; Entity *decl = scope->elements.entries[i].value; if (!is_entity_exported(decl)) continue; @@ -689,54 +697,6 @@ bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, Ast *expr, b return true; } - -struct TypeAndToken { - Type *type; - Token token; -}; - - -void add_constant_switch_case(CheckerContext *ctx, PtrMap<uintptr, TypeAndToken> *seen, Operand operand, bool use_expr = true) { - if (operand.mode != Addressing_Constant) { - return; - } - if (operand.value.kind == ExactValue_Invalid) { - return; - } - - uintptr key = hash_exact_value(operand.value); - TypeAndToken *found = map_get(seen, key); - if (found != nullptr) { - isize count = multi_map_count(seen, key); - TypeAndToken *taps = gb_alloc_array(temporary_allocator(), TypeAndToken, count); - - multi_map_get_all(seen, key, taps); - for (isize i = 0; i < count; i++) { - TypeAndToken tap = taps[i]; - if (!are_types_identical(operand.type, tap.type)) { - continue; - } - - TokenPos pos = tap.token.pos; - if (use_expr) { - gbString expr_str = expr_to_string(operand.expr); - error(operand.expr, - "Duplicate case '%s'\n" - "\tprevious case at %s", - expr_str, - token_pos_to_string(pos)); - gb_string_free(expr_str); - } else { - error(operand.expr, "Duplicate case found with previous case at %s", token_pos_to_string(pos)); - } - return; - } - } - - TypeAndToken tap = {operand.type, ast_token(operand.expr)}; - multi_map_insert(seen, key, tap); -} - void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { ast_node(irs, UnrollRangeStmt, node); check_open_scope(ctx, node); @@ -961,7 +921,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { } } - PtrMap<uintptr, TypeAndToken> seen = {}; // NOTE(bill): Multimap, Key: ExactValue + SeenMap seen = {}; // NOTE(bill): Multimap, Key: ExactValue map_init(&seen, heap_allocator()); defer (map_destroy(&seen)); @@ -1001,9 +961,9 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { TokenKind upper_op = Token_Invalid; switch (be->op.kind) { - case Token_Ellipsis: upper_op = Token_GtEq; break; - case Token_RangeFull: upper_op = Token_GtEq; break; - case Token_RangeHalf: upper_op = Token_Gt; break; + case Token_Ellipsis: upper_op = Token_LtEq; break; + case Token_RangeFull: upper_op = Token_LtEq; break; + case Token_RangeHalf: upper_op = Token_Lt; break; default: GB_PANIC("Invalid range operator"); break; } @@ -1024,45 +984,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { Operand b1 = rhs; check_comparison(ctx, &a1, &b1, Token_LtEq); - if (is_type_enum(x.type)) { - // TODO(bill): Fix this logic so it's fast!!! - - i64 v0 = exact_value_to_i64(lhs.value); - i64 v1 = exact_value_to_i64(rhs.value); - Operand v = {}; - v.mode = Addressing_Constant; - v.type = x.type; - v.expr = x.expr; - - Type *bt = base_type(x.type); - GB_ASSERT(bt->kind == Type_Enum); - for (i64 vi = v0; vi <= v1; vi++) { - if (upper_op != Token_GtEq && vi == v1) { - break; - } - - bool found = false; - for_array(j, bt->Enum.fields) { - Entity *f = bt->Enum.fields[j]; - GB_ASSERT(f->kind == Entity_Constant); - - i64 fv = exact_value_to_i64(f->Constant.value); - if (fv == vi) { - found = true; - break; - } - } - if (found) { - v.value = exact_value_i64(vi); - add_constant_switch_case(ctx, &seen, v); - } - } - } else { - add_constant_switch_case(ctx, &seen, lhs); - if (upper_op == Token_GtEq) { - add_constant_switch_case(ctx, &seen, rhs); - } - } + add_to_seen_map(ctx, &seen, upper_op, x, lhs, rhs); if (is_type_string(x.type)) { // NOTE(bill): Force dependency for strings here @@ -1107,7 +1029,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { continue; } update_untyped_expr_type(ctx, z.expr, x.type, !is_type_untyped(x.type)); - add_constant_switch_case(ctx, &seen, y); + add_to_seen_map(ctx, &seen, y); } } } @@ -1143,7 +1065,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { if (unhandled.count == 1) { error_no_newline(node, "Unhandled switch case: %.*s", LIT(unhandled[0]->token.string)); } else { - error_no_newline(node, "Unhandled switch cases: "); + error(node, "Unhandled switch cases:"); for_array(i, unhandled) { Entity *f = unhandled[i]; error_line("\t%.*s\n", LIT(f->token.string)); @@ -2230,7 +2152,6 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { e->state = EntityState_Resolved; } ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); - e->Variable.thread_local_model = ac.thread_local_model; if (ac.link_name.len > 0) { e->Variable.link_name = ac.link_name; @@ -2243,6 +2164,9 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { error(e->token, "The 'static' attribute is not allowed to be applied to '_'"); } else { e->flags |= EntityFlag_Static; + if (ctx->in_defer) { + error(e->token, "'static' variables cannot be declared within a defer statement"); + } } } if (ac.thread_local_model != "") { @@ -2251,10 +2175,18 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { error(e->token, "The 'thread_local' attribute is not allowed to be applied to '_'"); } else { e->flags |= EntityFlag_Static; + if (ctx->in_defer) { + error(e->token, "'thread_local' variables cannot be declared within a defer statement"); + } } e->Variable.thread_local_model = ac.thread_local_model; } + if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { + error(e->token, "@(thread_local) is not supported for this target platform"); + } + + if (ac.is_static && ac.thread_local_model != "") { error(e->token, "The 'static' attribute is not needed if 'thread_local' is applied"); } diff --git a/src/check_type.cpp b/src/check_type.cpp index b4f30d2f0..51f472961 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -109,14 +109,19 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields } i32 field_src_index = 0; + i32 field_group_index = -1; for_array(i, params) { Ast *param = params[i]; if (param->kind != Ast_Field) { continue; } + field_group_index += 1; + ast_node(p, Field, param); Ast *type_expr = p->type; Type *type = nullptr; + CommentGroup *docs = p->docs; + CommentGroup *comment = p->comment; if (type_expr != nullptr) { type = check_type_expr(ctx, type_expr, nullptr); @@ -139,6 +144,7 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields } bool is_using = (p->flags&FieldFlag_using) != 0; + bool is_subtype = (p->flags&FieldFlag_subtype) != 0; for_array(j, p->names) { Ast *name = p->names[j]; @@ -152,6 +158,18 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields Entity *field = alloc_entity_field(ctx->scope, name_token, type, is_using, field_src_index); add_entity(ctx, ctx->scope, name, field); + field->Variable.field_group_index = field_group_index; + if (is_subtype) { + field->flags |= EntityFlag_Subtype; + } + + if (j == 0) { + field->Variable.docs = docs; + } + if (j+1 == p->names.count) { + field->Variable.comment = comment; + } + array_add(&fields_array, field); String tag = p->tag.string; if (tag.len != 0 && !unquote_string(permanent_allocator(), &tag, 0, tag.text[0] == '`')) { @@ -180,6 +198,20 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields populate_using_entity_scope(ctx, node, p, type); } + + if (is_subtype && p->names.count > 0) { + Type *first_type = fields_array[fields_array.count-1]->type; + Type *t = base_type(type_deref(first_type)); + + if (!does_field_type_allow_using(t) && + p->names.count >= 1 && + p->names[0]->kind == Ast_Ident) { + Token name_token = p->names[0]->Ident.token; + gbString type_str = type_to_string(first_type); + error(name_token, "'subtype' cannot be applied to the field '%.*s' of type '%s'", LIT(name_token.string), type_str); + gb_string_free(type_str); + } + } } *fields = slice_from_array(fields_array); @@ -309,6 +341,10 @@ void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_t } named_type->Named.type_name = e; + GB_ASSERT(original_type->kind == Type_Named); + e->TypeName.objc_class_name = original_type->Named.type_name->TypeName.objc_class_name; + // TODO(bill): Is this even correct? Or should the metadata be copied? + e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; mutex_lock(&ctx->info->gen_types_mutex); auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); @@ -639,22 +675,31 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Op } if (ok) { array_add(&variants, t); + + if (ut->kind == UnionType_shared_nil) { + if (!type_has_nil(t)) { + gbString s = type_to_string(t); + error(node, "Each variant of a union with #shared_nil must have a 'nil' value, got %s", s); + gb_string_free(s); + } + } } } } union_type->Union.variants = slice_from_array(variants); - union_type->Union.no_nil = ut->no_nil; - union_type->Union.maybe = ut->maybe; - if (union_type->Union.no_nil) { + union_type->Union.kind = ut->kind; + switch (ut->kind) { + case UnionType_no_nil: if (variants.count < 2) { error(ut->align, "A union with #no_nil must have at least 2 variants"); } - } - if (union_type->Union.maybe) { + break; + case UnionType_maybe: if (variants.count != 1) { error(ut->align, "A union with #maybe must have at 1 variant, got %lld", cast(long long)variants.count); } + break; } if (ut->align != nullptr) { @@ -718,20 +763,19 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast Ast *ident = nullptr; Ast *init = nullptr; u32 entity_flags = 0; - if (field->kind == Ast_FieldValue) { - ast_node(fv, FieldValue, field); - if (fv->field == nullptr || fv->field->kind != Ast_Ident) { - error(field, "An enum field's name must be an identifier"); - continue; - } - ident = fv->field; - init = fv->value; - } else if (field->kind == Ast_Ident) { - ident = field; - } else { + if (field->kind != Ast_EnumFieldValue) { + error(field, "An enum field's name must be an identifier"); + continue; + } + ident = field->EnumFieldValue.name; + init = field->EnumFieldValue.value; + if (ident == nullptr || ident->kind != Ast_Ident) { error(field, "An enum field's name must be an identifier"); continue; } + CommentGroup *docs = field->EnumFieldValue.docs; + CommentGroup *comment = field->EnumFieldValue.comment; + String name = ident->Ident.token.string; if (init != nullptr) { @@ -789,6 +833,8 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast e->flags |= EntityFlag_Visited; e->state = EntityState_Resolved; e->Constant.flags |= entity_flags; + e->Constant.docs = docs; + e->Constant.comment = comment; if (scope_lookup_current(ctx->scope, name) != nullptr) { error(ident, "'%.*s' is already declared in this enumeration", LIT(name)); @@ -922,20 +968,19 @@ void check_bit_set_type(CheckerContext *c, Type *type, Type *named_type, Ast *no i64 lower = big_int_to_i64(&i); i64 upper = big_int_to_i64(&j); - bool lower_changed = false; + i64 actual_lower = lower; i64 bits = MAX_BITS; if (type->BitSet.underlying != nullptr) { bits = 8*type_size_of(type->BitSet.underlying); if (lower > 0) { - lower = 0; - lower_changed = true; + actual_lower = 0; } else if (lower < 0) { error(bs->elem, "bit_set does not allow a negative lower bound (%lld) when an underlying type is set", lower); } } - i64 bits_required = upper-lower; + i64 bits_required = upper-actual_lower; switch (be->op.kind) { case Token_Ellipsis: case Token_RangeFull: @@ -959,7 +1004,7 @@ void check_bit_set_type(CheckerContext *c, Type *type, Type *named_type, Ast *no break; } if (!is_valid) { - if (lower_changed) { + if (actual_lower != lower) { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required (internal the lower changed was changed 0 as an underlying type was set)", bits, bits_required); } else { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, bits_required); @@ -1367,11 +1412,13 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is isize variadic_index = -1; bool is_c_vararg = false; auto variables = array_make<Entity *>(permanent_allocator(), 0, variable_count); + i32 field_group_index = -1; for_array(i, params) { Ast *param = params[i]; if (param->kind != Ast_Field) { continue; } + field_group_index += 1; ast_node(p, Field, param); Ast *type_expr = unparen_expr(p->type); Type *type = nullptr; @@ -1672,9 +1719,11 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is } param = alloc_entity_const_param(scope, name->Ident.token, type, poly_const, is_type_polymorphic(type)); + param->Constant.field_group_index = field_group_index; } else { param = alloc_entity_param(scope, name->Ident.token, type, is_using, true); param->Variable.param_value = param_value; + param->Variable.field_group_index = field_group_index; } } if (p->flags&FieldFlag_no_alias) { @@ -1768,7 +1817,10 @@ Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_results) { } auto variables = array_make<Entity *>(permanent_allocator(), 0, variable_count); + i32 field_group_index = -1; for_array(i, results) { + field_group_index += 1; + ast_node(field, Field, results[i]); Ast *default_value = unparen_expr(field->default_value); ParameterValue param_value = {}; @@ -1799,6 +1851,7 @@ Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_results) { token.string = str_lit(""); Entity *param = alloc_entity_param(scope, token, type, false, false); param->Variable.param_value = param_value; + param->Variable.field_group_index = -1; array_add(&variables, param); } else { for_array(j, field->names) { @@ -1822,6 +1875,7 @@ Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_results) { Entity *param = alloc_entity_param(scope, token, type, false, false); param->flags |= EntityFlag_Result; param->Variable.param_value = param_value; + param->Variable.field_group_index = field_group_index; array_add(&variables, param); add_entity(ctx, scope, name, param); // NOTE(bill): Removes `declared but not used` when using -vet @@ -1883,6 +1937,25 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, c->scope->flags &= ~ScopeFlag_ContextDefined; } + TargetArchKind arch = build_context.metrics.arch; + switch (cc) { + case ProcCC_StdCall: + case ProcCC_FastCall: + if (arch != TargetArch_i386 && arch != TargetArch_amd64) { + error(proc_type_node, "Invalid procedure calling convention \"%s\" for target architecture, expected either i386 or amd64, got %.*s", + proc_calling_convention_strings[cc], LIT(target_arch_names[arch])); + } + break; + case ProcCC_Win64: + case ProcCC_SysV: + if (arch != TargetArch_amd64) { + error(proc_type_node, "Invalid procedure calling convention \"%s\" for target architecture, expected amd64, got %.*s", + proc_calling_convention_strings[cc], LIT(target_arch_names[arch])); + } + break; + } + + bool variadic = false; isize variadic_index = -1; bool success = true; @@ -1896,20 +1969,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, if (params) param_count = params ->Tuple.variables.count; if (results) result_count = results->Tuple.variables.count; - if (param_count > 0) { - for_array(i, params->Tuple.variables) { - Entity *param = params->Tuple.variables[i]; - if (param->kind == Entity_Variable) { - ParameterValue pv = param->Variable.param_value; - if (pv.kind == ParameterValue_Constant && - pv.value.kind == ExactValue_Procedure) { - type->Proc.has_proc_default_values = true; - break; - } - } - } - } - if (result_count > 0) { Entity *first = results->Tuple.variables[0]; type->Proc.has_named_results = first->token.string != ""; @@ -1967,10 +2026,14 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, if (param_count > 0) { Entity *end = params->Tuple.variables[param_count-1]; if (end->flags&EntityFlag_CVarArg) { - if (cc == ProcCC_StdCall || cc == ProcCC_CDecl) { + switch (cc) { + default: type->Proc.c_vararg = true; - } else { + break; + case ProcCC_Odin: + case ProcCC_Contextless: error(end->token, "Calling convention does not support #c_vararg"); + break; } } } @@ -2106,7 +2169,7 @@ void init_map_entry_type(Type *type) { /* struct { - hash: runtime.Map_Hash, + hash: uintptr, next: int, key: Key, value: Value, @@ -2285,10 +2348,21 @@ void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) { } if (!is_type_valid_for_matrix_elems(elem)) { + if (elem == t_typeid) { + Entity *e = entity_of_node(mt->elem); + if (e && e->kind == Entity_TypeName && e->TypeName.is_type_alias) { + // HACK TODO(bill): This is to allow polymorphic parameters for matrix elements + // proc($T: typeid) -> matrix[2, 2]T + // + // THIS IS NEEDS TO BE FIXED AND NOT USE THIS HACK + goto type_assign; + } + } gbString s = type_to_string(elem); error(column.expr, "Matrix elements types are limited to integers, floats, and complex, got %s", s); gb_string_free(s); } +type_assign:; *type = alloc_type_matrix(elem, row_count, column_count, generic_row, generic_column); @@ -2679,29 +2753,30 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t Type *t = alloc_type_enumerated_array(elem, index, bt->Enum.min_value, bt->Enum.max_value, Token_Invalid); - bool is_partial = false; + bool is_sparse = false; if (at->tag != nullptr) { GB_ASSERT(at->tag->kind == Ast_BasicDirective); String name = at->tag->BasicDirective.name.string; - if (name == "partial") { - is_partial = true; + if (name == "sparse") { + is_sparse = true; } else { error(at->tag, "Invalid tag applied to an enumerated array, got #%.*s", LIT(name)); } } - if (!is_partial && t->EnumeratedArray.count > bt->Enum.fields.count) { + if (!is_sparse && t->EnumeratedArray.count > bt->Enum.fields.count) { error(e, "Non-contiguous enumeration used as an index in an enumerated array"); long long ea_count = cast(long long)t->EnumeratedArray.count; long long enum_count = cast(long long)bt->Enum.fields.count; error_line("\tenumerated array length: %lld\n", ea_count); error_line("\tenum field count: %lld\n", enum_count); - error_line("\tSuggestion: prepend #partial to the enumerated array to allow for non-named elements\n"); + error_line("\tSuggestion: prepend #sparse to the enumerated array to allow for non-contiguous elements\n"); if (2*enum_count < ea_count) { error_line("\tWarning: the number of named elements is much smaller than the length of the array, are you sure this is what you want?\n"); - error_line("\t this warning will be removed if #partial is applied\n"); + error_line("\t this warning will be removed if #sparse is applied\n"); } } + t->EnumeratedArray.is_sparse = is_sparse; *type = t; @@ -2951,5 +3026,7 @@ Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) { } set_base_type(named_type, type); + check_rtti_type_disallowed(e, type, "Use of a type, %s, which has been disallowed"); + return type; } diff --git a/src/checker.cpp b/src/checker.cpp index 667146eda..1bb786ea1 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4,7 +4,7 @@ void check_expr(CheckerContext *c, Operand *operand, Ast *expression); void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr); void add_comparison_procedures_for_fields(CheckerContext *c, Type *t); - +Type *check_type(CheckerContext *ctx, Ast *e); bool is_operand_value(Operand o) { switch (o.mode) { @@ -29,6 +29,23 @@ bool is_operand_undef(Operand o) { return o.mode == Addressing_Value && o.type == t_untyped_undef; } +bool check_rtti_type_disallowed(Token const &token, Type *type, char const *format) { + if (build_context.disallow_rtti && type) { + if (is_type_any(type)) { + gbString t = type_to_string(type); + error(token, format, t); + gb_string_free(t); + return true; + } + } + return false; +} + +bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *format) { + GB_ASSERT(expr != nullptr); + return check_rtti_type_disallowed(ast_token(expr), type, format); +} + void scope_reset(Scope *scope) { if (scope == nullptr) return; @@ -225,8 +242,8 @@ bool decl_info_has_init(DeclInfo *d) { Scope *create_scope(CheckerInfo *info, Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CAPACITY) { Scope *s = gb_alloc_item(permanent_allocator(), Scope); s->parent = parent; - string_map_init(&s->elements, permanent_allocator(), init_elements_capacity); - ptr_set_init(&s->imported, permanent_allocator(), 0); + string_map_init(&s->elements, heap_allocator(), init_elements_capacity); + ptr_set_init(&s->imported, heap_allocator(), 0); mutex_init(&s->mutex); if (parent != nullptr && parent != builtin_pkg->scope) { @@ -446,7 +463,7 @@ Entity *scope_lookup(Scope *s, String const &name) { -Entity *scope_insert_with_name(Scope *s, String const &name, Entity *entity) { +Entity *scope_insert_with_name(Scope *s, String const &name, Entity *entity, bool use_mutex=true) { if (name == "") { return nullptr; } @@ -454,8 +471,8 @@ Entity *scope_insert_with_name(Scope *s, String const &name, Entity *entity) { Entity **found = nullptr; Entity *result = nullptr; - mutex_lock(&s->mutex); - defer (mutex_unlock(&s->mutex)); + if (use_mutex) mutex_lock(&s->mutex); + defer (if (use_mutex) mutex_unlock(&s->mutex)); found = string_map_get(&s->elements, key); @@ -485,9 +502,9 @@ end:; return result; } -Entity *scope_insert(Scope *s, Entity *entity) { +Entity *scope_insert(Scope *s, Entity *entity, bool use_mutex) { String name = entity->token.string; - return scope_insert_with_name(s, name, entity); + return scope_insert_with_name(s, name, entity, use_mutex); } @@ -504,6 +521,7 @@ enum VettedEntityKind { VettedEntity_Unused, VettedEntity_Shadowed, + VettedEntity_Shadowed_And_Unused, }; struct VettedEntity { VettedEntityKind kind; @@ -622,15 +640,21 @@ void check_scope_usage(Checker *c, Scope *scope) { Array<VettedEntity> vetted_entities = {}; array_init(&vetted_entities, heap_allocator()); - for_array(i, scope->elements.entries) { + MUTEX_GUARD_BLOCK(scope->mutex) for_array(i, scope->elements.entries) { Entity *e = scope->elements.entries[i].value; if (e == nullptr) continue; - VettedEntity ve = {}; - if (vet_unused && check_vet_unused(c, e, &ve)) { - array_add(&vetted_entities, ve); - } - if (vet_shadowing && check_vet_shadowing(c, e, &ve)) { - array_add(&vetted_entities, ve); + VettedEntity ve_unused = {}; + VettedEntity ve_shadowed = {}; + bool is_unused = vet_unused && check_vet_unused(c, e, &ve_unused); + bool is_shadowed = vet_shadowing && check_vet_shadowing(c, e, &ve_shadowed); + if (is_unused && is_shadowed) { + VettedEntity ve_both = ve_shadowed; + ve_both.kind = VettedEntity_Shadowed_And_Unused; + array_add(&vetted_entities, ve_both); + } else if (is_unused) { + array_add(&vetted_entities, ve_unused); + } else if (is_shadowed) { + array_add(&vetted_entities, ve_shadowed); } } @@ -642,16 +666,18 @@ void check_scope_usage(Checker *c, Scope *scope) { Entity *other = ve.other; String name = e->token.string; - if (build_context.vet) { + if (ve.kind == VettedEntity_Shadowed_And_Unused) { + error(e->token, "'%.*s' declared but not used, possibly shadows declaration at line %d", LIT(name), other->token.pos.line); + } else if (build_context.vet) { switch (ve.kind) { case VettedEntity_Unused: error(e->token, "'%.*s' declared but not used", LIT(name)); break; case VettedEntity_Shadowed: if (e->flags&EntityFlag_Using) { - error(e->token, "Declaration of '%.*s' from 'using' shadows declaration at line %lld", LIT(name), cast(long long)other->token.pos.line); + error(e->token, "Declaration of '%.*s' from 'using' shadows declaration at line %d", LIT(name), other->token.pos.line); } else { - error(e->token, "Declaration of '%.*s' shadows declaration at line %lld", LIT(name), cast(long long)other->token.pos.line); + error(e->token, "Declaration of '%.*s' shadows declaration at line %d", LIT(name), other->token.pos.line); } break; default: @@ -688,12 +714,17 @@ void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { ptr_set_add(&d->deps, e); mutex_unlock(&info->deps_mutex); } -void add_type_info_dependency(DeclInfo *d, Type *type) { +void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type, bool require_mutex) { if (d == nullptr) { return; } - // NOTE(bill): no mutex is required here because the only procedure calling it is wrapped in a mutex already + if (require_mutex) { + mutex_lock(&info->deps_mutex); + } ptr_set_add(&d->type_info_deps, type); + if (require_mutex) { + mutex_unlock(&info->deps_mutex); + } } AstPackage *get_core_package(CheckerInfo *info, String name) { @@ -719,12 +750,25 @@ void add_package_dependency(CheckerContext *c, char const *package_name, char co String n = make_string_c(name); AstPackage *p = get_core_package(&c->checker->info, make_string_c(package_name)); Entity *e = scope_lookup(p->scope, n); - e->flags |= EntityFlag_Used; GB_ASSERT_MSG(e != nullptr, "%s", name); GB_ASSERT(c->decl != nullptr); + e->flags |= EntityFlag_Used; + add_dependency(c->info, c->decl, e); +} + +void try_to_add_package_dependency(CheckerContext *c, char const *package_name, char const *name) { + String n = make_string_c(name); + AstPackage *p = get_core_package(&c->checker->info, make_string_c(package_name)); + Entity *e = scope_lookup(p->scope, n); + if (e == nullptr) { + return; + } + GB_ASSERT(c->decl != nullptr); + e->flags |= EntityFlag_Used; add_dependency(c->info, c->decl, e); } + void add_declaration_dependency(CheckerContext *c, Entity *e) { if (e == nullptr) { return; @@ -780,6 +824,69 @@ AstPackage *create_builtin_package(char const *name) { return pkg; } +struct GlobalEnumValue { + char const *name; + i64 value; +}; + +Slice<Entity *> add_global_enum_type(String const &type_name, GlobalEnumValue *values, isize value_count, Type **enum_type_ = nullptr) { + Scope *scope = create_scope(nullptr, builtin_pkg->scope); + Entity *entity = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); + + Type *enum_type = alloc_type_enum(); + Type *named_type = alloc_type_named(type_name, enum_type, entity); + set_base_type(named_type, enum_type); + enum_type->Enum.base_type = t_int; + enum_type->Enum.scope = scope; + entity->type = named_type; + + auto fields = array_make<Entity *>(permanent_allocator(), value_count); + for (isize i = 0; i < value_count; i++) { + i64 value = values[i].value; + Entity *e = alloc_entity_constant(scope, make_token_ident(values[i].name), named_type, exact_value_i64(value)); + e->flags |= EntityFlag_Visited; + e->state = EntityState_Resolved; + fields[i] = e; + + Entity *ie = scope_insert(scope, e); + GB_ASSERT(ie == nullptr); + } + + + enum_type->Enum.fields = fields; + enum_type->Enum.min_value_index = 0; + enum_type->Enum.max_value_index = value_count-1; + enum_type->Enum.min_value = &enum_type->Enum.fields[enum_type->Enum.min_value_index]->Constant.value; + enum_type->Enum.max_value = &enum_type->Enum.fields[enum_type->Enum.max_value_index]->Constant.value; + + + if (enum_type_) *enum_type_ = named_type; + + return slice_from_array(fields); +} +void add_global_enum_constant(Slice<Entity *> const &fields, char const *name, i64 value) { + for (Entity *field : fields) { + GB_ASSERT(field->kind == Entity_Constant); + if (value == exact_value_to_i64(field->Constant.value)) { + add_global_constant(name, field->type, field->Constant.value); + return; + } + } + GB_PANIC("Unfound enum value for global constant: %s %lld", name, cast(long long)value); +} + +Type *add_global_type_name(Scope *scope, String const &type_name, Type *backing_type) { + Entity *e = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); + Type *named_type = alloc_type_named(type_name, backing_type, e); + e->type = named_type; + set_base_type(named_type, backing_type); + if (scope_insert(scope, e)) { + compiler_error("double declaration of %.*s", LIT(e->token.string)); + } + return named_type; +} + + void init_universal(void) { BuildContext *bc = &build_context; @@ -789,7 +896,8 @@ void init_universal(void) { // Types for (isize i = 0; i < gb_count_of(basic_types); i++) { - add_global_type_entity(basic_types[i].Basic.name, &basic_types[i]); + String const &name = basic_types[i].Basic.name; + add_global_type_entity(name, &basic_types[i]); } add_global_type_entity(str_lit("byte"), &basic_types[Basic_u8]); @@ -808,14 +916,96 @@ void init_universal(void) { add_global_bool_constant("false", false); // TODO(bill): Set through flags in the compiler - add_global_string_constant("ODIN_OS", bc->ODIN_OS); - add_global_string_constant("ODIN_ARCH", bc->ODIN_ARCH); - add_global_string_constant("ODIN_ENDIAN", bc->ODIN_ENDIAN); add_global_string_constant("ODIN_VENDOR", bc->ODIN_VENDOR); add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION); add_global_string_constant("ODIN_ROOT", bc->ODIN_ROOT); + + { + GlobalEnumValue values[TargetOs_COUNT] = { + {"Unknown", TargetOs_Invalid}, + {"Windows", TargetOs_windows}, + {"Darwin", TargetOs_darwin}, + {"Linux", TargetOs_linux}, + {"Essence", TargetOs_essence}, + {"FreeBSD", TargetOs_freebsd}, + {"OpenBSD", TargetOs_openbsd}, + {"WASI", TargetOs_wasi}, + {"JS", TargetOs_js}, + {"Freestanding", TargetOs_freestanding}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_OS_Type"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_OS", bc->metrics.os); + add_global_string_constant("ODIN_OS_STRING", target_os_names[bc->metrics.os]); + } + + { + GlobalEnumValue values[TargetArch_COUNT] = { + {"Unknown", TargetArch_Invalid}, + {"amd64", TargetArch_amd64}, + {"i386", TargetArch_i386}, + {"arm64", TargetArch_arm64}, + {"wasm32", TargetArch_wasm32}, + {"wasm64", TargetArch_wasm64}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Arch_Type"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_ARCH", bc->metrics.arch); + add_global_string_constant("ODIN_ARCH_STRING", target_arch_names[bc->metrics.arch]); + } - add_global_string_constant("ODIN_BUILD_MODE", bc->ODIN_BUILD_MODE); + { + GlobalEnumValue values[BuildMode_COUNT] = { + {"Executable", BuildMode_Executable}, + {"Dynamic", BuildMode_DynamicLibrary}, + {"Object", BuildMode_Object}, + {"Assembly", BuildMode_Assembly}, + {"LLVM_IR", BuildMode_LLVM_IR}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Build_Mode_Type"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_BUILD_MODE", bc->build_mode); + } + + { + GlobalEnumValue values[TargetEndian_COUNT] = { + {"Unknown", TargetEndian_Invalid}, + + {"Little", TargetEndian_Little}, + {"Big", TargetEndian_Big}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Endian_Type"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_ENDIAN", target_endians[bc->metrics.arch]); + add_global_string_constant("ODIN_ENDIAN_STRING", target_endian_names[target_endians[bc->metrics.arch]]); + } + + { + GlobalEnumValue values[ErrorPosStyle_COUNT] = { + {"Default", ErrorPosStyle_Default}, + {"Unix", ErrorPosStyle_Unix}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Error_Pos_Style_Type"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_ERROR_POS_STYLE", build_context.ODIN_ERROR_POS_STYLE); + } + + { + GlobalEnumValue values[OdinAtomicMemoryOrder_COUNT] = { + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_relaxed], OdinAtomicMemoryOrder_relaxed}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_consume], OdinAtomicMemoryOrder_consume}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_acquire], OdinAtomicMemoryOrder_acquire}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_release], OdinAtomicMemoryOrder_release}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_acq_rel], OdinAtomicMemoryOrder_acq_rel}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_seq_cst], OdinAtomicMemoryOrder_seq_cst}, + }; + + add_global_enum_type(str_lit("Atomic_Memory_Order"), values, gb_count_of(values), &t_atomic_memory_order); + GB_ASSERT(t_atomic_memory_order->kind == Type_Named); + scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name); + } + + add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); @@ -823,6 +1013,9 @@ void init_universal(void) { add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); + add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); + add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); + add_global_bool_constant("ODIN_DISALLOW_RTTI", bc->disallow_rtti); // Builtin Procedures @@ -887,6 +1080,17 @@ void init_universal(void) { t_f64_ptr = alloc_type_pointer(t_f64); t_u8_slice = alloc_type_slice(t_u8); t_string_slice = alloc_type_slice(t_string); + + // intrinsics types for objective-c stuff + { + t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct()); + t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct()); + t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct()); + + t_objc_id = alloc_type_pointer(t_objc_object); + t_objc_SEL = alloc_type_pointer(t_objc_selector); + t_objc_Class = alloc_type_pointer(t_objc_class); + } } @@ -941,6 +1145,11 @@ void init_checker_info(CheckerInfo *i) { mutex_init(&i->foreign_mutex); semaphore_init(&i->collect_semaphore); + + mpmc_init(&i->intrinsics_entry_point_usage, a, 1<<10); // just waste some memory here, even if it probably never used + + mutex_init(&i->objc_types_mutex); + map_init(&i->objc_msgSend_types, a); } void destroy_checker_info(CheckerInfo *i) { @@ -973,6 +1182,9 @@ void destroy_checker_info(CheckerInfo *i) { mutex_destroy(&i->type_and_value_mutex); mutex_destroy(&i->identifier_uses_mutex); mutex_destroy(&i->foreign_mutex); + + mutex_destroy(&i->objc_types_mutex); + map_destroy(&i->objc_msgSend_types); } CheckerContext make_checker_context(Checker *c) { @@ -1177,7 +1389,7 @@ isize type_info_index(CheckerInfo *info, Type *type, bool error_on_failure) { // TODO(bill): This is O(n) and can be very slow for_array(i, info->type_info_map.entries){ auto *e = &info->type_info_map.entries[i]; - if (are_types_identical(e->key, type)) { + if (are_types_identical_unique_tuples(e->key, type)) { entry_index = e->value; // NOTE(bill): Add it to the search map map_set(&info->type_info_map, type, entry_index); @@ -1228,7 +1440,7 @@ void add_type_and_value(CheckerInfo *i, Ast *expr, AddressingMode mode, Type *ty while (prev_expr != expr) { prev_expr = expr; expr->tav.mode = mode; - if (type != nullptr && expr->tav.type != nullptr && + if (type != nullptr && expr->tav.type != nullptr && is_type_any(type) && is_type_untyped(expr->tav.type)) { // ignore } else { @@ -1424,7 +1636,7 @@ bool could_entity_be_lazy(Entity *e, DeclInfo *d) { return false; } else if (name == "linkage") { return false; - } + } } } } @@ -1496,6 +1708,10 @@ void add_implicit_entity(CheckerContext *c, Ast *clause, Entity *e) { void add_type_info_type(CheckerContext *c, Type *t) { void add_type_info_type_internal(CheckerContext *c, Type *t); + if (build_context.disallow_rtti) { + return; + } + mutex_lock(&c->info->type_info_mutex); add_type_info_type_internal(c, t); mutex_unlock(&c->info->type_info_mutex); @@ -1513,7 +1729,7 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) { return; } - add_type_info_dependency(c->decl, t); + add_type_info_dependency(c->info, c->decl, t, false); auto found = map_get(&c->info->type_info_map, t); if (found != nullptr) { @@ -1525,7 +1741,7 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) { isize ti_index = -1; for_array(i, c->info->type_info_map.entries) { auto *e = &c->info->type_info_map.entries[i]; - if (are_types_identical(t, e->key)) { + if (are_types_identical_unique_tuples(t, e->key)) { // Duplicate entry ti_index = e->value; prev = true; @@ -1642,6 +1858,7 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) { } else { add_type_info_type_internal(c, t_type_info_ptr); } + add_type_info_type_internal(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_type_info_type_internal(c, bt->Union.variants[i]); } @@ -1665,6 +1882,7 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) { } } } + add_type_info_type_internal(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; add_type_info_type_internal(c, f->type); @@ -1704,7 +1922,7 @@ void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->RelativeSlice.slice_type); add_type_info_type_internal(c, bt->RelativeSlice.base_integer); break; - + case Type_Matrix: add_type_info_type_internal(c, bt->Matrix.elem); break; @@ -1858,6 +2076,7 @@ void add_min_dep_type_info(Checker *c, Type *t) { } else { add_min_dep_type_info(c, t_type_info_ptr); } + add_min_dep_type_info(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_min_dep_type_info(c, bt->Union.variants[i]); } @@ -1881,6 +2100,7 @@ void add_min_dep_type_info(Checker *c, Type *t) { } } } + add_min_dep_type_info(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; add_min_dep_type_info(c, f->type); @@ -1919,7 +2139,7 @@ void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, bt->RelativeSlice.slice_type); add_min_dep_type_info(c, bt->RelativeSlice.base_integer); break; - + case Type_Matrix: add_min_dep_type_info(c, bt->Matrix.elem); break; @@ -2004,23 +2224,27 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { ptr_set_init(&c->info.minimum_dependency_set, heap_allocator(), min_dep_set_cap); ptr_set_init(&c->info.minimum_dependency_type_info_set, heap_allocator()); - String required_runtime_entities[] = { +#define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do { \ + if (condition) { \ + String entities[] = {__VA_ARGS__}; \ + for (isize i = 0; i < gb_count_of(entities); i++) { \ + force_add_dependency_entity(c, c->info.runtime_package->scope, entities[i]); \ + } \ + } \ +} while (0) + + // required runtime entities + FORCE_ADD_RUNTIME_ENTITIES(true, // Odin types - str_lit("Type_Info"), str_lit("Source_Code_Location"), str_lit("Context"), str_lit("Allocator"), str_lit("Logger"), - // Global variables - str_lit("args__"), - str_lit("type_table"), - // Odin internal procedures str_lit("__init_context"), - str_lit("__type_info_of"), str_lit("cstring_to_string"), - str_lit("_cleanup_runtime"), + str_lit("_cleanup_runtime"), // Pseudo-CRT required procedures str_lit("memset"), @@ -2047,39 +2271,40 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { str_lit("gnu_h2f_ieee"), str_lit("gnu_f2h_ieee"), str_lit("extendhfsf2"), - + // WASM Specific str_lit("__ashlti3"), str_lit("__multi3"), - }; - for (isize i = 0; i < gb_count_of(required_runtime_entities); i++) { - force_add_dependency_entity(c, c->info.runtime_package->scope, required_runtime_entities[i]); - } + ); - if (build_context.no_crt) { - String required_no_crt_entities[] = { - // NOTE(bill): Only if these exist - str_lit("_tls_index"), - str_lit("_fltused"), - }; - for (isize i = 0; i < gb_count_of(required_no_crt_entities); i++) { - force_add_dependency_entity(c, c->info.runtime_package->scope, required_no_crt_entities[i]); - } - } + FORCE_ADD_RUNTIME_ENTITIES(!build_context.disallow_rtti, + // Odin types + str_lit("Type_Info"), - if (!build_context.no_bounds_check) { - String bounds_check_entities[] = { - // Bounds checking related procedures - str_lit("bounds_check_error"), - str_lit("matrix_bounds_check_error"), - str_lit("slice_expr_error_hi"), - str_lit("slice_expr_error_lo_hi"), - str_lit("multi_pointer_slice_expr_error"), - }; - for (isize i = 0; i < gb_count_of(bounds_check_entities); i++) { - force_add_dependency_entity(c, c->info.runtime_package->scope, bounds_check_entities[i]); - } - } + // Global variables + str_lit("type_table"), + str_lit("__type_info_of"), + ); + + FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_entry_point, + // Global variables + str_lit("args__"), + ); + + FORCE_ADD_RUNTIME_ENTITIES((build_context.no_crt && !is_arch_wasm()), + // NOTE(bill): Only if these exist + str_lit("_tls_index"), + str_lit("_fltused"), + ); + + FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_bounds_check, + // Bounds checking related procedures + str_lit("bounds_check_error"), + str_lit("matrix_bounds_check_error"), + str_lit("slice_expr_error_hi"), + str_lit("slice_expr_error_lo_hi"), + str_lit("multi_pointer_slice_expr_error"), + ); for_array(i, c->info.definitions) { Entity *e = c->info.definitions[i]; @@ -2110,34 +2335,38 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { case Entity_Variable: if (e->Variable.is_export) { add_dependency_to_set(c, e); + } else if (e->flags & EntityFlag_Require) { + add_dependency_to_set(c, e); } break; case Entity_Procedure: if (e->Procedure.is_export) { add_dependency_to_set(c, e); + } else if (e->flags & EntityFlag_Require) { + add_dependency_to_set(c, e); } if (e->flags & EntityFlag_Init) { Type *t = base_type(e->type); GB_ASSERT(t->kind == Type_Proc); - + bool is_init = true; - + if (t->Proc.param_count != 0 || t->Proc.result_count != 0) { gbString str = type_to_string(t); error(e->token, "@(init) procedures must have a signature type with no parameters nor results, got %s", str); gb_string_free(str); is_init = false; } - + if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) { error(e->token, "@(init) procedures must be declared at the file scope"); is_init = false; } - + if (is_init) { add_dependency_to_set(c, e); array_add(&c->info.init_procedures, e); - } + } } break; } @@ -2197,6 +2426,8 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { start->flags |= EntityFlag_Used; add_dependency_to_set(c, start); } + +#undef FORCE_ADD_RUNTIME_ENTITIES } bool is_entity_a_dependency(Entity *e) { @@ -2445,6 +2676,15 @@ Array<Entity *> proc_group_entities(CheckerContext *c, Operand o) { return procs; } +Array<Entity *> proc_group_entities_cloned(CheckerContext *c, Operand o) { + auto entities = proc_group_entities(c, o); + if (entities.count == 0) { + return {}; + } + return array_clone(permanent_allocator(), entities); +} + + void init_core_type_info(Checker *c) { @@ -2604,6 +2844,14 @@ ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) { return ev; } +Type *check_decl_attribute_type(CheckerContext *c, Ast *value) { + if (value != nullptr) { + return check_type(c, value); + } + return nullptr; +} + + #define ATTRIBUTE_USER_TAG_NAME "tag" @@ -2903,6 +3151,42 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string for '%.*s'", LIT(name)); } return true; + } else if (name == "objc_name") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + if (string_is_valid_identifier(ev.value_string)) { + ac->objc_name = ev.value_string; + } else { + error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_is_class_method") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_class_method = ev.value_bool; + } else { + error(elem, "Expected a boolean value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_type") { + if (value == nullptr) { + error(elem, "Expected a type for '%.*s'", LIT(name)); + } else { + Type *objc_type = check_type(c, value); + if (objc_type != nullptr) { + if (!has_type_got_objc_class_attribute(objc_type)) { + gbString t = type_to_string(objc_type); + error(value, "'%.*s' expected a named type with the attribute @(obj_class=<string>), got type %s", LIT(name), t); + gb_string_free(t); + } else { + ac->objc_type = objc_type; + } + } + } + return true; } return false; } @@ -3053,6 +3337,14 @@ DECL_ATTRIBUTE_PROC(type_decl_attribute) { } else if (name == "private") { // NOTE(bill): Handled elsewhere `check_collect_value_decl` return true; + } else if (name == "objc_class") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind != ExactValue_String || ev.value_string == "") { + error(elem, "Expected a non-empty string value for '%.*s'", LIT(name)); + } else { + ac->objc_class = ev.value_string; + } + return true; } return false; } @@ -3366,6 +3658,16 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) { } } + if (entity_visibility_kind == EntityVisiblity_Public && + (c->scope->flags&ScopeFlag_File) && + c->scope->file) { + if (c->scope->file->flags & AstFile_IsPrivateFile) { + entity_visibility_kind = EntityVisiblity_PrivateToFile; + } else if (c->scope->file->flags & AstFile_IsPrivatePkg) { + entity_visibility_kind = EntityVisiblity_PrivateToPackage; + } + } + if (entity_visibility_kind != EntityVisiblity_Public && !(c->scope->flags&ScopeFlag_File)) { error(decl, "Attribute 'private' is not allowed on a non file scope entity"); } @@ -3454,9 +3756,6 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) { if (is_ast_type(init)) { e = alloc_entity_type_name(d->scope, token, nullptr); - // if (vd->type != nullptr) { - // error(name, "A type declaration cannot have an type parameter"); - // } } else if (init->kind == Ast_ProcLit) { if (c->scope->flags&ScopeFlag_Type) { error(name, "Procedure declarations are not allowed within a struct"); @@ -3559,6 +3858,59 @@ void check_add_foreign_block_decl(CheckerContext *ctx, Ast *decl) { check_collect_entities(&c, block->stmts); } +bool correct_single_type_alias(CheckerContext *c, Entity *e) { + if (e->kind == Entity_Constant) { + DeclInfo *d = e->decl_info; + if (d != nullptr && d->init_expr != nullptr) { + Ast *init = d->init_expr; + Entity *alias_of = check_entity_from_ident_or_selector(c, init, true); + if (alias_of != nullptr && alias_of->kind == Entity_TypeName) { + e->kind = Entity_TypeName; + return true; + } + } + } + return false; +} + +bool correct_type_alias_in_scope_backwards(CheckerContext *c, Scope *s) { + isize n = s->elements.entries.count; + bool correction = false; + for (isize i = n-1; i >= 0; i--) { + correction |= correct_single_type_alias(c, s->elements.entries[i].value); + } + return correction; +} +bool correct_type_alias_in_scope_forwards(CheckerContext *c, Scope *s) { + isize n = s->elements.entries.count; + bool correction = false; + for (isize i = 0; i < n; i++) { + correction |= correct_single_type_alias(c, s->elements.entries[i].value); + } + return correction; +} + + +void correct_type_aliases_in_scope(CheckerContext *c, Scope *s) { + // NOTE(bill, 2022-02-04): This is used to solve the problem caused by type aliases + // of type aliases being "confused" as constants + // + // A :: C + // B :: A + // C :: struct {b: ^B} + // + // See @TypeAliasingProblem for more information + for (;;) { + bool corrections = false; + corrections |= correct_type_alias_in_scope_backwards(c, s); + corrections |= correct_type_alias_in_scope_forwards(c, s); + if (!corrections) { + return; + } + } +} + + // NOTE(bill): If file_scopes == nullptr, this will act like a local scope void check_collect_entities(CheckerContext *c, Slice<Ast *> const &nodes) { AstFile *curr_file = nullptr; @@ -3630,6 +3982,7 @@ void check_collect_entities(CheckerContext *c, Slice<Ast *> const &nodes) { } } + // correct_type_aliases(c); // NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something // declared after this stmt in source @@ -3677,11 +4030,6 @@ void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) { error(e->token, "'main' is reserved as the entry point procedure in the initial scope"); return; } - } else if (pkg->kind == Package_Runtime) { - if (e->token.string == "main") { - error(e->token, "'main' is reserved as the entry point procedure in the initial scope"); - return; - } } check_entity_decl(ctx, e, d, nullptr); @@ -3841,7 +4189,7 @@ void add_import_dependency_node(Checker *c, Ast *decl, PtrMap<AstPackage *, Impo } Array<ImportGraphNode *> generate_import_dependency_graph(Checker *c) { - PtrMap<AstPackage *, ImportGraphNode *> M = {}; + PtrMap<AstPackage *, ImportGraphNode *> M = {}; map_init(&M, heap_allocator(), 2*c->parser->packages.count); defer (map_destroy(&M)); @@ -4121,11 +4469,11 @@ void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { mpmc_enqueue(&ctx->info->required_foreign_imports_through_force_queue, e); add_entity_use(ctx, nullptr, e); } - + if (has_asm_extension(fullpath)) { if (build_context.metrics.arch != TargetArch_amd64 || build_context.metrics.os != TargetOs_windows) { - error(decl, "Assembly files are not yet supported on this platform: %.*s_%.*s", + error(decl, "Assembly files are not yet supported on this platform: %.*s_%.*s", LIT(target_os_names[build_context.metrics.os]), LIT(target_arch_names[build_context.metrics.arch])); } } @@ -4280,10 +4628,11 @@ bool collect_file_decls(CheckerContext *ctx, Slice<Ast *> const &decls) { for_array(i, decls) { if (collect_file_decl(ctx, decls[i])) { + correct_type_aliases_in_scope(ctx, ctx->scope); return true; } } - + correct_type_aliases_in_scope(ctx, ctx->scope); return false; } @@ -4327,7 +4676,7 @@ void check_with_workers(Checker *c, WorkerTaskProc *proc, isize total_count) { if (!build_context.threaded_checker) { worker_count = 0; } - + semaphore_post(&c->info.collect_semaphore, cast(i32)thread_count); if (worker_count == 0) { ThreadProcCheckerSection section_all = {}; @@ -4351,7 +4700,7 @@ void check_with_workers(Checker *c, WorkerTaskProc *proc, isize total_count) { } GB_ASSERT(remaining_count <= 0); - + for (isize i = 0; i < thread_count; i++) { global_thread_pool_add_task(proc, thread_data+i); } @@ -4553,6 +4902,15 @@ void check_import_entities(Checker *c) { } add_untyped_expressions(ctx.info, &untyped); } + + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + reset_checker_context(&ctx, f, &untyped); + ctx.collect_delayed_decls = false; + + correct_type_aliases_in_scope(&ctx, pkg->scope); + } + for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); @@ -4750,11 +5108,11 @@ bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped, Proc ctx.decl = pi->decl; ctx.procs_to_check_queue = procs_to_check_queue; GB_ASSERT(procs_to_check_queue != nullptr); - + GB_ASSERT(pi->type->kind == Type_Proc); TypeProc *pt = &pi->type->Proc; String name = pi->token.string; - + if (pt->is_polymorphic && !pt->is_poly_specialized) { Token token = pi->token; if (pi->poly_def_node != nullptr) { @@ -4774,6 +5132,9 @@ bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped, Proc bool bounds_check = (pi->tags & ProcTag_bounds_check) != 0; bool no_bounds_check = (pi->tags & ProcTag_no_bounds_check) != 0; + bool type_assert = (pi->tags & ProcTag_type_assert) != 0; + bool no_type_assert = (pi->tags & ProcTag_no_type_assert) != 0; + if (bounds_check) { ctx.state_flags |= StateFlag_bounds_check; ctx.state_flags &= ~StateFlag_no_bounds_check; @@ -4781,6 +5142,15 @@ bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped, Proc ctx.state_flags |= StateFlag_no_bounds_check; ctx.state_flags &= ~StateFlag_bounds_check; } + + if (type_assert) { + ctx.state_flags |= StateFlag_type_assert; + ctx.state_flags &= ~StateFlag_no_type_assert; + } else if (no_type_assert) { + ctx.state_flags |= StateFlag_no_type_assert; + ctx.state_flags &= ~StateFlag_type_assert; + } + if (pi->body != nullptr && e != nullptr) { GB_ASSERT((e->flags & EntityFlag_ProcBodyChecked) == 0); } @@ -4832,7 +5202,7 @@ void check_unchecked_bodies(Checker *c) { if (pi->body == nullptr) { continue; } - + debugf("unchecked: %.*s\n", LIT(e->token.string)); mpmc_enqueue(&c->procs_to_check_queue, pi); } @@ -4941,17 +5311,16 @@ void check_procedure_bodies(Checker *c) { if (!build_context.threaded_checker) { worker_count = 0; } - worker_count = 0; if (worker_count == 0) { auto *this_queue = &c->procs_to_check_queue; - + UntypedExprInfoMap untyped = {}; map_init(&untyped, heap_allocator()); - + for (ProcInfo *pi = nullptr; mpmc_dequeue(this_queue, &pi); /**/) { consume_proc_info_queue(c, pi, this_queue, &untyped); } - + map_destroy(&untyped); debugf("Total Procedure Bodies Checked: %td\n", total_bodies_checked.load(std::memory_order_relaxed)); @@ -4995,7 +5364,7 @@ void check_procedure_bodies(Checker *c) { GB_ASSERT(total_queued == original_queue_count); semaphore_post(&c->procs_to_check_semaphore, cast(i32)thread_count); - + for (isize i = 0; i < thread_count; i++) { global_thread_pool_add_task(thread_proc_body, thread_data+i); } @@ -5032,7 +5401,7 @@ void check_deferred_procedures(Checker *c) { Entity *dst = src->Procedure.deferred_procedure.entity; GB_ASSERT(dst != nullptr); GB_ASSERT(dst->kind == Entity_Procedure); - + char const *attribute = "deferred_none"; switch (dst_kind) { case DeferredProcedure_none: @@ -5195,12 +5564,18 @@ void check_unique_package_names(Checker *c) { string_map_set(&pkgs, key, pkg); continue; } + auto *curr = pkg->files[0]->pkg_decl; + auto *prev = (*found)->files[0]->pkg_decl; + if (curr == prev) { + // NOTE(bill): A false positive was found, ignore it + continue; + } - error(pkg->files[0]->pkg_decl, "Duplicate declaration of 'package %.*s'", LIT(name)); + error(curr, "Duplicate declaration of 'package %.*s'", LIT(name)); error_line("\tA package name must be unique\n" "\tThere is no relation between a package name and the directory that contains it, so they can be completely different\n" "\tA package name is required for link name prefixing to have a consistent ABI\n"); - error((*found)->files[0]->pkg_decl, "found at previous location"); + error(prev, "found at previous location"); } } @@ -5233,7 +5608,7 @@ GB_COMPARE_PROC(init_procedures_cmp) { cmp = 0; return cmp; } - + if (x->pkg != y->pkg) { isize order_x = x->pkg ? x->pkg->order : 0; isize order_y = y->pkg ? y->pkg->order : 0; @@ -5247,14 +5622,14 @@ GB_COMPARE_PROC(init_procedures_cmp) { String fullpath_y = y->file ? y->file->fullpath : (String{}); String file_x = filename_from_path(fullpath_x); String file_y = filename_from_path(fullpath_y); - + cmp = string_compare(file_x, file_y); if (cmp) { return cmp; } } - + cmp = u64_cmp(x->order_in_src, y->order_in_src); if (cmp) { return cmp; @@ -5392,9 +5767,6 @@ void check_parsed_files(Checker *c) { TIME_SECTION("calculate global init order"); calculate_global_init_order(c); - TIME_SECTION("generate minimum dependency set"); - generate_minimum_dependency_set(c, c->info.entry_point); - TIME_SECTION("check test procedures"); check_test_procedures(c); @@ -5405,6 +5777,9 @@ void check_parsed_files(Checker *c) { add_type_info_for_type_definitions(c); check_merge_queues_into_arrays(c); + TIME_SECTION("generate minimum dependency set"); + generate_minimum_dependency_set(c, c->info.entry_point); + TIME_SECTION("check entry point"); if (build_context.build_mode == BuildMode_Executable && !build_context.no_entry_point && build_context.command_kind != Command_test) { Scope *s = c->info.init_scope; @@ -5434,9 +5809,21 @@ void check_parsed_files(Checker *c) { TIME_SECTION("sanity checks"); GB_ASSERT(c->info.entity_queue.count.load(std::memory_order_relaxed) == 0); GB_ASSERT(c->info.definition_queue.count.load(std::memory_order_relaxed) == 0); - + TIME_SECTION("sort init procedures"); check_sort_init_procedures(c); + if (c->info.intrinsics_entry_point_usage.count > 0) { + TIME_SECTION("check intrinsics.__entry_point usage"); + Ast *node = nullptr; + while (mpmc_dequeue(&c->info.intrinsics_entry_point_usage, &node)) { + if (c->info.entry_point == nullptr && node != nullptr) { + if (node->file()->pkg->kind != Package_Runtime) { + warning(node, "usage of intrinsics.__entry_point will be a no-op"); + } + } + } + } + TIME_SECTION("type check finish"); } diff --git a/src/checker.hpp b/src/checker.hpp index 74435c1d4..552e6aca7 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -118,6 +118,11 @@ struct AttributeContext { bool init : 1; bool set_cold : 1; u32 optimization_mode; // ProcedureOptimizationMode + + String objc_class; + String objc_name; + bool objc_is_class_method; + Type * objc_type; }; AttributeContext make_attribute_context(String link_prefix) { @@ -267,6 +272,17 @@ struct UntypedExprInfo { typedef PtrMap<Ast *, ExprInfo *> UntypedExprInfoMap; typedef MPMCQueue<ProcInfo *> ProcBodyQueue; +enum ObjcMsgKind : u32 { + ObjcMsg_normal, + ObjcMsg_fpret, + ObjcMsg_fp2ret, + ObjcMsg_stret, +}; +struct ObjcMsgData { + ObjcMsgKind kind; + Type *proc_type; +}; + // CheckerInfo stores all the symbol information for a type-checked program struct CheckerInfo { Checker *checker; @@ -338,6 +354,10 @@ struct CheckerInfo { MPMCQueue<Entity *> required_global_variable_queue; MPMCQueue<Entity *> required_foreign_imports_through_force_queue; + MPMCQueue<Ast *> intrinsics_entry_point_usage; + + BlockingMutex objc_types_mutex; + PtrMap<Ast *, ObjcMsgData> objc_msgSend_types; }; struct CheckerContext { @@ -423,7 +443,7 @@ Entity *entity_of_node(Ast *expr); Entity *scope_lookup_current(Scope *s, String const &name); Entity *scope_lookup (Scope *s, String const &name); void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_); -Entity *scope_insert (Scope *s, Entity *entity); +Entity *scope_insert (Scope *s, Entity *entity, bool use_mutex=true); void add_type_and_value (CheckerInfo *i, Ast *expression, AddressingMode mode, Type *type, ExactValue value); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index abd9fc6ca..0f72f01f7 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -86,77 +86,31 @@ enum BuiltinProcId { BuiltinProc_prefetch_write_instruction, BuiltinProc_prefetch_write_data, - BuiltinProc_atomic_fence, - BuiltinProc_atomic_fence_acq, - BuiltinProc_atomic_fence_rel, - BuiltinProc_atomic_fence_acqrel, - + BuiltinProc_atomic_type_is_lock_free, + BuiltinProc_atomic_thread_fence, + BuiltinProc_atomic_signal_fence, BuiltinProc_atomic_store, - BuiltinProc_atomic_store_rel, - BuiltinProc_atomic_store_relaxed, - BuiltinProc_atomic_store_unordered, - + BuiltinProc_atomic_store_explicit, BuiltinProc_atomic_load, - BuiltinProc_atomic_load_acq, - BuiltinProc_atomic_load_relaxed, - BuiltinProc_atomic_load_unordered, - + BuiltinProc_atomic_load_explicit, BuiltinProc_atomic_add, - BuiltinProc_atomic_add_acq, - BuiltinProc_atomic_add_rel, - BuiltinProc_atomic_add_acqrel, - BuiltinProc_atomic_add_relaxed, + BuiltinProc_atomic_add_explicit, BuiltinProc_atomic_sub, - BuiltinProc_atomic_sub_acq, - BuiltinProc_atomic_sub_rel, - BuiltinProc_atomic_sub_acqrel, - BuiltinProc_atomic_sub_relaxed, + BuiltinProc_atomic_sub_explicit, BuiltinProc_atomic_and, - BuiltinProc_atomic_and_acq, - BuiltinProc_atomic_and_rel, - BuiltinProc_atomic_and_acqrel, - BuiltinProc_atomic_and_relaxed, + BuiltinProc_atomic_and_explicit, BuiltinProc_atomic_nand, - BuiltinProc_atomic_nand_acq, - BuiltinProc_atomic_nand_rel, - BuiltinProc_atomic_nand_acqrel, - BuiltinProc_atomic_nand_relaxed, + BuiltinProc_atomic_nand_explicit, BuiltinProc_atomic_or, - BuiltinProc_atomic_or_acq, - BuiltinProc_atomic_or_rel, - BuiltinProc_atomic_or_acqrel, - BuiltinProc_atomic_or_relaxed, + BuiltinProc_atomic_or_explicit, BuiltinProc_atomic_xor, - BuiltinProc_atomic_xor_acq, - BuiltinProc_atomic_xor_rel, - BuiltinProc_atomic_xor_acqrel, - BuiltinProc_atomic_xor_relaxed, - - BuiltinProc_atomic_xchg, - BuiltinProc_atomic_xchg_acq, - BuiltinProc_atomic_xchg_rel, - BuiltinProc_atomic_xchg_acqrel, - BuiltinProc_atomic_xchg_relaxed, - - BuiltinProc_atomic_cxchg, - BuiltinProc_atomic_cxchg_acq, - BuiltinProc_atomic_cxchg_rel, - BuiltinProc_atomic_cxchg_acqrel, - BuiltinProc_atomic_cxchg_relaxed, - BuiltinProc_atomic_cxchg_failrelaxed, - BuiltinProc_atomic_cxchg_failacq, - BuiltinProc_atomic_cxchg_acq_failrelaxed, - BuiltinProc_atomic_cxchg_acqrel_failrelaxed, - - BuiltinProc_atomic_cxchgweak, - BuiltinProc_atomic_cxchgweak_acq, - BuiltinProc_atomic_cxchgweak_rel, - BuiltinProc_atomic_cxchgweak_acqrel, - BuiltinProc_atomic_cxchgweak_relaxed, - BuiltinProc_atomic_cxchgweak_failrelaxed, - BuiltinProc_atomic_cxchgweak_failacq, - BuiltinProc_atomic_cxchgweak_acq_failrelaxed, - BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed, + BuiltinProc_atomic_xor_explicit, + BuiltinProc_atomic_exchange, + BuiltinProc_atomic_exchange_explicit, + BuiltinProc_atomic_compare_exchange_strong, + BuiltinProc_atomic_compare_exchange_strong_explicit, + BuiltinProc_atomic_compare_exchange_weak, + BuiltinProc_atomic_compare_exchange_weak_explicit, BuiltinProc_fixed_point_mul, BuiltinProc_fixed_point_div, @@ -213,8 +167,6 @@ BuiltinProc__type_simple_boolean_begin, BuiltinProc_type_is_union, BuiltinProc_type_is_enum, BuiltinProc_type_is_proc, - BuiltinProc_type_is_bit_field, - BuiltinProc_type_is_bit_field_value, BuiltinProc_type_is_bit_set, BuiltinProc_type_is_simd_vector, BuiltinProc_type_is_matrix, @@ -227,6 +179,7 @@ BuiltinProc__type_simple_boolean_begin, BuiltinProc__type_simple_boolean_end, BuiltinProc_type_has_field, + BuiltinProc_type_field_type, BuiltinProc_type_is_specialization_of, @@ -243,6 +196,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_polymorphic_record_parameter_count, BuiltinProc_type_polymorphic_record_parameter_value, + BuiltinProc_type_is_subtype_of, + BuiltinProc_type_field_index_of, BuiltinProc_type_equal_proc, @@ -250,6 +205,19 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc__type_end, + BuiltinProc___entry_point, + + BuiltinProc_objc_send, + BuiltinProc_objc_find_selector, + BuiltinProc_objc_find_class, + BuiltinProc_objc_register_selector, + BuiltinProc_objc_register_class, + + BuiltinProc_constant_utf16_cstring, + + BuiltinProc_wasm_memory_grow, + BuiltinProc_wasm_memory_size, + BuiltinProc_COUNT, }; gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { @@ -339,78 +307,31 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("prefetch_write_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_write_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_acq"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_rel"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_acqrel"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_rel"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_relaxed"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_unordered"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_acq"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_relaxed"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_unordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_xchg"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_cxchg"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_rel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acqrel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_relaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_failacq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acq_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acqrel_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_cxchgweak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_rel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acqrel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_relaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_failacq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acq_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acqrel_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - + {STR_LIT("atomic_type_is_lock_free"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_thread_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_store_explicit"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_load_explicit"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_add_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_sub_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_and_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_nand_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_or_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_xor_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_exchange"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_exchange_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_strong"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -464,8 +385,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_is_union"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_enum"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("type_is_bit_field"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("type_is_bit_field_value"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_bit_set"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_simd_vector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_matrix"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -477,6 +396,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("type_has_field"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_field_type"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -493,10 +413,27 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_polymorphic_record_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_polymorphic_record_parameter_value"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_subtype_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_field_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_equal_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_hasher_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - + + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + + {STR_LIT("__entry_point"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + + {STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("objc_find_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("wasm_memory_grow"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_size"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, }; diff --git a/src/common.cpp b/src/common.cpp index ab2a46118..94248fb62 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -675,262 +675,7 @@ wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { #endif - -#if defined(GB_SYSTEM_WINDOWS) - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - String16 wstr = string_to_string16(a, path); - defer (gb_free(a, wstr.text)); - - i32 attribs = GetFileAttributesW(wstr.text); - if (attribs < 0) return false; - - return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - -#else - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - char *copy = cast(char *)copy_string(a, path).text; - defer (gb_free(a, copy)); - - struct stat s; - if (stat(copy, &s) == 0) { - return (s.st_mode & S_IFDIR) != 0; - } - return false; - } -#endif - - -String path_to_full_path(gbAllocator a, String path) { - gbAllocator ha = heap_allocator(); - char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); - defer (gb_free(ha, path_c)); - - char *fullpath = gb_path_get_full_name(a, path_c); - String res = string_trim_whitespace(make_string_c(fullpath)); -#if defined(GB_SYSTEM_WINDOWS) - for (isize i = 0; i < res.len; i++) { - if (res.text[i] == '\\') { - res.text[i] = '/'; - } - } -#endif - return res; -} - - - -struct FileInfo { - String name; - String fullpath; - i64 size; - bool is_dir; -}; - -enum ReadDirectoryError { - ReadDirectory_None, - - ReadDirectory_InvalidPath, - ReadDirectory_NotExists, - ReadDirectory_Permission, - ReadDirectory_NotDir, - ReadDirectory_Empty, - ReadDirectory_Unknown, - - ReadDirectory_COUNT, -}; - -i64 get_file_size(String path) { - char *c_str = alloc_cstring(heap_allocator(), path); - defer (gb_free(heap_allocator(), c_str)); - - gbFile f = {}; - gbFileError err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - if (err != gbFileError_None) { - return -1; - } - return gb_file_size(&f); -} - - -#if defined(GB_SYSTEM_WINDOWS) -ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - while (path.len > 0) { - Rune end = path[path.len-1]; - if (end == '/') { - path.len -= 1; - } else if (end == '\\') { - path.len -= 1; - } else { - break; - } - } - - if (path.len == 0) { - return ReadDirectory_InvalidPath; - } - { - char *c_str = alloc_cstring(a, path); - defer (gb_free(a, c_str)); - - gbFile f = {}; - gbFileError file_err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - - switch (file_err) { - case gbFileError_Invalid: return ReadDirectory_InvalidPath; - case gbFileError_NotExists: return ReadDirectory_NotExists; - // case gbFileError_Permission: return ReadDirectory_Permission; - } - } - - if (!path_is_directory(path)) { - return ReadDirectory_NotDir; - } - - - char *new_path = gb_alloc_array(a, char, path.len+3); - defer (gb_free(a, new_path)); - - gb_memmove(new_path, path.text, path.len); - gb_memmove(new_path+path.len, "/*", 2); - new_path[path.len+2] = 0; - - String np = make_string(cast(u8 *)new_path, path.len+2); - String16 wstr = string_to_string16(a, np); - defer (gb_free(a, wstr.text)); - - WIN32_FIND_DATAW file_data = {}; - HANDLE find_file = FindFirstFileW(wstr.text, &file_data); - if (find_file == INVALID_HANDLE_VALUE) { - return ReadDirectory_Unknown; - } - defer (FindClose(find_file)); - - array_init(fi, a, 0, 100); - - do { - wchar_t *filename_w = file_data.cFileName; - i64 size = cast(i64)file_data.nFileSizeLow; - size |= (cast(i64)file_data.nFileSizeHigh) << 32; - String name = string16_to_string(a, make_string16_c(filename_w)); - if (name == "." || name == "..") { - gb_free(a, name.text); - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - array_add(fi, info); - } while (FindNextFileW(find_file, &file_data)); - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) - -#include <dirent.h> - -ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - char *c_path = alloc_cstring(a, path); - defer (gb_free(a, c_path)); - - DIR *dir = opendir(c_path); - if (!dir) { - switch (errno) { - case ENOENT: - return ReadDirectory_NotExists; - case EACCES: - return ReadDirectory_Permission; - case ENOTDIR: - return ReadDirectory_NotDir; - default: - // ENOMEM: out of memory - // EMFILE: per-process limit on open fds reached - // ENFILE: system-wide limit on total open files reached - return ReadDirectory_Unknown; - } - GB_PANIC("unreachable"); - } - - array_init(fi, a, 0, 100); - - for (;;) { - struct dirent *entry = readdir(dir); - if (entry == nullptr) { - break; - } - - String name = make_string_c(entry->d_name); - if (name == "." || name == "..") { - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - filepath.text[filepath.len] = 0; - - - struct stat dir_stat = {}; - - if (stat((char *)filepath.text, &dir_stat)) { - continue; - } - - if (S_ISDIR(dir_stat.st_mode)) { - continue; - } - - i64 size = dir_stat.st_size; - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - array_add(fi, info); - } - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#else -#error Implement read_directory -#endif - - +#include "path.cpp" struct LoadedFile { void *handle; @@ -1021,7 +766,7 @@ LoadedFileError load_file_32(char const *fullpath, LoadedFile *memory_mapped_fil #endif } - gbFileContents fc = gb_file_read_contents(heap_allocator(), true, fullpath); + gbFileContents fc = gb_file_read_contents(permanent_allocator(), true, fullpath); if (fc.size > I32_MAX) { err = LoadedFile_FileTooLarge; diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 2d7a7a246..953462077 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -139,6 +139,7 @@ struct PlatformMemoryBlock { }; +gb_global std::atomic<isize> global_platform_memory_total_usage; gb_global PlatformMemoryBlock global_platform_memory_block_sentinel; PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size); @@ -158,10 +159,17 @@ void platform_virtual_memory_protect(void *memory, isize size); PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size) { PlatformMemoryBlock *pmblock = (PlatformMemoryBlock *)VirtualAlloc(0, total_size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); - GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no..."); + if (pmblock == nullptr) { + gb_printf_err("Out of Virtual memory, oh no...\n"); + gb_printf_err("Requested: %lld bytes\n", cast(long long)total_size); + gb_printf_err("Total Usage: %lld bytes\n", cast(long long)global_platform_memory_total_usage); + GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no..."); + } + global_platform_memory_total_usage += total_size; return pmblock; } void platform_virtual_memory_free(PlatformMemoryBlock *block) { + global_platform_memory_total_usage -= block->total_size; GB_ASSERT(VirtualFree(block, 0, MEM_RELEASE)); } void platform_virtual_memory_protect(void *memory, isize size) { @@ -180,11 +188,18 @@ void platform_virtual_memory_protect(void *memory, isize size); PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size) { PlatformMemoryBlock *pmblock = (PlatformMemoryBlock *)mmap(nullptr, total_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no..."); + if (pmblock == nullptr) { + gb_printf_err("Out of Virtual memory, oh no...\n"); + gb_printf_err("Requested: %lld bytes\n", cast(long long)total_size); + gb_printf_err("Total Usage: %lld bytes\n", cast(long long)global_platform_memory_total_usage); + GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no..."); + } + global_platform_memory_total_usage += total_size; return pmblock; } void platform_virtual_memory_free(PlatformMemoryBlock *block) { isize size = block->total_size; + global_platform_memory_total_usage -= size; munmap(block, size); } void platform_virtual_memory_protect(void *memory, isize size) { @@ -325,18 +340,32 @@ GB_ALLOCATOR_PROC(heap_allocator_proc) { // TODO(bill): Throughly test! switch (type) { #if defined(GB_COMPILER_MSVC) - case gbAllocation_Alloc: { - isize aligned_size = align_formula_isize(size, alignment); - // TODO(bill): Make sure this is aligned correctly - ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, aligned_size); - } break; + case gbAllocation_Alloc: + if (size == 0) { + return NULL; + } else { + isize aligned_size = align_formula_isize(size, alignment); + // TODO(bill): Make sure this is aligned correctly + ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, aligned_size); + } + break; case gbAllocation_Free: - HeapFree(GetProcessHeap(), 0, old_memory); + if (old_memory != nullptr) { + HeapFree(GetProcessHeap(), 0, old_memory); + } + break; + case gbAllocation_Resize: + if (old_memory != nullptr && size > 0) { + isize aligned_size = align_formula_isize(size, alignment); + ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, old_memory, aligned_size); + } else if (old_memory != nullptr) { + HeapFree(GetProcessHeap(), 0, old_memory); + } else if (size != 0) { + isize aligned_size = align_formula_isize(size, alignment); + // TODO(bill): Make sure this is aligned correctly + ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, aligned_size); + } break; - case gbAllocation_Resize: { - isize aligned_size = align_formula_isize(size, alignment); - ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, old_memory, aligned_size); - } break; #elif defined(GB_SYSTEM_LINUX) // TODO(bill): *nix version that's decent case gbAllocation_Alloc: { diff --git a/src/docs.cpp b/src/docs.cpp index 8d65cb83a..3ea3cce1b 100644 --- a/src/docs.cpp +++ b/src/docs.cpp @@ -67,6 +67,14 @@ GB_COMPARE_PROC(cmp_ast_package_by_name) { #include "docs_format.cpp" #include "docs_writer.cpp" +void print_doc_line(i32 indent, String const &data) { + while (indent --> 0) { + gb_printf("\t"); + } + gb_file_write(gb_file_get_standard(gbFileStandard_Output), data.text, data.len); + gb_printf("\n"); +} + void print_doc_line(i32 indent, char const *fmt, ...) { while (indent --> 0) { gb_printf("\t"); @@ -86,6 +94,13 @@ void print_doc_line_no_newline(i32 indent, char const *fmt, ...) { gb_printf_va(fmt, va); va_end(va); } +void print_doc_line_no_newline(i32 indent, String const &data) { + while (indent --> 0) { + gb_printf("\t"); + } + gb_file_write(gb_file_get_standard(gbFileStandard_Output), data.text, data.len); +} + bool print_doc_comment_group_string(i32 indent, CommentGroup *g) { if (g == nullptr) { @@ -106,8 +121,9 @@ bool print_doc_comment_group_string(i32 indent, CommentGroup *g) { String comment = g->list[i].string; String original_comment = comment; - bool slash_slash = comment[1] == '/'; + bool slash_slash = false; if (comment[1] == '/') { + slash_slash = true; comment.text += 2; comment.len -= 2; } else if (comment[1] == '*') { @@ -131,7 +147,7 @@ bool print_doc_comment_group_string(i32 indent, CommentGroup *g) { } if (slash_slash) { - print_doc_line(indent, "%.*s", LIT(comment)); + print_doc_line(indent, comment); count += 1; } else { isize pos = 0; @@ -143,7 +159,7 @@ bool print_doc_comment_group_string(i32 indent, CommentGroup *g) { } } String line = substring(comment, pos, end); - pos = end+1; + pos = end; String trimmed_line = string_trim_whitespace(line); if (trimmed_line.len == 0) { if (count == 0) { @@ -159,7 +175,7 @@ bool print_doc_comment_group_string(i32 indent, CommentGroup *g) { line = substring(line, 2, line.len); } - print_doc_line(indent, "%.*s", LIT(line)); + print_doc_line(indent, line); count += 1; } } @@ -263,7 +279,7 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) { } GB_ASSERT(type_expr != nullptr || init_expr != nullptr); - print_doc_line_no_newline(2, "%.*s", LIT(e->token.string)); + print_doc_line_no_newline(2, e->token.string); if (type_expr != nullptr) { gbString t = expr_to_string(type_expr); gb_printf(": %s ", t); @@ -298,7 +314,7 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) { for_array(i, pkg->files) { AstFile *f = pkg->files[i]; String filename = remove_directory_from_path(f->fullpath); - print_doc_line(2, "%.*s", LIT(filename)); + print_doc_line(2, filename); } } diff --git a/src/docs_format.cpp b/src/docs_format.cpp index 1c3af6257..ee32d0e05 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -15,7 +15,7 @@ struct OdinDocVersionType { #define OdinDocVersionType_Major 0 #define OdinDocVersionType_Minor 2 -#define OdinDocVersionType_Patch 1 +#define OdinDocVersionType_Patch 4 struct OdinDocHeaderBase { u8 magic[8]; @@ -99,6 +99,7 @@ enum OdinDocTypeFlag_Union : u32 { OdinDocTypeFlag_Union_polymorphic = 1<<0, OdinDocTypeFlag_Union_no_nil = 1<<1, OdinDocTypeFlag_Union_maybe = 1<<2, + OdinDocTypeFlag_Union_shared_nil = 1<<3, }; enum OdinDocTypeFlag_Proc : u32 { @@ -137,6 +138,7 @@ struct OdinDocType { OdinDocArray<OdinDocEntityIndex> entities; OdinDocTypeIndex polmorphic_params; OdinDocArray<OdinDocString> where_clauses; + OdinDocArray<OdinDocString> tags; // struct field tags }; struct OdinDocAttribute { @@ -153,6 +155,7 @@ enum OdinDocEntityKind : u32 { OdinDocEntity_ProcGroup = 5, OdinDocEntity_ImportName = 6, OdinDocEntity_LibraryName = 7, + OdinDocEntity_Builtin = 8, }; enum OdinDocEntityFlag : u64 { @@ -169,8 +172,13 @@ enum OdinDocEntityFlag : u64 { OdinDocEntityFlag_Type_Alias = 1ull<<20, + OdinDocEntityFlag_Builtin_Pkg_Builtin = 1ull<<30, + OdinDocEntityFlag_Builtin_Pkg_Intrinsics = 1ull<<31, + OdinDocEntityFlag_Var_Thread_Local = 1ull<<40, OdinDocEntityFlag_Var_Static = 1ull<<41, + + OdinDocEntityFlag_Private = 1ull<<50, }; struct OdinDocEntity { @@ -182,8 +190,9 @@ struct OdinDocEntity { OdinDocTypeIndex type; OdinDocString init_string; u32 reserved_for_init; - OdinDocString comment; - OdinDocString docs; + OdinDocString comment; // line comment + OdinDocString docs; // preceding comment + i32 field_group_index; OdinDocEntityIndex foreign_library; OdinDocString link_name; OdinDocArray<OdinDocAttribute> attributes; @@ -197,15 +206,21 @@ enum OdinDocPkgFlags : u32 { OdinDocPkgFlag_Init = 1<<2, }; +struct OdinDocScopeEntry { + OdinDocString name; + OdinDocEntityIndex entity; +}; + struct OdinDocPkg { OdinDocString fullpath; OdinDocString name; u32 flags; OdinDocString docs; - OdinDocArray<OdinDocFileIndex> files; - OdinDocArray<OdinDocEntityIndex> entities; + OdinDocArray<OdinDocFileIndex> files; + OdinDocArray<OdinDocScopeEntry> entries; }; + struct OdinDocHeader { OdinDocHeaderBase base; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index e8e8892ec..0ad10ac49 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -292,8 +292,9 @@ bool odin_doc_append_comment_group_string(Array<u8> *buf, CommentGroup *g) { String comment = g->list[i].string; String original_comment = comment; - bool slash_slash = comment[1] == '/'; + bool slash_slash = false; if (comment[1] == '/') { + slash_slash = true; comment.text += 2; comment.len -= 2; } else if (comment[1] == '*') { @@ -330,7 +331,7 @@ bool odin_doc_append_comment_group_string(Array<u8> *buf, CommentGroup *g) { } } String line = substring(comment, pos, end); - pos = end+1; + pos = end; String trimmed_line = string_trim_whitespace(line); if (trimmed_line.len == 0) { if (count == 0) { @@ -482,7 +483,7 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { for_array(i, w->type_cache.entries) { // NOTE(bill): THIS IS SLOW Type *other = w->type_cache.entries[i].key; - if (are_types_identical(type, other)) { + if (are_types_identical_unique_tuples(type, other)) { OdinDocTypeIndex index = w->type_cache.entries[i].value; map_set(&w->type_cache, type, index); return index; @@ -511,10 +512,16 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.entities = odin_doc_add_entity_as_slice(w, type->Named.type_name); break; case Type_Generic: - doc_type.kind = OdinDocType_Generic; - doc_type.name = odin_doc_write_string(w, type->Generic.name); - if (type->Generic.specialized) { - doc_type.types = odin_doc_type_as_slice(w, type->Generic.specialized); + { + String name = type->Generic.name; + if (type->Generic.entity) { + name = type->Generic.entity->token.string; + } + doc_type.kind = OdinDocType_Generic; + doc_type.name = odin_doc_write_string(w, name); + if (type->Generic.specialized) { + doc_type.types = odin_doc_type_as_slice(w, type->Generic.specialized); + } } break; case Type_Pointer: @@ -598,14 +605,25 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { } doc_type.where_clauses = odin_doc_where_clauses(w, st->where_clauses); } + + auto tags = array_make<OdinDocString>(heap_allocator(), type->Struct.fields.count); + defer (array_free(&tags)); + + for_array(i, type->Struct.fields) { + tags[i] = odin_doc_write_string(w, type->Struct.tags[i]); + } + + doc_type.tags = odin_write_slice(w, tags.data, tags.count); } break; case Type_Union: doc_type.kind = OdinDocType_Union; if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; } - if (type->Union.no_nil) { doc_type.flags |= OdinDocTypeFlag_Union_no_nil; } - if (type->Union.maybe) { doc_type.flags |= OdinDocTypeFlag_Union_maybe; } - + switch (type->Union.kind) { + case UnionType_maybe: doc_type.flags |= OdinDocTypeFlag_Union_maybe; break; + case UnionType_no_nil: doc_type.flags |= OdinDocTypeFlag_Union_no_nil; break; + case UnionType_shared_nil: doc_type.flags |= OdinDocTypeFlag_Union_shared_nil; break; + } { auto variants = array_make<OdinDocTypeIndex>(heap_allocator(), type->Union.variants.count); defer (array_free(&variants)); @@ -667,40 +685,7 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { types[1] = odin_doc_type(w, type->Proc.results); doc_type.types = odin_write_slice(w, types, gb_count_of(types)); - String calling_convention = {}; - switch (type->Proc.calling_convention) { - case ProcCC_Invalid: - // no need - break; - case ProcCC_Odin: - if (default_calling_convention() != ProcCC_Odin) { - calling_convention = str_lit("odin"); - } - break; - case ProcCC_Contextless: - if (default_calling_convention() != ProcCC_Contextless) { - calling_convention = str_lit("contextless"); - } - break; - case ProcCC_CDecl: - calling_convention = str_lit("cdecl"); - break; - case ProcCC_StdCall: - calling_convention = str_lit("stdcall"); - break; - case ProcCC_FastCall: - calling_convention = str_lit("fastcall"); - break; - case ProcCC_None: - calling_convention = str_lit("none"); - break; - case ProcCC_Naked: - calling_convention = str_lit("naked"); - break; - case ProcCC_InlineAsm: - calling_convention = str_lit("inline-assembly"); - break; - } + String calling_convention = make_string_c(proc_calling_convention_strings[type->Proc.calling_convention]); doc_type.calling_convention = odin_doc_write_string(w, calling_convention); } break; @@ -795,11 +780,21 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { comment = e->decl_info->comment; docs = e->decl_info->docs; } + if (e->kind == Entity_Variable) { + if (!comment) { comment = e->Variable.comment; } + if (!docs) { docs = e->Variable.docs; } + } else if (e->kind == Entity_Constant) { + if (!comment) { comment = e->Constant.comment; } + if (!docs) { docs = e->Constant.docs; } + } + String name = e->token.string; String link_name = {}; + TokenPos pos = e->token.pos; OdinDocEntityKind kind = OdinDocEntity_Invalid; - u32 flags = 0; + u64 flags = 0; + i32 field_group_index = -1; switch (e->kind) { case Entity_Invalid: kind = OdinDocEntity_Invalid; break; @@ -810,6 +805,7 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { case Entity_ProcGroup: kind = OdinDocEntity_ProcGroup; break; case Entity_ImportName: kind = OdinDocEntity_ImportName; break; case Entity_LibraryName: kind = OdinDocEntity_LibraryName; break; + case Entity_Builtin: kind = OdinDocEntity_Builtin; break; } switch (e->kind) { @@ -829,12 +825,33 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { if (init_expr == nullptr) { init_expr = e->Variable.init_expr; } + field_group_index = e->Variable.field_group_index; + break; + case Entity_Constant: + field_group_index = e->Constant.field_group_index; break; case Entity_Procedure: if (e->Procedure.is_foreign) { flags |= OdinDocEntityFlag_Foreign; } if (e->Procedure.is_export) { flags |= OdinDocEntityFlag_Export; } link_name = e->Procedure.link_name; break; + case Entity_Builtin: + { + auto bp = builtin_procs[e->Builtin.id]; + pos = {}; + name = bp.name; + switch (bp.pkg) { + case BuiltinProcPkg_builtin: + flags |= OdinDocEntityFlag_Builtin_Pkg_Builtin; + break; + case BuiltinProcPkg_intrinsics: + flags |= OdinDocEntityFlag_Builtin_Pkg_Intrinsics; + break; + default: + GB_PANIC("Unhandled BuiltinProcPkg"); + } + } + break; } if (e->flags & EntityFlag_Param) { @@ -845,6 +862,9 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { if (e->flags & EntityFlag_NoAlias) { flags |= OdinDocEntityFlag_Param_NoAlias; } if (e->flags & EntityFlag_AnyInt) { flags |= OdinDocEntityFlag_Param_AnyInt; } } + if (e->scope && (e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) && !is_entity_exported(e)) { + flags |= OdinDocEntityFlag_Private; + } OdinDocString init_string = {}; if (init_expr) { @@ -867,12 +887,13 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { doc_entity.kind = kind; doc_entity.flags = flags; - doc_entity.pos = odin_doc_token_pos_cast(w, e->token.pos); - doc_entity.name = odin_doc_write_string(w, e->token.string); + doc_entity.pos = odin_doc_token_pos_cast(w, pos); + doc_entity.name = odin_doc_write_string(w, name); doc_entity.type = 0; // Set later doc_entity.init_string = init_string; doc_entity.comment = odin_doc_comment_group_string(w, comment); doc_entity.docs = odin_doc_comment_group_string(w, docs); + doc_entity.field_group_index = field_group_index; doc_entity.foreign_library = 0; // Set later doc_entity.link_name = odin_doc_write_string(w, link_name); if (e->decl_info != nullptr) { @@ -944,7 +965,7 @@ void odin_doc_update_entities(OdinDocWriter *w) { -OdinDocArray<OdinDocEntityIndex> odin_doc_add_pkg_entities(OdinDocWriter *w, AstPackage *pkg) { +OdinDocArray<OdinDocScopeEntry> odin_doc_add_pkg_entries(OdinDocWriter *w, AstPackage *pkg) { if (pkg->scope == nullptr) { return {}; } @@ -952,14 +973,14 @@ OdinDocArray<OdinDocEntityIndex> odin_doc_add_pkg_entities(OdinDocWriter *w, Ast return {}; } - auto entities = array_make<Entity *>(heap_allocator(), 0, pkg->scope->elements.entries.count); - defer (array_free(&entities)); + auto entries = array_make<OdinDocScopeEntry>(heap_allocator(), 0, w->entity_cache.entries.count); + defer (array_free(&entries)); for_array(i, pkg->scope->elements.entries) { + String name = pkg->scope->elements.entries[i].key.string; Entity *e = pkg->scope->elements.entries[i].value; switch (e->kind) { case Entity_Invalid: - case Entity_Builtin: case Entity_Nil: case Entity_Label: continue; @@ -970,34 +991,27 @@ OdinDocArray<OdinDocEntityIndex> odin_doc_add_pkg_entities(OdinDocWriter *w, Ast case Entity_ProcGroup: case Entity_ImportName: case Entity_LibraryName: + case Entity_Builtin: // Fine break; } - array_add(&entities, e); - } - gb_sort_array(entities.data, entities.count, cmp_entities_for_printing); - - auto entity_indices = array_make<OdinDocEntityIndex>(heap_allocator(), 0, w->entity_cache.entries.count); - defer (array_free(&entity_indices)); - - for_array(i, entities) { - Entity *e = entities[i]; if (e->pkg != pkg) { continue; } - if (!is_entity_exported(e)) { + if (!is_entity_exported(e, true)) { continue; } if (e->token.string.len == 0) { continue; } - OdinDocEntityIndex doc_entity_index = 0; - doc_entity_index = odin_doc_add_entity(w, e); - array_add(&entity_indices, doc_entity_index); + OdinDocScopeEntry entry = {}; + entry.name = odin_doc_write_string(w, name); + entry.entity = odin_doc_add_entity(w, e); + array_add(&entries, entry); } - return odin_write_slice(w, entity_indices.data, entity_indices.count); + return odin_write_slice(w, entries.data, entries.count); } @@ -1065,7 +1079,7 @@ void odin_doc_write_docs(OdinDocWriter *w) { } doc_pkg.files = odin_write_slice(w, file_indices.data, file_indices.count); - doc_pkg.entities = odin_doc_add_pkg_entities(w, pkg); + doc_pkg.entries = odin_doc_add_pkg_entries(w, pkg); if (dst) { *dst = doc_pkg; diff --git a/src/entity.cpp b/src/entity.cpp index b39ffc63a..1f87f7af6 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,9 +45,9 @@ enum EntityFlag : u64 { EntityFlag_NoAlias = 1ull<<9, EntityFlag_TypeField = 1ull<<10, EntityFlag_Value = 1ull<<11, - EntityFlag_Sret = 1ull<<12, - EntityFlag_ByVal = 1ull<<13, - EntityFlag_BitFieldValue = 1ull<<14, + + + EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, EntityFlag_ConstInput = 1ull<<17, @@ -74,6 +74,7 @@ enum EntityFlag : u64 { EntityFlag_Test = 1ull<<30, EntityFlag_Init = 1ull<<31, + EntityFlag_Subtype = 1ull<<32, EntityFlag_CustomLinkName = 1ull<<40, EntityFlag_CustomLinkage_Internal = 1ull<<41, @@ -86,6 +87,10 @@ enum EntityFlag : u64 { EntityFlag_Overridden = 1ull<<63, }; +enum : u64 { + EntityFlags_IsSubtype = EntityFlag_Using|EntityFlag_Subtype, +}; + enum EntityState : u32 { EntityState_Unresolved = 0, EntityState_InProgress = 1, @@ -110,6 +115,16 @@ struct ParameterValue { }; }; +bool has_parameter_value(ParameterValue const ¶m_value) { + if (param_value.kind != ParameterValue_Invalid) { + return true; + } + if (param_value.original_ast_expr != nullptr) { + return true; + } + return false; +} + enum EntityConstantFlags : u32 { EntityConstantFlag_ImplicitEnumValue = 1<<0, }; @@ -122,6 +137,28 @@ enum ProcedureOptimizationMode : u32 { ProcedureOptimizationMode_Speed, }; + +BlockingMutex global_type_name_objc_metadata_mutex; + +struct TypeNameObjCMetadataEntry { + String name; + Entity *entity; +}; +struct TypeNameObjCMetadata { + BlockingMutex *mutex; + Array<TypeNameObjCMetadataEntry> type_entries; + Array<TypeNameObjCMetadataEntry> value_entries; +}; + +TypeNameObjCMetadata *create_type_name_obj_c_metadata() { + TypeNameObjCMetadata *md = gb_alloc_item(permanent_allocator(), TypeNameObjCMetadata); + md->mutex = gb_alloc_item(permanent_allocator(), BlockingMutex); + mutex_init(md->mutex); + array_init(&md->type_entries, heap_allocator()); + array_init(&md->value_entries, heap_allocator()); + return md; +} + // An Entity is a named "thing" in the language struct Entity { EntityKind kind; @@ -160,10 +197,14 @@ struct Entity { ExactValue value; ParameterValue param_value; u32 flags; + i32 field_group_index; + CommentGroup *docs; + CommentGroup *comment; } Constant; struct { Ast *init_expr; // only used for some variables within procedure bodies i32 field_index; + i32 field_group_index; ParameterValue param_value; @@ -173,6 +214,8 @@ struct Entity { String link_name; String link_prefix; String link_section; + CommentGroup *docs; + CommentGroup *comment; bool is_foreign; bool is_export; } Variable; @@ -180,6 +223,8 @@ struct Entity { Type * type_parameter_specialization; String ir_mangled_name; bool is_type_alias; + String objc_class_name; + TypeNameObjCMetadata *objc_metadata; } TypeName; struct { u64 tags; @@ -239,7 +284,7 @@ bool is_entity_exported(Entity *e, bool allow_builtin = false) { if (e->flags & EntityFlag_NotExported) { return false; } - if (e->file != nullptr && (e->file->flags & AstFile_IsPrivate) != 0) { + if (e->file != nullptr && (e->file->flags & (AstFile_IsPrivatePkg|AstFile_IsPrivateFile)) != 0) { return false; } diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 000000000..faf4d11fb --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,416 @@ +struct ErrorCollector { + TokenPos prev; + std::atomic<i64> count; + std::atomic<i64> warning_count; + std::atomic<bool> in_block; + BlockingMutex mutex; + BlockingMutex error_out_mutex; + BlockingMutex string_mutex; + RecursiveMutex block_mutex; + + Array<u8> error_buffer; + Array<String> errors; +}; + +gb_global ErrorCollector global_error_collector; + +#define MAX_ERROR_COLLECTOR_COUNT (36) + + +bool any_errors(void) { + return global_error_collector.count.load() != 0; +} + +void init_global_error_collector(void) { + mutex_init(&global_error_collector.mutex); + mutex_init(&global_error_collector.block_mutex); + mutex_init(&global_error_collector.error_out_mutex); + mutex_init(&global_error_collector.string_mutex); + array_init(&global_error_collector.errors, heap_allocator()); + array_init(&global_error_collector.error_buffer, heap_allocator()); + array_init(&global_file_path_strings, heap_allocator(), 1, 4096); + array_init(&global_files, heap_allocator(), 1, 4096); +} + + +// temporary +// defined in build_settings.cpp +char *token_pos_to_string(TokenPos const &pos); + +bool set_file_path_string(i32 index, String const &path) { + bool ok = false; + GB_ASSERT(index >= 0); + mutex_lock(&global_error_collector.string_mutex); + + if (index >= global_file_path_strings.count) { + array_resize(&global_file_path_strings, index+1); + } + String prev = global_file_path_strings[index]; + if (prev.len == 0) { + global_file_path_strings[index] = path; + ok = true; + } + + mutex_unlock(&global_error_collector.string_mutex); + return ok; +} + +bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { + bool ok = false; + GB_ASSERT(index >= 0); + mutex_lock(&global_error_collector.string_mutex); + + if (index >= global_files.count) { + array_resize(&global_files, index+1); + } + AstFile *prev = global_files[index]; + if (prev == nullptr) { + global_files[index] = file; + ok = true; + } + + mutex_unlock(&global_error_collector.string_mutex); + return ok; +} + +String get_file_path_string(i32 index) { + GB_ASSERT(index >= 0); + mutex_lock(&global_error_collector.string_mutex); + + String path = {}; + if (index < global_file_path_strings.count) { + path = global_file_path_strings[index]; + } + + mutex_unlock(&global_error_collector.string_mutex); + return path; +} + +AstFile *thread_safe_get_ast_file_from_id(i32 index) { + GB_ASSERT(index >= 0); + mutex_lock(&global_error_collector.string_mutex); + + AstFile *file = nullptr; + if (index < global_files.count) { + file = global_files[index]; + } + + mutex_unlock(&global_error_collector.string_mutex); + return file; +} + + + +void begin_error_block(void) { + mutex_lock(&global_error_collector.block_mutex); + global_error_collector.in_block.store(true); +} + +void end_error_block(void) { + if (global_error_collector.error_buffer.count > 0) { + isize n = global_error_collector.error_buffer.count; + u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); + gb_memmove(text, global_error_collector.error_buffer.data, n); + text[n] = 0; + String s = {text, n}; + array_add(&global_error_collector.errors, s); + global_error_collector.error_buffer.count = 0; + } + + global_error_collector.in_block.store(false); + mutex_unlock(&global_error_collector.block_mutex); +} + +#define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) + + +#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) +typedef ERROR_OUT_PROC(ErrorOutProc); + +ERROR_OUT_PROC(default_error_out_va) { + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + + char buf[4096] = {}; + isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); + isize n = len-1; + if (global_error_collector.in_block) { + isize cap = global_error_collector.error_buffer.count + n; + array_reserve(&global_error_collector.error_buffer, cap); + u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count; + gb_memmove(data, buf, n); + global_error_collector.error_buffer.count += n; + } else { + mutex_lock(&global_error_collector.error_out_mutex); + { + u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); + gb_memmove(text, buf, n); + text[n] = 0; + array_add(&global_error_collector.errors, make_string(text, n)); + } + mutex_unlock(&global_error_collector.error_out_mutex); + + } + gb_file_write(f, buf, n); +} + + +ErrorOutProc *error_out_va = default_error_out_va; + +// NOTE: defined in build_settings.cpp +bool global_warnings_as_errors(void); +bool global_ignore_warnings(void); +bool show_error_line(void); +gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); + +void error_out(char const *fmt, ...) { + va_list va; + va_start(va, fmt); + error_out_va(fmt, va); + va_end(va); +} + + +bool show_error_on_line(TokenPos const &pos, TokenPos end) { + if (!show_error_line()) { + return false; + } + + i32 offset = 0; + gbString the_line = get_file_line_as_string(pos, &offset); + defer (gb_string_free(the_line)); + + if (the_line != nullptr) { + String line = make_string(cast(u8 const *)the_line, gb_string_length(the_line)); + + // TODO(bill): This assumes ASCII + + enum { + MAX_LINE_LENGTH = 76, + MAX_TAB_WIDTH = 8, + ELLIPSIS_PADDING = 8 + }; + + error_out("\n\t"); + if (line.len+MAX_TAB_WIDTH+ELLIPSIS_PADDING > MAX_LINE_LENGTH) { + i32 const half_width = MAX_LINE_LENGTH/2; + i32 left = cast(i32)(offset); + i32 right = cast(i32)(line.len - offset); + left = gb_min(left, half_width); + right = gb_min(right, half_width); + + line.text += offset-left; + line.len -= offset+right-left; + + line = string_trim_whitespace(line); + + offset = left + ELLIPSIS_PADDING/2; + + error_out("... %.*s ...", LIT(line)); + } else { + error_out("%.*s", LIT(line)); + } + error_out("\n\t"); + + for (i32 i = 0; i < offset; i++) { + error_out(" "); + } + error_out("^"); + if (end.file_id == pos.file_id) { + if (end.line > pos.line) { + for (i32 i = offset; i < line.len; i++) { + error_out("~"); + } + } else if (end.line == pos.line && end.column > pos.column) { + i32 length = gb_min(end.offset - pos.offset, cast(i32)(line.len-offset)); + for (i32 i = 1; i < length-1; i++) { + error_out("~"); + } + if (length > 1) { + error_out("^"); + } + } + } + + error_out("\n\n"); + return true; + } + return false; +} + +void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { + global_error_collector.count.fetch_add(1); + + mutex_lock(&global_error_collector.mutex); + // NOTE(bill): Duplicate error, skip it + if (pos.line == 0) { + error_out("Error: %s\n", gb_bprintf_va(fmt, va)); + } else if (global_error_collector.prev != pos) { + global_error_collector.prev = pos; + error_out("%s %s\n", + token_pos_to_string(pos), + gb_bprintf_va(fmt, va)); + show_error_on_line(pos, end); + } + mutex_unlock(&global_error_collector.mutex); + if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { + gb_exit(1); + } +} + +void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { + if (global_warnings_as_errors()) { + error_va(pos, end, fmt, va); + return; + } + global_error_collector.warning_count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); + if (!global_ignore_warnings()) { + // NOTE(bill): Duplicate error, skip it + if (pos.line == 0) { + error_out("Warning: %s\n", gb_bprintf_va(fmt, va)); + } else if (global_error_collector.prev != pos) { + global_error_collector.prev = pos; + error_out("%s Warning: %s\n", + token_pos_to_string(pos), + gb_bprintf_va(fmt, va)); + show_error_on_line(pos, end); + } + } + mutex_unlock(&global_error_collector.mutex); +} + + +void error_line_va(char const *fmt, va_list va) { + error_out_va(fmt, va); +} + +void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { + mutex_lock(&global_error_collector.mutex); + global_error_collector.count++; + // NOTE(bill): Duplicate error, skip it + if (pos.line == 0) { + error_out("Error: %s", gb_bprintf_va(fmt, va)); + } else if (global_error_collector.prev != pos) { + global_error_collector.prev = pos; + error_out("%s %s", + token_pos_to_string(pos), + gb_bprintf_va(fmt, va)); + } + mutex_unlock(&global_error_collector.mutex); + if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { + gb_exit(1); + } +} + + +void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { + mutex_lock(&global_error_collector.mutex); + global_error_collector.count++; + // NOTE(bill): Duplicate error, skip it + if (global_error_collector.prev != pos) { + global_error_collector.prev = pos; + error_out("%s Syntax Error: %s\n", + token_pos_to_string(pos), + gb_bprintf_va(fmt, va)); + show_error_on_line(pos, end); + } else if (pos.line == 0) { + error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va)); + } + + mutex_unlock(&global_error_collector.mutex); + if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { + gb_exit(1); + } +} + +void syntax_warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { + if (global_warnings_as_errors()) { + syntax_error_va(pos, end, fmt, va); + return; + } + mutex_lock(&global_error_collector.mutex); + global_error_collector.warning_count++; + if (!global_ignore_warnings()) { + // NOTE(bill): Duplicate error, skip it + if (global_error_collector.prev != pos) { + global_error_collector.prev = pos; + error_out("%s Syntax Warning: %s\n", + token_pos_to_string(pos), + gb_bprintf_va(fmt, va)); + show_error_on_line(pos, end); + } else if (pos.line == 0) { + error_out("Warning: %s\n", gb_bprintf_va(fmt, va)); + } + } + mutex_unlock(&global_error_collector.mutex); +} + + + +void warning(Token const &token, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + warning_va(token.pos, {}, fmt, va); + va_end(va); +} + +void error(Token const &token, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + error_va(token.pos, {}, fmt, va); + va_end(va); +} + +void error(TokenPos pos, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + Token token = {}; + token.pos = pos; + error_va(pos, {}, fmt, va); + va_end(va); +} + +void error_line(char const *fmt, ...) { + va_list va; + va_start(va, fmt); + error_line_va(fmt, va); + va_end(va); +} + + +void syntax_error(Token const &token, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + syntax_error_va(token.pos, {}, fmt, va); + va_end(va); +} + +void syntax_error(TokenPos pos, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + syntax_error_va(pos, {}, fmt, va); + va_end(va); +} + +void syntax_warning(Token const &token, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + syntax_warning_va(token.pos, {}, fmt, va); + va_end(va); +} + + +void compiler_error(char const *fmt, ...) { + va_list va; + + va_start(va, fmt); + gb_printf_err("Internal Compiler Error: %s\n", + gb_bprintf_va(fmt, va)); + va_end(va); + GB_DEBUG_TRAP(); + gb_exit(1); +} + + + + diff --git a/src/exact_value.cpp b/src/exact_value.cpp index fd90278e5..cedef48c4 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -50,9 +50,9 @@ struct ExactValue { union { bool value_bool; String value_string; - BigInt value_integer; // NOTE(bill): This must be an integer and not a pointer + BigInt value_integer; f64 value_float; - i64 value_pointer; + i64 value_pointer; // NOTE(bill): This must be an integer and not a pointer Complex128 *value_complex; Quaternion256 *value_quaternion; Ast * value_compound; @@ -591,6 +591,7 @@ failure: i32 exact_value_order(ExactValue const &v) { switch (v.kind) { case ExactValue_Invalid: + case ExactValue_Compound: return 0; case ExactValue_Bool: case ExactValue_String: @@ -607,8 +608,6 @@ i32 exact_value_order(ExactValue const &v) { return 6; case ExactValue_Procedure: return 7; - // case ExactValue_Compound: - // return 8; default: GB_PANIC("How'd you get here? Invalid Value.kind %d", v.kind); @@ -630,6 +629,9 @@ void match_exact_values(ExactValue *x, ExactValue *y) { case ExactValue_Bool: case ExactValue_String: case ExactValue_Quaternion: + case ExactValue_Pointer: + case ExactValue_Procedure: + case ExactValue_Typeid: return; case ExactValue_Integer: @@ -671,9 +673,6 @@ void match_exact_values(ExactValue *x, ExactValue *y) { return; } break; - - case ExactValue_Procedure: - return; } compiler_error("match_exact_values: How'd you get here? Invalid ExactValueKind %d", x->kind); @@ -932,6 +931,17 @@ bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) { break; } + case ExactValue_Pointer: { + switch (op) { + case Token_CmpEq: return x.value_pointer == y.value_pointer; + case Token_NotEq: return x.value_pointer != y.value_pointer; + case Token_Lt: return x.value_pointer < y.value_pointer; + case Token_LtEq: return x.value_pointer <= y.value_pointer; + case Token_Gt: return x.value_pointer > y.value_pointer; + case Token_GtEq: return x.value_pointer >= y.value_pointer; + } + } + case ExactValue_Typeid: switch (op) { case Token_CmpEq: return are_types_identical(x.value_typeid, y.value_typeid); diff --git a/src/gb/gb.h b/src/gb/gb.h index f716b0840..3b2d6434c 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -79,6 +79,10 @@ extern "C" { #ifndef GB_SYSTEM_FREEBSD #define GB_SYSTEM_FREEBSD 1 #endif + #elif defined(__OpenBSD__) + #ifndef GB_SYSTEM_OPENBSD + #define GB_SYSTEM_OPENBSD 1 + #endif #else #error This UNIX operating system is not supported #endif @@ -199,7 +203,7 @@ extern "C" { #endif #include <stdlib.h> // NOTE(bill): malloc on linux #include <sys/mman.h> - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) #include <sys/sendfile.h> #endif #include <sys/stat.h> @@ -235,6 +239,12 @@ extern "C" { #define sendfile(out, in, offset, count) sendfile(out, in, offset, count, NULL, NULL, 0) #endif +#if defined(GB_SYSTEM_OPENBSD) + #include <stdio.h> + #include <pthread_np.h> + #define lseek64 lseek +#endif + #if defined(GB_SYSTEM_UNIX) #include <semaphore.h> #endif @@ -783,6 +793,13 @@ typedef struct gbAffinity { isize thread_count; isize threads_per_core; } gbAffinity; +#elif defined(GB_SYSTEM_OPENBSD) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #else #error TODO(bill): Unknown system #endif @@ -3355,6 +3372,8 @@ gb_inline u32 gb_thread_current_id(void) { __asm__("mov %%gs:0x08,%0" : "=r"(thread_id)); #elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); +#elif defined(GB_SYSTEM_LINUX) + thread_id = gettid(); #else #error Unsupported architecture for gb_thread_current_id() #endif @@ -3680,6 +3699,30 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { GB_ASSERT(0 <= core && core < a->core_count); return a->threads_per_core; } + +#elif defined(GB_SYSTEM_OPENBSD) +#include <unistd.h> + +void gb_affinity_init(gbAffinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} #else #error TODO(bill): Unknown system #endif @@ -6023,7 +6066,7 @@ gbFileTime gb_file_last_write_time(char const *filepath) { gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) { #if defined(GB_SYSTEM_OSX) return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0; -#else +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_FREEBSD) isize size; int existing_fd = open(existing_filename, O_RDONLY, 0); int new_fd = open(new_filename, O_WRONLY|O_CREAT, 0666); @@ -6040,6 +6083,49 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena close(existing_fd); return size == stat_existing.st_size; +#else + int new_flags = O_WRONLY | O_CREAT; + if (fail_if_exists) { + new_flags |= O_EXCL; + } + int existing_fd = open(existing_filename, O_RDONLY, 0); + int new_fd = open(new_filename, new_flags, 0666); + + struct stat stat_existing; + if (fstat(existing_fd, &stat_existing) == -1) { + return 0; + } + + size_t bsize = stat_existing.st_blksize > BUFSIZ ? stat_existing.st_blksize : BUFSIZ; + char *buf = (char *)malloc(bsize); + if (buf == NULL) { + close(new_fd); + close(existing_fd); + return 0; + } + + isize size = 0; + ssize_t nread, nwrite, offset; + while ((nread = read(existing_fd, buf, bsize)) != -1 && nread != 0) { + for (offset = 0; nread; nread -= nwrite, offset += nwrite) { + if ((nwrite = write(new_fd, buf + offset, nread)) == -1 || nwrite == 0) { + free(buf); + close(new_fd); + close(existing_fd); + return 0; + } + size += nwrite; + } + } + + free(buf); + close(new_fd); + close(existing_fd); + + if (nread == -1) { + return 0; + } + return size == stat_existing.st_size; #endif } @@ -6091,6 +6177,7 @@ gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char con } void gb_file_free_contents(gbFileContents *fc) { + if (fc == NULL || fc->size == 0) return; GB_ASSERT_NOT_NULL(fc->data); gb_free(fc->allocator, fc->data); fc->data = NULL; @@ -6186,20 +6273,44 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) { #else char *p, *result, *fullpath = NULL; isize len; - p = realpath(path, NULL); - fullpath = p; - if (p == NULL) { - // NOTE(bill): File does not exist - fullpath = cast(char *)path; - } + fullpath = realpath(path, NULL); + + if (fullpath == NULL) { + // NOTE(Jeroen): Path doesn't exist. + if (gb_strlen(path) > 0 && path[0] == '/') { + // But it is an absolute path, so return as-is. - len = gb_strlen(fullpath); + fullpath = (char *)path; + len = gb_strlen(fullpath) + 1; + result = gb_alloc_array(a, char, len + 1); - result = gb_alloc_array(a, char, len + 1); - gb_memmove(result, fullpath, len); - result[len] = 0; - free(p); + gb_memmove(result, fullpath, len); + result[len] = 0; + } else { + // Appears to be a relative path, so construct an absolute one relative to <cwd>. + char cwd[4096]; + getcwd(&cwd[0], 4096); + + isize path_len = gb_strlen(path); + isize cwd_len = gb_strlen(cwd); + len = cwd_len + 1 + path_len + 1; + result = gb_alloc_array(a, char, len); + + gb_memmove(result, (void *)cwd, cwd_len); + result[cwd_len] = '/'; + + gb_memmove(result + cwd_len + 1, (void *)path, gb_strlen(path)); + result[len] = 0; + + } + } else { + len = gb_strlen(fullpath) + 1; + result = gb_alloc_array(a, char, len + 1); + gb_memmove(result, fullpath, len); + result[len] = 0; + free(fullpath); + } return result; #endif } diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index e18dc344b..07d2dd6e3 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -965,6 +965,10 @@ namespace lbAbiArm64 { } return false; } + + unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef *base_type_, unsigned member_count_) { + return (member_count_ <= 4); + } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef type, bool return_is_defined) { LLVMTypeRef homo_base_type = {}; @@ -975,22 +979,31 @@ namespace lbAbiArm64 { } else if (is_register(type)) { return non_struct(c, type); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) { - return lb_arg_type_direct(type, LLVMArrayType(homo_base_type, homo_member_count), nullptr, nullptr); + if(is_homogenous_aggregate_small_enough(&homo_base_type, homo_member_count)) { + return lb_arg_type_direct(type, LLVMArrayType(homo_base_type, homo_member_count), nullptr, nullptr); + } else { + //TODO(Platin): do i need to create stuff that can handle the diffrent return type? + // else this needs a fix in llvm_backend_proc as we would need to cast it to the correct array type + + //LLVMTypeRef array_type = LLVMArrayType(homo_base_type, homo_member_count); + LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", type); + return lb_arg_type_indirect(type, attr); + } } else { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; if (size <= 1) { - cast_type = LLVMIntTypeInContext(c, 8); + cast_type = LLVMInt8TypeInContext(c); } else if (size <= 2) { - cast_type = LLVMIntTypeInContext(c, 16); + cast_type = LLVMInt16TypeInContext(c); } else if (size <= 4) { - cast_type = LLVMIntTypeInContext(c, 32); + cast_type = LLVMInt32TypeInContext(c); } else if (size <= 8) { - cast_type = LLVMIntTypeInContext(c, 64); + cast_type = LLVMInt64TypeInContext(c); } else { unsigned count = cast(unsigned)((size+7)/8); - cast_type = LLVMArrayType(LLVMIntTypeInContext(c, 64), count); + cast_type = LLVMArrayType(LLVMInt64TypeInContext(c), count); } return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } else { @@ -999,7 +1012,7 @@ namespace lbAbiArm64 { } } } - + Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { auto args = array_make<lbArgType>(heap_allocator(), arg_count); @@ -1171,16 +1184,24 @@ LB_ABI_INFO(lb_get_abi_info) { ft->calling_convention = calling_convention; return ft; } + case ProcCC_Win64: + GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); + return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); + case ProcCC_SysV: + GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); + return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } switch (build_context.metrics.arch) { case TargetArch_amd64: - if (build_context.metrics.os == TargetOs_windows) { + if (build_context.metrics.os == TargetOs_windows || build_context.metrics.abi == TargetABI_Win64) { return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); + } else if (build_context.metrics.abi == TargetABI_SysV) { + return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } else { return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } - case TargetArch_386: + case TargetArch_i386: return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_arm64: return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 5acd2a80f..7781997f7 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -624,6 +624,9 @@ struct lbGlobalVariable { }; lbProcedure *lb_create_startup_type_info(lbModule *m) { + if (build_context.disallow_rtti) { + return nullptr; + } LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(m->mod); lb_populate_function_pass_manager(m, default_function_pass_manager, false, build_context.optimization_level); LLVMFinalizeFunctionPassManager(default_function_pass_manager); @@ -652,7 +655,54 @@ lbProcedure *lb_create_startup_type_info(lbModule *m) { return p; } -lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *startup_type_info, Array<lbGlobalVariable> &global_variables) { // Startup Runtime +lbProcedure *lb_create_objc_names(lbModule *main_module) { + if (build_context.metrics.os != TargetOs_darwin) { + return nullptr; + } + Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_CDecl); + lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit("__$init_objc_names"), proc_type); + p->is_startup = true; + return p; +} + +void lb_finalize_objc_names(lbProcedure *p) { + if (p == nullptr) { + return; + } + lbModule *m = p->module; + + LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(m->mod); + lb_populate_function_pass_manager(m, default_function_pass_manager, false, build_context.optimization_level); + LLVMFinalizeFunctionPassManager(default_function_pass_manager); + + + auto args = array_make<lbValue>(permanent_allocator(), 1); + + LLVMSetLinkage(p->value, LLVMInternalLinkage); + lb_begin_procedure_body(p); + for_array(i, m->objc_classes.entries) { + auto const &entry = m->objc_classes.entries[i]; + String name = entry.key.string; + args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); + lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); + lb_addr_store(p, entry.value, ptr); + } + + for_array(i, m->objc_selectors.entries) { + auto const &entry = m->objc_selectors.entries[i]; + String name = entry.key.string; + args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); + lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); + lb_addr_store(p, entry.value, ptr); + } + + lb_end_procedure_body(p); + + lb_run_function_pass_manager(default_function_pass_manager, p); + +} + +lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *startup_type_info, lbProcedure *objc_names, Array<lbGlobalVariable> &global_variables) { // Startup Runtime LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(main_module->mod); lb_populate_function_pass_manager(main_module, default_function_pass_manager, false, build_context.optimization_level); LLVMFinalizeFunctionPassManager(default_function_pass_manager); @@ -664,7 +714,13 @@ lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *start lb_begin_procedure_body(p); - LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, startup_type_info->type)), startup_type_info->value, nullptr, 0, ""); + if (startup_type_info) { + LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, startup_type_info->type)), startup_type_info->value, nullptr, 0, ""); + } + + if (objc_names) { + LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, objc_names->type)), objc_names->value, nullptr, 0, ""); + } for_array(i, global_variables) { auto *var = &global_variables[i]; @@ -783,7 +839,7 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) params->Tuple.variables[1] = alloc_entity_param(nullptr, make_token_ident("fdwReason"), t_u32, false, true); params->Tuple.variables[2] = alloc_entity_param(nullptr, make_token_ident("lpReserved"), t_rawptr, false, true); call_cleanup = false; - } else if (build_context.metrics.os == TargetOs_windows && (build_context.metrics.arch == TargetArch_386 || build_context.no_crt)) { + } else if (build_context.metrics.os == TargetOs_windows && (build_context.metrics.arch == TargetArch_i386 || build_context.no_crt)) { name = str_lit("mainCRTStartup"); } else if (is_arch_wasm()) { name = str_lit("_start"); @@ -873,7 +929,7 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } else { if (m->info->entry_point != nullptr) { lbValue entry_point = lb_find_procedure_value_from_entity(m, m->info->entry_point); - lb_emit_call(p, entry_point, {}); + lb_emit_call(p, entry_point, {}, ProcInlining_no_inline); } } @@ -911,7 +967,12 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } String lb_filepath_ll_for_module(lbModule *m) { - String path = m->gen->output_base; + String path = concatenate3_strings(permanent_allocator(), + build_context.build_paths[BuildPath_Output].basename, + STR_LIT("/"), + build_context.build_paths[BuildPath_Output].name + ); + if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } else if (USE_SEPARATE_MODULES) { @@ -922,7 +983,12 @@ String lb_filepath_ll_for_module(lbModule *m) { return path; } String lb_filepath_obj_for_module(lbModule *m) { - String path = m->gen->output_base; + String path = concatenate3_strings(permanent_allocator(), + build_context.build_paths[BuildPath_Output].basename, + STR_LIT("/"), + build_context.build_paths[BuildPath_Output].name + ); + if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } @@ -945,6 +1011,19 @@ String lb_filepath_obj_for_module(lbModule *m) { case TargetOs_essence: ext = STR_LIT(".o"); break; + + case TargetOs_freestanding: + switch (build_context.metrics.abi) { + default: + case TargetABI_Default: + case TargetABI_SysV: + ext = STR_LIT(".o"); + break; + case TargetABI_Win64: + ext = STR_LIT(".obj"); + break; + } + break; } } } @@ -1140,7 +1219,7 @@ void lb_generate_code(lbGenerator *gen) { switch (build_context.metrics.arch) { case TargetArch_amd64: - case TargetArch_386: + case TargetArch_i386: LLVMInitializeX86TargetInfo(); LLVMInitializeX86Target(); LLVMInitializeX86TargetMC(); @@ -1197,6 +1276,8 @@ void lb_generate_code(lbGenerator *gen) { LLVMCodeModel code_mode = LLVMCodeModelDefault; if (is_arch_wasm()) { code_mode = LLVMCodeModelJITDefault; + } else if (build_context.metrics.os == TargetOs_freestanding) { + code_mode = LLVMCodeModelKernel; } char const *host_cpu_name = LLVMGetHostCPUName(); @@ -1219,10 +1300,18 @@ void lb_generate_code(lbGenerator *gen) { // x86-64-v3: (close to Haswell) AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE // x86-64-v4: AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL if (ODIN_LLVM_MINIMUM_VERSION_12) { - llvm_cpu = "x86-64-v2"; + if (build_context.metrics.os == TargetOs_freestanding) { + llvm_cpu = "x86-64"; + } else { + llvm_cpu = "x86-64-v2"; + } } } + if (build_context.target_features.len != 0) { + llvm_features = alloc_cstring(permanent_allocator(), build_context.target_features); + } + // GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target)); LLVMCodeGenOptLevel code_gen_level = LLVMCodeGenLevelNone; @@ -1238,12 +1327,36 @@ void lb_generate_code(lbGenerator *gen) { // NOTE(bill, 2021-05-04): Target machines must be unique to each module because they are not thread safe auto target_machines = array_make<LLVMTargetMachineRef>(permanent_allocator(), gen->modules.entries.count); + // NOTE(dweiler): Dynamic libraries require position-independent code. + LLVMRelocMode reloc_mode = LLVMRelocDefault; + if (build_context.build_mode == BuildMode_DynamicLibrary) { + reloc_mode = LLVMRelocPIC; + } + + switch (build_context.reloc_mode) { + case RelocMode_Default: + if (build_context.metrics.os == TargetOs_openbsd) { + // Always use PIC for OpenBSD: it defaults to PIE + reloc_mode = LLVMRelocPIC; + } + break; + case RelocMode_Static: + reloc_mode = LLVMRelocStatic; + break; + case RelocMode_PIC: + reloc_mode = LLVMRelocPIC; + break; + case RelocMode_DynamicNoPIC: + reloc_mode = LLVMRelocDynamicNoPic; + break; + } + for_array(i, gen->modules.entries) { target_machines[i] = LLVMCreateTargetMachine( target, target_triple, llvm_cpu, llvm_features, code_gen_level, - LLVMRelocDefault, + reloc_mode, code_mode); LLVMSetModuleDataLayout(gen->modules.entries[i].value->mod, LLVMCreateTargetDataLayout(target_machines[i])); } @@ -1304,7 +1417,7 @@ void lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Global Variables"); - { + if (!build_context.disallow_rtti) { lbModule *m = default_module; { // Add type info data @@ -1399,30 +1512,30 @@ void lb_generate_code(lbGenerator *gen) { isize global_variable_max_count = 0; - Entity *entry_point = info->entry_point; - bool has_dll_main = false; - bool has_win_main = false; + bool already_has_entry_point = false; for_array(i, info->entities) { Entity *e = info->entities[i]; String name = e->token.string; - bool is_global = e->pkg != nullptr; - if (e->kind == Entity_Variable) { global_variable_max_count++; - } else if (e->kind == Entity_Procedure && !is_global) { + } else if (e->kind == Entity_Procedure) { if ((e->scope->flags&ScopeFlag_Init) && name == "main") { - GB_ASSERT(e == entry_point); - // entry_point = e; + GB_ASSERT(e == info->entry_point); } - if (e->Procedure.is_export || - (e->Procedure.link_name.len > 0) || - ((e->scope->flags&ScopeFlag_File) && e->Procedure.link_name.len > 0)) { - if (!has_dll_main && name == "DllMain") { - has_dll_main = true; - } else if (!has_win_main && name == "WinMain") { - has_win_main = true; + if (build_context.command_kind == Command_test && + (e->Procedure.is_export || e->Procedure.link_name.len > 0)) { + String link_name = e->Procedure.link_name; + if (e->pkg->kind == Package_Runtime) { + if (link_name == "main" || + link_name == "DllMain" || + link_name == "WinMain" || + link_name == "wWinMain" || + link_name == "mainCRTStartup" || + link_name == "_start") { + already_has_entry_point = true; + } } } } @@ -1560,6 +1673,14 @@ void lb_generate_code(lbGenerator *gen) { } } + TIME_SECTION("LLVM Runtime Type Information Creation"); + lbProcedure *startup_type_info = lb_create_startup_type_info(default_module); + + lbProcedure *objc_names = lb_create_objc_names(default_module); + + TIME_SECTION("LLVM Runtime Startup Creation (Global Variables)"); + lbProcedure *startup_runtime = lb_create_startup_runtime(default_module, startup_type_info, objc_names, global_variables); + gb_unused(startup_runtime); TIME_SECTION("LLVM Global Procedures and Types"); for_array(i, info->entities) { @@ -1619,14 +1740,6 @@ void lb_generate_code(lbGenerator *gen) { } } - - TIME_SECTION("LLVM Runtime Type Information Creation"); - lbProcedure *startup_type_info = lb_create_startup_type_info(default_module); - - TIME_SECTION("LLVM Runtime Startup Creation (Global Variables)"); - lbProcedure *startup_runtime = lb_create_startup_runtime(default_module, startup_type_info, global_variables); - - TIME_SECTION("LLVM Procedure Generation"); for_array(j, gen->modules.entries) { lbModule *m = gen->modules.entries[j].value; @@ -1636,8 +1749,7 @@ void lb_generate_code(lbGenerator *gen) { } } - - if (!(build_context.build_mode == BuildMode_DynamicLibrary && !has_dll_main)) { + if (build_context.command_kind == Command_test && !already_has_entry_point) { TIME_SECTION("LLVM main"); lb_create_main_procedure(default_module, startup_runtime); } @@ -1651,6 +1763,8 @@ void lb_generate_code(lbGenerator *gen) { } } + lb_finalize_objc_names(objc_names); + if (build_context.ODIN_DEBUG) { TIME_SECTION("LLVM Debug Info Complete Types and Finalize"); for_array(j, gen->modules.entries) { @@ -1663,6 +1777,7 @@ void lb_generate_code(lbGenerator *gen) { } + TIME_SECTION("LLVM Function Pass"); for_array(i, gen->modules.entries) { lbModule *m = gen->modules.entries[i].value; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index e70b1f84c..f2bcfaff6 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -144,6 +144,9 @@ struct lbModule { PtrMap<void *, LLVMMetadataRef> debug_values; Array<lbIncompleteDebugType> debug_incomplete_types; + + StringMap<lbAddr> objc_classes; + StringMap<lbAddr> objc_selectors; }; struct lbGenerator { @@ -204,7 +207,6 @@ enum lbDeferExitKind { enum lbDeferKind { lbDefer_Node, - lbDefer_Instr, lbDefer_Proc, }; @@ -215,8 +217,6 @@ struct lbDefer { lbBlock * block; union { Ast *stmt; - // NOTE(bill): 'instr' will be copied every time to create a new one - lbValue instr; struct { lbValue deferred; Array<lbValue> result_as_args; @@ -235,6 +235,7 @@ struct lbTargetList { enum lbProcedureFlag : u32 { lbProcedureFlag_WithoutMemcpyPass = 1<<0, + lbProcedureFlag_DebugAllocaCopy = 1<<1, }; struct lbCopyElisionHint { @@ -270,7 +271,6 @@ struct lbProcedure { bool is_done; lbAddr return_ptr; - Array<lbValue> params; Array<lbDefer> defer_stmts; Array<lbBlock *> blocks; Array<lbBranchBlocks> branch_blocks; @@ -296,7 +296,6 @@ struct lbProcedure { bool lb_init_generator(lbGenerator *gen, Checker *c); -void lb_generate_module(lbGenerator *gen); String lb_mangle_name(lbModule *m, Entity *e); String lb_get_entity_name(lbModule *m, Entity *e, String name = {}); @@ -369,7 +368,7 @@ lbContextData *lb_push_context_onto_stack(lbProcedure *p, lbAddr ctx); lbContextData *lb_push_context_onto_stack_from_implicit_parameter(lbProcedure *p); -lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value={}); +lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value={}, Entity **entity_=nullptr); lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e=nullptr, bool zero_init=true, i32 param_index=0, bool force_no_init=false); void lb_add_foreign_library_path(lbModule *m, Entity *e); @@ -549,6 +548,10 @@ lbCallingConventionKind const lb_calling_convention_map[ProcCC_MAX] = { lbCallingConvention_C, // ProcCC_None, lbCallingConvention_C, // ProcCC_Naked, lbCallingConvention_C, // ProcCC_InlineAsm, + + lbCallingConvention_Win64, // ProcCC_Win64, + lbCallingConvention_X86_64_SysV, // ProcCC_SysV, + }; enum : LLVMDWARFTypeEncoding { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 5862a7add..8f17a1cfb 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -115,8 +115,8 @@ LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { lbValue lb_const_ptr_cast(lbModule *m, lbValue value, Type *t) { - GB_ASSERT(is_type_pointer(value.type)); - GB_ASSERT(is_type_pointer(t)); + GB_ASSERT(is_type_internally_pointer_like(value.type)); + GB_ASSERT(is_type_internally_pointer_like(t)); GB_ASSERT(lb_is_const(value)); lbValue res = {}; @@ -175,7 +175,7 @@ LLVMValueRef llvm_const_array(LLVMTypeRef elem_type, LLVMValueRef *values, isize } LLVMValueRef llvm_const_slice(lbModule *m, lbValue data, lbValue len) { - GB_ASSERT(is_type_pointer(data.type)); + GB_ASSERT(is_type_pointer(data.type) || is_type_multi_pointer(data.type)); GB_ASSERT(are_types_identical(len.type, t_int)); LLVMValueRef vals[2] = { data.value, @@ -568,7 +568,7 @@ lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_loc } case ExactValue_Integer: - if (is_type_pointer(type)) { + if (is_type_pointer(type) || is_type_multi_pointer(type)) { LLVMTypeRef t = lb_type(m, original_type); LLVMValueRef i = lb_big_int_to_llvm(m, t_uintptr, &value.value_integer); res.value = LLVMConstIntToPtr(i, t); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 7a2b00fe9..b91f32bfc 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -958,13 +958,79 @@ void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, T ); LLVMValueRef storage = ptr; - LLVMValueRef instr = ptr; + LLVMBasicBlockRef block = p->decl_block->block; LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, token.pos); LLVMMetadataRef llvm_expr = LLVMDIBuilderCreateExpression(m->debug_builder, nullptr, 0); lb_set_llvm_metadata(m, ptr, llvm_expr); - LLVMDIBuilderInsertDeclareBefore(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, instr); + LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block); } + +void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block) { + if (p->debug_info == nullptr) { + return; + } + if (type == nullptr) { + return; + } + if (type == t_invalid) { + return; + } + if (p->body == nullptr) { + return; + } + + lbModule *m = p->module; + String const &name = token.string; + if (name == "" || name == "_") { + return; + } + + if (lb_get_llvm_metadata(m, ptr) != nullptr) { + // Already been set + return; + } + + + AstFile *file = p->body->file(); + + LLVMMetadataRef llvm_scope = lb_get_current_debug_scope(p); + LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, file); + GB_ASSERT(llvm_scope != nullptr); + if (llvm_file == nullptr) { + llvm_file = LLVMDIScopeGetFile(llvm_scope); + } + + if (llvm_file == nullptr) { + return; + } + + LLVMDIFlags flags = LLVMDIFlagZero; + LLVMBool always_preserve = build_context.optimization_level == 0; + + LLVMMetadataRef debug_type = lb_debug_type(m, type); + + LLVMMetadataRef var_info = LLVMDIBuilderCreateParameterVariable( + m->debug_builder, llvm_scope, + cast(char const *)name.text, cast(size_t)name.len, + arg_number, + llvm_file, token.pos.line, + debug_type, + always_preserve, flags + ); + + LLVMValueRef storage = ptr; + LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, token.pos); + LLVMMetadataRef llvm_expr = LLVMDIBuilderCreateExpression(m->debug_builder, nullptr, 0); + lb_set_llvm_metadata(m, ptr, llvm_expr); + + // NOTE(bill, 2022-02-01): For parameter values, you must insert them at the end of the decl block + // The reason is that if the parameter is at index 0 and a pointer, there is not such things as an + // instruction "before" it. + LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); +} + + void lb_add_debug_context_variable(lbProcedure *p, lbAddr const &ctx) { if (!p->debug_info || !p->body) { return; @@ -984,5 +1050,10 @@ void lb_add_debug_context_variable(lbProcedure *p, lbAddr const &ctx) { token.string = str_lit("context"); token.pos = pos; - lb_add_debug_local_variable(p, ctx.addr.value, t_context, token); + LLVMValueRef ptr = ctx.addr.value; + while (LLVMIsABitCastInst(ptr)) { + ptr = LLVMGetOperand(ptr, 0); + } + + lb_add_debug_local_variable(p, ptr, t_context, token); } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index edda3a267..133df4d41 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -508,7 +508,7 @@ bool lb_is_matrix_simdable(Type *t) { case TargetArch_arm64: // TODO(bill): determine when this is fine return true; - case TargetArch_386: + case TargetArch_i386: case TargetArch_wasm32: case TargetArch_wasm64: return false; @@ -580,6 +580,27 @@ LLVMValueRef lb_matrix_to_trimmed_vector(lbProcedure *p, lbValue m) { lbValue lb_emit_matrix_tranpose(lbProcedure *p, lbValue m, Type *type) { if (is_type_array(m.type)) { + i32 rank = type_math_rank(m.type); + if (rank == 2) { + lbAddr addr = lb_add_local_generated(p, type, false); + lbValue dst = addr.addr; + lbValue src = m; + i32 n = cast(i32)get_array_type_count(m.type); + i32 m = cast(i32)get_array_type_count(type); + // m.type == [n][m]T + // type == [m][n]T + + for (i32 j = 0; j < m; j++) { + lbValue dst_col = lb_emit_struct_ep(p, dst, j); + for (i32 i = 0; i < n; i++) { + lbValue dst_row = lb_emit_struct_ep(p, dst_col, i); + lbValue src_col = lb_emit_struct_ev(p, src, i); + lbValue src_row = lb_emit_struct_ev(p, src_col, j); + lb_emit_store(p, dst_row, src_row); + } + } + return lb_addr_load(p, addr); + } // no-op m.type = type; return m; @@ -1655,26 +1676,10 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return res; } - if (is_type_float(src) && is_type_complex(dst)) { - Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, false); - lbValue gp = lb_addr_get_ptr(p, gen); - lbValue real = lb_emit_conv(p, value, ft); - lb_emit_store(p, lb_emit_struct_ep(p, gp, 0), real); - return lb_addr_load(p, gen); - } - if (is_type_float(src) && is_type_quaternion(dst)) { - Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, false); - lbValue gp = lb_addr_get_ptr(p, gen); - lbValue real = lb_emit_conv(p, value, ft); - lb_emit_store(p, lb_emit_struct_ep(p, gp, 0), real); - return lb_addr_load(p, gen); - } if (is_type_complex(src) && is_type_complex(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, false); + lbAddr gen = lb_add_local_generated(p, t, false); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, lb_emit_struct_ev(p, value, 0), ft); lbValue imag = lb_emit_conv(p, lb_emit_struct_ev(p, value, 1), ft); @@ -1686,7 +1691,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_quaternion(src) && is_type_quaternion(dst)) { // @QuaternionLayout Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, false); + lbAddr gen = lb_add_local_generated(p, t, false); lbValue gp = lb_addr_get_ptr(p, gen); lbValue q0 = lb_emit_conv(p, lb_emit_struct_ev(p, value, 0), ft); lbValue q1 = lb_emit_conv(p, lb_emit_struct_ev(p, value, 1), ft); @@ -1701,7 +1706,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_integer(src) && is_type_complex(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, true); + lbAddr gen = lb_add_local_generated(p, t, true); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, value, ft); lb_emit_store(p, lb_emit_struct_ep(p, gp, 0), real); @@ -1709,7 +1714,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } if (is_type_float(src) && is_type_complex(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, true); + lbAddr gen = lb_add_local_generated(p, t, true); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, value, ft); lb_emit_store(p, lb_emit_struct_ep(p, gp, 0), real); @@ -1719,7 +1724,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_integer(src) && is_type_quaternion(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, true); + lbAddr gen = lb_add_local_generated(p, t, true); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, value, ft); // @QuaternionLayout @@ -1728,7 +1733,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } if (is_type_float(src) && is_type_quaternion(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, true); + lbAddr gen = lb_add_local_generated(p, t, true); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, value, ft); // @QuaternionLayout @@ -1737,7 +1742,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } if (is_type_complex(src) && is_type_quaternion(dst)) { Type *ft = base_complex_elem_type(dst); - lbAddr gen = lb_add_local_generated(p, dst, true); + lbAddr gen = lb_add_local_generated(p, t, true); lbValue gp = lb_addr_get_ptr(p, gen); lbValue real = lb_emit_conv(p, lb_emit_struct_ev(p, value, 0), ft); lbValue imag = lb_emit_conv(p, lb_emit_struct_ev(p, value, 1), ft); @@ -1850,6 +1855,15 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return lb_addr_load(p, parent); } } + if (dst->Union.variants.count == 1) { + Type *vt = dst->Union.variants[0]; + if (internal_check_is_assignable_to(src, vt)) { + value = lb_emit_conv(p, value, vt); + lbAddr parent = lb_add_local_generated(p, t, true); + lb_emit_store_union_variant(p, parent.addr, value, vt); + return lb_addr_load(p, parent); + } + } } // NOTE(bill): This has to be done before 'Pointer <-> Pointer' as it's @@ -2188,6 +2202,21 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri } } + if (is_type_matrix(a) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) { + Type *tl = base_type(a); + lbValue lhs = lb_address_from_load_or_generate_local(p, left); + lbValue rhs = lb_address_from_load_or_generate_local(p, right); + + + // TODO(bill): Test to see if this is actually faster!!!! + auto args = array_make<lbValue>(permanent_allocator(), 3); + args[0] = lb_emit_conv(p, lhs, t_rawptr); + args[1] = lb_emit_conv(p, rhs, t_rawptr); + args[2] = lb_const_int(p->module, t_int, type_size_of(tl)); + lbValue val = lb_emit_runtime_call(p, "memory_compare", args); + lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type)); + return lb_emit_conv(p, res, t_bool); + } if (is_type_array(a) || is_type_enumerated_array(a)) { Type *tl = base_type(a); lbValue lhs = lb_address_from_load_or_generate_local(p, left); @@ -2784,27 +2813,38 @@ lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { Type *src_type = type_deref(v.type); Type *dst_type = type; - lbValue src_tag = {}; - lbValue dst_tag = {}; - if (is_type_union_maybe_pointer(src_type)) { - src_tag = lb_emit_comp_against_nil(p, Token_NotEq, v); - dst_tag = lb_const_bool(p->module, t_bool, true); - } else { - src_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, v)); - dst_tag = lb_const_union_tag(p->module, src_type, dst_type); - } - lbValue ok = lb_emit_comp(p, Token_CmpEq, src_tag, dst_tag); - auto args = array_make<lbValue>(permanent_allocator(), 6); - args[0] = ok; + if ((p->state_flags & StateFlag_no_type_assert) == 0) { + lbValue src_tag = {}; + lbValue dst_tag = {}; + if (is_type_union_maybe_pointer(src_type)) { + src_tag = lb_emit_comp_against_nil(p, Token_NotEq, v); + dst_tag = lb_const_bool(p->module, t_bool, true); + } else { + src_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, v)); + dst_tag = lb_const_union_tag(p->module, src_type, dst_type); + } + - args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(p->module, t_i32, pos.line); - args[3] = lb_const_int(p->module, t_i32, pos.column); + isize arg_count = 6; + if (build_context.disallow_rtti) { + arg_count = 4; + } + + lbValue ok = lb_emit_comp(p, Token_CmpEq, src_tag, dst_tag); + auto args = array_make<lbValue>(permanent_allocator(), arg_count); + args[0] = ok; - args[4] = lb_typeid(p->module, src_type); - args[5] = lb_typeid(p->module, dst_type); - lb_emit_runtime_call(p, "type_assertion_check", args); + args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(p->module, t_i32, pos.line); + args[3] = lb_const_int(p->module, t_i32, pos.column); + + if (!build_context.disallow_rtti) { + args[4] = lb_typeid(p->module, src_type); + args[5] = lb_typeid(p->module, dst_type); + } + lb_emit_runtime_call(p, "type_assertion_check", args); + } lbValue data_ptr = v; return lb_emit_conv(p, data_ptr, tv.type); @@ -2813,23 +2853,25 @@ lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { if (is_type_pointer(v.type)) { v = lb_emit_load(p, v); } - lbValue data_ptr = lb_emit_struct_ev(p, v, 0); - lbValue any_id = lb_emit_struct_ev(p, v, 1); - lbValue id = lb_typeid(p->module, type); + if ((p->state_flags & StateFlag_no_type_assert) == 0) { + GB_ASSERT(!build_context.disallow_rtti); + lbValue any_id = lb_emit_struct_ev(p, v, 1); - lbValue ok = lb_emit_comp(p, Token_CmpEq, any_id, id); - auto args = array_make<lbValue>(permanent_allocator(), 6); - args[0] = ok; + lbValue id = lb_typeid(p->module, type); + lbValue ok = lb_emit_comp(p, Token_CmpEq, any_id, id); + auto args = array_make<lbValue>(permanent_allocator(), 6); + args[0] = ok; - args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(p->module, t_i32, pos.line); - args[3] = lb_const_int(p->module, t_i32, pos.column); + args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(p->module, t_i32, pos.line); + args[3] = lb_const_int(p->module, t_i32, pos.column); - args[4] = any_id; - args[5] = id; - lb_emit_runtime_call(p, "type_assertion_check", args); + args[4] = any_id; + args[5] = id; + lb_emit_runtime_call(p, "type_assertion_check", args); + } return lb_emit_conv(p, data_ptr, tv.type); } else { @@ -2859,6 +2901,14 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { out &= ~StateFlag_bounds_check; } + if (in & StateFlag_type_assert) { + out |= StateFlag_type_assert; + out &= ~StateFlag_no_type_assert; + } else if (in & StateFlag_no_type_assert) { + out |= StateFlag_no_type_assert; + out &= ~StateFlag_type_assert; + } + p->state_flags = out; } @@ -2978,7 +3028,7 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { lbBlock *done = lb_create_block(p, "if.done"); // NOTE(bill): Append later lbBlock *else_ = lb_create_block(p, "if.else"); - lbValue cond = lb_build_cond(p, te->cond, then, else_); + lb_build_cond(p, te->cond, then, else_); lb_start_block(p, then); Type *type = default_type(type_of_expr(expr)); @@ -3279,9 +3329,9 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { case_end; case_ast_node(se, SelectorExpr, expr); - Ast *sel = unparen_expr(se->selector); - if (sel->kind == Ast_Ident) { - String selector = sel->Ident.token.string; + Ast *sel_node = unparen_expr(se->selector); + if (sel_node->kind == Ast_Ident) { + String selector = sel_node->Ident.token.string; TypeAndValue tav = type_and_value_of_expr(se->expr); if (tav.mode == Addressing_Invalid) { @@ -3296,7 +3346,12 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { Type *type = base_type(tav.type); if (tav.mode == Addressing_Type) { // Addressing_Type - GB_PANIC("Unreachable"); + Selection sel = lookup_field(tav.type, selector, true); + if (sel.pseudo_field) { + GB_ASSERT(sel.entity->kind == Entity_Procedure); + return lb_addr(lb_find_value_from_entity(p->module, sel.entity)); + } + GB_PANIC("Unreachable %.*s", LIT(selector)); } if (se->swizzle_count > 0) { @@ -3323,6 +3378,11 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { Selection sel = lookup_field(type, selector, false); GB_ASSERT(sel.entity != nullptr); + if (sel.pseudo_field) { + GB_ASSERT(sel.entity->kind == Entity_Procedure); + Entity *e = entity_of_node(sel_node); + return lb_addr(lb_find_value_from_entity(p->module, e)); + } { lbAddr addr = lb_build_addr(p, se->expr); @@ -3476,7 +3536,8 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { GB_ASSERT_MSG(is_type_indexable(t), "%s %s", type_to_string(t), expr_to_string(expr)); if (is_type_map(t)) { - lbValue map_val = lb_build_addr_ptr(p, ie->expr); + lbAddr map_addr = lb_build_addr(p, ie->expr); + lbValue map_val = lb_addr_load(p, map_addr); if (deref) { map_val = lb_emit_load(p, map_val); } @@ -3485,7 +3546,8 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { key = lb_emit_conv(p, key, t->Map.key); Type *result_type = type_of_expr(expr); - return lb_addr_map(map_val, key, t, result_type); + lbValue map_ptr = lb_address_from_load_or_generate_local(p, map_val); + return lb_addr_map(map_ptr, key, t, result_type); } switch (t->kind) { @@ -4584,7 +4646,7 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { lbBlock *done = lb_create_block(p, "if.done"); // NOTE(bill): Append later lbBlock *else_ = lb_create_block(p, "if.else"); - lbValue cond = lb_build_cond(p, te->cond, then, else_); + lb_build_cond(p, te->cond, then, else_); lb_start_block(p, then); Type *ptr_type = alloc_type_pointer(default_type(type_of_expr(expr))); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 17eeb0bea..1a431a4ac 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -72,6 +72,9 @@ void lb_init_module(lbModule *m, Checker *c) { map_init(&m->debug_values, a); array_init(&m->debug_incomplete_types, a, 0, 1024); + + string_map_init(&m->objc_classes, a); + string_map_init(&m->objc_selectors, a); } bool lb_init_generator(lbGenerator *gen, Checker *c) { @@ -84,7 +87,6 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) { return false; } - String init_fullpath = c->parser->init_fullpath; if (build_context.out_filepath.len == 0) { @@ -271,6 +273,10 @@ lbAddr lb_addr(lbValue addr) { lbAddr lb_addr_map(lbValue addr, lbValue map_key, Type *map_type, Type *map_result) { + GB_ASSERT(is_type_pointer(addr.type)); + Type *mt = type_deref(addr.type); + GB_ASSERT(is_type_map(mt)); + lbAddr v = {lbAddr_Map, addr}; v.map.key = map_key; v.map.type = map_type; @@ -1169,10 +1175,35 @@ void lb_emit_store_union_variant_tag(lbProcedure *p, lbValue parent, Type *varia } void lb_emit_store_union_variant(lbProcedure *p, lbValue parent, lbValue variant, Type *variant_type) { - lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + Type *pt = base_type(type_deref(parent.type)); + GB_ASSERT(pt->kind == Type_Union); + if (pt->Union.kind == UnionType_shared_nil) { + lbBlock *if_nil = lb_create_block(p, "shared_nil.if_nil"); + lbBlock *if_not_nil = lb_create_block(p, "shared_nil.if_not_nil"); + lbBlock *done = lb_create_block(p, "shared_nil.done"); + + lbValue cond_is_nil = lb_emit_comp_against_nil(p, Token_CmpEq, variant); + lb_emit_if(p, cond_is_nil, if_nil, if_not_nil); + + lb_start_block(p, if_nil); + lb_emit_store(p, parent, lb_const_nil(p->module, type_deref(parent.type))); + lb_emit_jump(p, done); + + lb_start_block(p, if_not_nil); + lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + lb_emit_store(p, underlying, variant); + lb_emit_store_union_variant_tag(p, parent, variant_type); + lb_emit_jump(p, done); + + lb_start_block(p, done); - lb_emit_store(p, underlying, variant); - lb_emit_store_union_variant_tag(p, parent, variant_type); + + } else { + lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + + lb_emit_store(p, underlying, variant); + lb_emit_store_union_variant_tag(p, parent, variant_type); + } } @@ -1598,8 +1629,9 @@ LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { return llvm_type; } llvm_type = LLVMStructCreateNamed(ctx, name); + LLVMTypeRef found_val = *found; map_set(&m->types, type, llvm_type); - lb_clone_struct_type(llvm_type, *found); + lb_clone_struct_type(llvm_type, found_val); return llvm_type; } } @@ -2304,7 +2336,7 @@ LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String const &str) { LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(data), name); LLVMSetInitializer(global_data, data); - LLVMSetLinkage(global_data, LLVMInternalLinkage); + LLVMSetLinkage(global_data, LLVMPrivateLinkage); LLVMValueRef ptr = LLVMConstInBoundsGEP(global_data, indices, 2); string_map_set(&m->const_strings, key, ptr); @@ -2346,7 +2378,7 @@ lbValue lb_find_or_add_entity_string_byte_slice(lbModule *m, String const &str) } LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(data), name); LLVMSetInitializer(global_data, data); - LLVMSetLinkage(global_data, LLVMInternalLinkage); + LLVMSetLinkage(global_data, LLVMPrivateLinkage); LLVMValueRef ptr = nullptr; if (str.len != 0) { @@ -2445,7 +2477,7 @@ lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) { return {}; } -lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value) { +lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value, Entity **entity_) { GB_ASSERT(type != nullptr); type = default_type(type); @@ -2471,6 +2503,9 @@ lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value) { lb_add_entity(m, e, g); lb_add_member(m, name, g); + + if (entity_) *entity_ = e; + return lb_addr(g); } @@ -2579,7 +2614,7 @@ lbValue lb_generate_global_array(lbModule *m, Type *elem_type, i64 count, String g.value = LLVMAddGlobal(m->mod, lb_type(m, t), text); g.type = alloc_type_pointer(t); LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g.value, LLVMInternalLinkage); + LLVMSetLinkage(g.value, LLVMPrivateLinkage); string_map_set(&m->members, s, g); return g; } @@ -2591,6 +2626,9 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f GB_ASSERT(true_block != nullptr); GB_ASSERT(false_block != nullptr); + // Use to signal not to do compile time short circuit for consts + lbValue no_comptime_short_circuit = {}; + switch (cond->kind) { case_ast_node(pe, ParenExpr, cond); return lb_build_cond(p, pe->expr, true_block, false_block); @@ -2598,7 +2636,11 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f case_ast_node(ue, UnaryExpr, cond); if (ue->op.kind == Token_Not) { - return lb_build_cond(p, ue->expr, false_block, true_block); + lbValue cond_val = lb_build_cond(p, ue->expr, false_block, true_block); + if (cond_val.value && LLVMIsConstant(cond_val.value)) { + return lb_const_bool(p->module, cond_val.type, LLVMConstIntGetZExtValue(cond_val.value) == 0); + } + return no_comptime_short_circuit; } case_end; @@ -2607,12 +2649,14 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f lbBlock *block = lb_create_block(p, "cmp.and"); lb_build_cond(p, be->left, block, false_block); lb_start_block(p, block); - return lb_build_cond(p, be->right, true_block, false_block); + lb_build_cond(p, be->right, true_block, false_block); + return no_comptime_short_circuit; } else if (be->op.kind == Token_CmpOr) { lbBlock *block = lb_create_block(p, "cmp.or"); lb_build_cond(p, be->left, true_block, block); lb_start_block(p, block); - return lb_build_cond(p, be->right, true_block, false_block); + lb_build_cond(p, be->right, true_block, false_block); + return no_comptime_short_circuit; } case_end; } diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index de925655f..d36bdec0b 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -48,12 +48,6 @@ LLVMBool lb_must_preserve_predicate_callback(LLVMValueRef value, void *user_data return LLVMIsAAllocaInst(value) != nullptr; } -void lb_add_must_preserve_predicate_pass(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) { - if (false && optimization_level == 0 && m->debug_builder) { - // LLVMAddInternalizePassWithMustPreservePredicate(fpm, m, lb_must_preserve_predicate_callback); - } -} - #if LLVM_VERSION_MAJOR < 12 #define LLVM_ADD_CONSTANT_VALUE_PASS(fpm) LLVMAddConstantPropagationPass(fpm) @@ -61,16 +55,15 @@ void lb_add_must_preserve_predicate_pass(lbModule *m, LLVMPassManagerRef fpm, i3 #define LLVM_ADD_CONSTANT_VALUE_PASS(fpm) #endif -void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) { - LLVMAddPromoteMemoryToRegisterPass(fpm); - LLVMAddMergedLoadStoreMotionPass(fpm); - LLVM_ADD_CONSTANT_VALUE_PASS(fpm); - LLVMAddEarlyCSEPass(fpm); - - // LLVM_ADD_CONSTANT_VALUE_PASS(fpm); - // LLVMAddMergedLoadStoreMotionPass(fpm); - // LLVMAddPromoteMemoryToRegisterPass(fpm); - // LLVMAddCFGSimplificationPass(fpm); +void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm, i32 optimization_level) { + if (false && optimization_level == 0 && build_context.ODIN_DEBUG) { + LLVMAddMergedLoadStoreMotionPass(fpm); + } else { + LLVMAddPromoteMemoryToRegisterPass(fpm); + LLVMAddMergedLoadStoreMotionPass(fpm); + LLVM_ADD_CONSTANT_VALUE_PASS(fpm); + LLVMAddEarlyCSEPass(fpm); + } } void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) { @@ -78,14 +71,12 @@ void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool // TODO(bill): Determine which opt definitions should exist in the first place optimization_level = gb_clamp(optimization_level, 0, 2); - lb_add_must_preserve_predicate_pass(m, fpm, optimization_level); - if (ignore_memcpy_pass) { - lb_basic_populate_function_pass_manager(fpm); + lb_basic_populate_function_pass_manager(fpm, optimization_level); return; } else if (optimization_level == 0) { LLVMAddMemCpyOptPass(fpm); - lb_basic_populate_function_pass_manager(fpm); + lb_basic_populate_function_pass_manager(fpm, optimization_level); return; } @@ -96,7 +87,7 @@ void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool LLVMPassManagerBuilderPopulateFunctionPassManager(pmb, fpm); #else LLVMAddMemCpyOptPass(fpm); - lb_basic_populate_function_pass_manager(fpm); + lb_basic_populate_function_pass_manager(fpm, optimization_level); LLVMAddSCCPPass(fpm); @@ -114,11 +105,9 @@ void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef // TODO(bill): Determine which opt definitions should exist in the first place optimization_level = gb_clamp(optimization_level, 0, 2); - lb_add_must_preserve_predicate_pass(m, fpm, optimization_level); - if (optimization_level == 0) { LLVMAddMemCpyOptPass(fpm); - lb_basic_populate_function_pass_manager(fpm); + lb_basic_populate_function_pass_manager(fpm, optimization_level); return; } @@ -191,6 +180,9 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa // NOTE(bill): Treat -opt:3 as if it was -opt:2 // TODO(bill): Determine which opt definitions should exist in the first place optimization_level = gb_clamp(optimization_level, 0, 2); + if (optimization_level == 0 && build_context.ODIN_DEBUG) { + return; + } LLVMAddAlwaysInlinerPass(mpm); LLVMAddStripDeadPrototypesPass(mpm); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 25b27ee47..96ff19d10 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -57,11 +57,12 @@ void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValue src, lbVal LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); } + lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) { GB_ASSERT(entity != nullptr); GB_ASSERT(entity->kind == Entity_Procedure); if (!entity->Procedure.is_foreign) { - GB_ASSERT(entity->flags & EntityFlag_ProcBodyChecked); + GB_ASSERT_MSG(entity->flags & EntityFlag_ProcBodyChecked, "%.*s :: %s", LIT(entity->token.string), type_to_string(entity->type)); } String link_name = {}; @@ -107,7 +108,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) gbAllocator a = heap_allocator(); p->children.allocator = a; - p->params.allocator = a; p->defer_stmts.allocator = a; p->blocks.allocator = a; p->branch_blocks.allocator = a; @@ -135,6 +135,10 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) lb_add_attribute_to_proc(m, p->value, "naked"); } + if (!entity->Procedure.is_foreign && build_context.disable_red_zone) { + lb_add_attribute_to_proc(m, p->value, "noredzone"); + } + switch (p->inlining) { case ProcInlining_inline: lb_add_attribute_to_proc(m, p->value, "alwaysinline"); @@ -164,14 +168,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) break; } - - - // lbCallingConventionKind cc_kind = lbCallingConvention_C; - // // TODO(bill): Clean up this logic - // if (build_context.metrics.os != TargetOs_js) { - // cc_kind = lb_calling_convention_map[pt->Proc.calling_convention]; - // } - // LLVMSetFunctionCallConv(p->value, cc_kind); lbValue proc_value = {p->value, p->type}; lb_add_entity(m, entity, proc_value); lb_add_member(m, p->name, proc_value); @@ -304,7 +300,7 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name, Type *type) { { lbValue *found = string_map_get(&m->members, link_name); - GB_ASSERT(found == nullptr); + GB_ASSERT_MSG(found == nullptr, "failed to create dummy procedure for: %.*s", LIT(link_name)); } lbProcedure *p = gb_alloc_item(permanent_allocator(), lbProcedure); @@ -323,7 +319,6 @@ lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name, Type *type gbAllocator a = permanent_allocator(); p->children.allocator = a; - p->params.allocator = a; p->defer_stmts.allocator = a; p->blocks.allocator = a; p->branch_blocks.allocator = a; @@ -452,7 +447,7 @@ void lb_begin_procedure_body(lbProcedure *p) { Type *ptr_type = alloc_type_pointer(reduce_tuple_to_single_type(p->type->Proc.results)); Entity *e = alloc_entity_param(nullptr, make_token_ident(name), ptr_type, false, false); - e->flags |= EntityFlag_Sret | EntityFlag_NoAlias; + e->flags |= EntityFlag_NoAlias; return_ptr_value.value = LLVMGetParam(p->value, 0); LLVMSetValueName2(return_ptr_value.value, cast(char const *)name.text, name.len); @@ -475,44 +470,42 @@ void lb_begin_procedure_body(lbProcedure *p) { } lbArgType *arg_type = &ft->args[param_index]; + defer (param_index += 1); + if (arg_type->kind == lbArg_Ignore) { continue; } else if (arg_type->kind == lbArg_Direct) { - lbParamPasskind kind = lbParamPass_Value; - LLVMTypeRef param_type = lb_type(p->module, e->type); - if (param_type != arg_type->type) { - kind = lbParamPass_BitCast; - } - LLVMValueRef value = LLVMGetParam(p->value, param_offset+param_index); + if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { + LLVMTypeRef param_type = lb_type(p->module, e->type); + LLVMValueRef original_value = LLVMGetParam(p->value, param_offset+param_index); + LLVMValueRef value = OdinLLVMBuildTransmute(p, original_value, param_type); - value = OdinLLVMBuildTransmute(p, value, param_type); + lbValue param = {}; + param.value = value; + param.type = e->type; - lbValue param = {}; - param.value = value; - param.type = e->type; - array_add(&p->params, param); + lbValue ptr = lb_address_from_load_or_generate_local(p, param); + GB_ASSERT(LLVMIsAAllocaInst(ptr.value)); + lb_add_entity(p->module, e, ptr); - if (e->token.string.len != 0) { - lbAddr l = lb_add_local(p, e->type, e, false, param_index); - lb_addr_store(p, l, param); + lbBlock *block = p->decl_block; + if (original_value != value) { + block = p->curr_block; + } + LLVMValueRef debug_storage_value = value; + if (original_value != value && LLVMIsALoadInst(value)) { + debug_storage_value = LLVMGetOperand(value, 0); + } + lb_add_debug_param_variable(p, debug_storage_value, e->type, e->token, param_index+1, block); } - - param_index += 1; } else if (arg_type->kind == lbArg_Indirect) { - LLVMValueRef value_ptr = LLVMGetParam(p->value, param_offset+param_index); - LLVMValueRef value = LLVMBuildLoad(p->builder, value_ptr, ""); - - lbValue param = {}; - param.value = value; - param.type = e->type; - array_add(&p->params, param); - - lbValue ptr = {}; - ptr.value = value_ptr; - ptr.type = alloc_type_pointer(e->type); - - lb_add_entity(p->module, e, ptr); - param_index += 1; + if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { + lbValue ptr = {}; + ptr.value = LLVMGetParam(p->value, param_offset+param_index); + ptr.type = alloc_type_pointer(e->type); + lb_add_entity(p->module, e, ptr); + lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block); + } } } } @@ -736,6 +729,10 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, LLVMValueRef ret = LLVMBuildCall2(p->builder, fnp, fn, args, arg_count, ""); + if (return_ptr.value != nullptr) { + LLVMAddCallSiteAttribute(ret, 1, lb_create_enum_attribute_with_type(p->module->ctx, "sret", LLVMTypeOf(args[0]))); + } + switch (inlining) { case ProcInlining_none: break; @@ -1049,7 +1046,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return lb_string_len(p, v); } else if (is_type_array(t)) { GB_PANIC("Array lengths are constant"); - } else if (is_type_slice(t)) { + } else if (is_type_slice(t) || is_type_relative_slice(t)) { return lb_slice_len(p, v); } else if (is_type_dynamic_array(t)) { return lb_dynamic_array_len(p, v); @@ -1075,7 +1072,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, GB_PANIC("Unreachable"); } else if (is_type_array(t)) { GB_PANIC("Array lengths are constant"); - } else if (is_type_slice(t)) { + } else if (is_type_slice(t) || is_type_relative_slice(t)) { return lb_slice_len(p, v); } else if (is_type_dynamic_array(t)) { return lb_dynamic_array_cap(p, v); @@ -1372,15 +1369,23 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } case BuiltinProc_cpu_relax: - if (build_context.metrics.arch == TargetArch_386 || + if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) { LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); - LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("pause"), {}); + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("pause"), {}, true); GB_ASSERT(the_asm != nullptr); LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); } else if (build_context.metrics.arch == TargetArch_arm64) { LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); - LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("yield"), {}); + // NOTE(bill, 2022-03-30): `isb` appears to a better option that `yield` + // See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604 + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("isb"), {}, true); + GB_ASSERT(the_asm != nullptr); + LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + } else { + // NOTE: default to something to prevent optimization + LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit(""), {}, true); GB_ASSERT(the_asm != nullptr); LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); } @@ -1409,14 +1414,23 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, case BuiltinProc_read_cycle_counter: { - char const *name = "llvm.readcyclecounter"; - unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); - GB_ASSERT_MSG(id != 0, "Unable to find %s", name); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0); - lbValue res = {}; - res.value = LLVMBuildCall(p->builder, ip, nullptr, 0, ""); res.type = tv.type; + + if (build_context.metrics.arch == TargetArch_arm64) { + LLVMTypeRef func_type = LLVMFunctionType(LLVMInt64TypeInContext(p->module->ctx), nullptr, 0, false); + bool has_side_effects = false; + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs x9, cntvct_el0"), str_lit("=r"), has_side_effects); + GB_ASSERT(the_asm != nullptr); + res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + } else { + char const *name = "llvm.readcyclecounter"; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0); + + res.value = LLVMBuildCall(p->builder, ip, nullptr, 0, ""); + } return res; } @@ -1592,36 +1606,26 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } - - case BuiltinProc_atomic_fence: - LLVMBuildFence(p->builder, LLVMAtomicOrderingSequentiallyConsistent, false, ""); - return {}; - case BuiltinProc_atomic_fence_acq: - LLVMBuildFence(p->builder, LLVMAtomicOrderingAcquire, false, ""); + // TODO(bill): Which is correct? + case BuiltinProc_atomic_thread_fence: + LLVMBuildFence(p->builder, llvm_atomic_ordering_from_odin(ce->args[0]), false, ""); return {}; - case BuiltinProc_atomic_fence_rel: - LLVMBuildFence(p->builder, LLVMAtomicOrderingRelease, false, ""); - return {}; - case BuiltinProc_atomic_fence_acqrel: - LLVMBuildFence(p->builder, LLVMAtomicOrderingAcquireRelease, false, ""); + case BuiltinProc_atomic_signal_fence: + LLVMBuildFence(p->builder, llvm_atomic_ordering_from_odin(ce->args[0]), true, ""); return {}; case BuiltinProc_volatile_store: case BuiltinProc_atomic_store: - case BuiltinProc_atomic_store_rel: - case BuiltinProc_atomic_store_relaxed: - case BuiltinProc_atomic_store_unordered: { + case BuiltinProc_atomic_store_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); lbValue val = lb_build_expr(p, ce->args[1]); val = lb_emit_conv(p, val, type_deref(dst.type)); LLVMValueRef instr = LLVMBuildStore(p->builder, val.value, dst.value); switch (id) { - case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; - case BuiltinProc_atomic_store_rel: LLVMSetOrdering(instr, LLVMAtomicOrderingRelease); break; - case BuiltinProc_atomic_store_relaxed: LLVMSetOrdering(instr, LLVMAtomicOrderingMonotonic); break; - case BuiltinProc_atomic_store_unordered: LLVMSetOrdering(instr, LLVMAtomicOrderingUnordered); break; + case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; + case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_atomic_store_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[2])); break; } LLVMSetAlignment(instr, cast(unsigned)type_align_of(type_deref(dst.type))); @@ -1631,18 +1635,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, case BuiltinProc_volatile_load: case BuiltinProc_atomic_load: - case BuiltinProc_atomic_load_acq: - case BuiltinProc_atomic_load_relaxed: - case BuiltinProc_atomic_load_unordered: { + case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); LLVMValueRef instr = LLVMBuildLoad(p->builder, dst.value, ""); switch (id) { - case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; - case BuiltinProc_atomic_load_acq: LLVMSetOrdering(instr, LLVMAtomicOrderingAcquire); break; - case BuiltinProc_atomic_load_relaxed: LLVMSetOrdering(instr, LLVMAtomicOrderingMonotonic); break; - case BuiltinProc_atomic_load_unordered: LLVMSetOrdering(instr, LLVMAtomicOrderingUnordered); break; + case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; + case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_atomic_load_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[1])); break; } LLVMSetAlignment(instr, cast(unsigned)type_align_of(type_deref(dst.type))); @@ -1672,40 +1672,19 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } case BuiltinProc_atomic_add: - case BuiltinProc_atomic_add_acq: - case BuiltinProc_atomic_add_rel: - case BuiltinProc_atomic_add_acqrel: - case BuiltinProc_atomic_add_relaxed: case BuiltinProc_atomic_sub: - case BuiltinProc_atomic_sub_acq: - case BuiltinProc_atomic_sub_rel: - case BuiltinProc_atomic_sub_acqrel: - case BuiltinProc_atomic_sub_relaxed: case BuiltinProc_atomic_and: - case BuiltinProc_atomic_and_acq: - case BuiltinProc_atomic_and_rel: - case BuiltinProc_atomic_and_acqrel: - case BuiltinProc_atomic_and_relaxed: case BuiltinProc_atomic_nand: - case BuiltinProc_atomic_nand_acq: - case BuiltinProc_atomic_nand_rel: - case BuiltinProc_atomic_nand_acqrel: - case BuiltinProc_atomic_nand_relaxed: case BuiltinProc_atomic_or: - case BuiltinProc_atomic_or_acq: - case BuiltinProc_atomic_or_rel: - case BuiltinProc_atomic_or_acqrel: - case BuiltinProc_atomic_or_relaxed: case BuiltinProc_atomic_xor: - case BuiltinProc_atomic_xor_acq: - case BuiltinProc_atomic_xor_rel: - case BuiltinProc_atomic_xor_acqrel: - case BuiltinProc_atomic_xor_relaxed: - case BuiltinProc_atomic_xchg: - case BuiltinProc_atomic_xchg_acq: - case BuiltinProc_atomic_xchg_rel: - case BuiltinProc_atomic_xchg_acqrel: - case BuiltinProc_atomic_xchg_relaxed: { + case BuiltinProc_atomic_exchange: + case BuiltinProc_atomic_add_explicit: + case BuiltinProc_atomic_sub_explicit: + case BuiltinProc_atomic_and_explicit: + case BuiltinProc_atomic_nand_explicit: + case BuiltinProc_atomic_or_explicit: + case BuiltinProc_atomic_xor_explicit: + case BuiltinProc_atomic_exchange_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); lbValue val = lb_build_expr(p, ce->args[1]); val = lb_emit_conv(p, val, type_deref(dst.type)); @@ -1714,41 +1693,20 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMAtomicOrdering ordering = {}; switch (id) { - case BuiltinProc_atomic_add: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_add_acq: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_add_rel: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_add_acqrel: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_add_relaxed: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_sub: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_sub_acq: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_sub_rel: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_sub_acqrel: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_sub_relaxed: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_and: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_and_acq: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_and_rel: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_and_acqrel: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_and_relaxed: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_nand: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_nand_acq: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_nand_rel: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_nand_acqrel: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_nand_relaxed: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_or: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_or_acq: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_or_rel: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_or_acqrel: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_or_relaxed: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_xor: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_xor_acq: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_xor_rel: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_xor_acqrel: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_xor_relaxed: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_xchg: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_xchg_acq: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_xchg_rel: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_xchg_acqrel: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_xchg_relaxed: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingMonotonic; break; + case BuiltinProc_atomic_add: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_sub: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_and: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_nand: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_or: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_xor: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_exchange: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_add_explicit: op = LLVMAtomicRMWBinOpAdd; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_sub_explicit: op = LLVMAtomicRMWBinOpSub; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_and_explicit: op = LLVMAtomicRMWBinOpAnd; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_nand_explicit: op = LLVMAtomicRMWBinOpNand; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_or_explicit: op = LLVMAtomicRMWBinOpOr; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_xor_explicit: op = LLVMAtomicRMWBinOpXor; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_exchange_explicit: op = LLVMAtomicRMWBinOpXchg; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; } lbValue res = {}; @@ -1757,24 +1715,10 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } - case BuiltinProc_atomic_cxchg: - case BuiltinProc_atomic_cxchg_acq: - case BuiltinProc_atomic_cxchg_rel: - case BuiltinProc_atomic_cxchg_acqrel: - case BuiltinProc_atomic_cxchg_relaxed: - case BuiltinProc_atomic_cxchg_failrelaxed: - case BuiltinProc_atomic_cxchg_failacq: - case BuiltinProc_atomic_cxchg_acq_failrelaxed: - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: - case BuiltinProc_atomic_cxchgweak: - case BuiltinProc_atomic_cxchgweak_acq: - case BuiltinProc_atomic_cxchgweak_rel: - case BuiltinProc_atomic_cxchgweak_acqrel: - case BuiltinProc_atomic_cxchgweak_relaxed: - case BuiltinProc_atomic_cxchgweak_failrelaxed: - case BuiltinProc_atomic_cxchgweak_failacq: - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: { + case BuiltinProc_atomic_compare_exchange_strong: + case BuiltinProc_atomic_compare_exchange_weak: + case BuiltinProc_atomic_compare_exchange_strong_explicit: + case BuiltinProc_atomic_compare_exchange_weak_explicit: { lbValue address = lb_build_expr(p, ce->args[0]); Type *elem = type_deref(address.type); lbValue old_value = lb_build_expr(p, ce->args[1]); @@ -1787,28 +1731,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMBool weak = false; switch (id) { - case BuiltinProc_atomic_cxchg: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_acq: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_rel: success_ordering = LLVMAtomicOrderingRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_acqrel: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_relaxed: success_ordering = LLVMAtomicOrderingMonotonic; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_failrelaxed: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_failacq: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingAcquire; weak = false; break; - case BuiltinProc_atomic_cxchg_acq_failrelaxed: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchgweak: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchgweak_acq: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_rel: success_ordering = LLVMAtomicOrderingRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acqrel: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_relaxed: success_ordering = LLVMAtomicOrderingMonotonic; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_failrelaxed: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_failacq: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingAcquire; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; + case BuiltinProc_atomic_compare_exchange_strong: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; + case BuiltinProc_atomic_compare_exchange_weak: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; + case BuiltinProc_atomic_compare_exchange_strong_explicit: success_ordering = llvm_atomic_ordering_from_odin(ce->args[3]); failure_ordering = llvm_atomic_ordering_from_odin(ce->args[4]); weak = false; break; + case BuiltinProc_atomic_compare_exchange_weak_explicit: success_ordering = llvm_atomic_ordering_from_odin(ce->args[3]); failure_ordering = llvm_atomic_ordering_from_odin(ce->args[4]); weak = true; break; } // TODO(bill): Figure out how to make it weak - LLVMBool single_threaded = weak; + LLVMBool single_threaded = false; LLVMValueRef value = LLVMBuildAtomicCmpXchg( p->builder, address.value, @@ -1817,6 +1747,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, failure_ordering, single_threaded ); + LLVMSetWeak(value, weak); if (tv.type->kind == Type_Tuple) { Type *fix_typed = alloc_type_tuple(); @@ -1961,6 +1892,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc___entry_point: + if (p->module->info->entry_point) { + lbValue entry_point = lb_find_procedure_value_from_entity(p->module, p->module->info->entry_point); + GB_ASSERT(entry_point.value != nullptr); + lb_emit_call(p, entry_point, {}); + } + return {}; + case BuiltinProc_syscall: { unsigned arg_count = cast(unsigned)ce->args.count; @@ -2021,7 +1960,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; - case TargetArch_386: + case TargetArch_i386: { GB_ASSERT(arg_count <= 7); @@ -2054,26 +1993,47 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, break; case TargetArch_arm64: { - GB_ASSERT(arg_count <= 7); - - char asm_string[] = "svc #0"; - gbString constraints = gb_string_make(heap_allocator(), "={x0}"); - for (unsigned i = 0; i < arg_count; i++) { - constraints = gb_string_appendc(constraints, ",{"); - static char const *regs[] = { - "x8", - "x0", - "x1", - "x2", - "x3", - "x4", - "x5", - }; - constraints = gb_string_appendc(constraints, regs[i]); - constraints = gb_string_appendc(constraints, "}"); - } - - inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + GB_ASSERT(arg_count <= 7); + + if(build_context.metrics.os == TargetOs_darwin) { + char asm_string[] = "svc #0x80"; + gbString constraints = gb_string_make(heap_allocator(), "={x0}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "x16", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } else { + char asm_string[] = "svc #0"; + gbString constraints = gb_string_make(heap_allocator(), "={x0}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "x8", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } } break; default: @@ -2085,6 +2045,124 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, res.type = t_uintptr; return res; } + + case BuiltinProc_objc_send: + return lb_handle_objc_send(p, expr); + + case BuiltinProc_objc_find_selector: return lb_handle_objc_find_selector(p, expr); + case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr); + case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); + case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); + + + case BuiltinProc_constant_utf16_cstring: + { + auto const encode_surrogate_pair = [](Rune r, u16 *r1, u16 *r2) { + if (r < 0x10000 || r > 0x10ffff) { + *r1 = 0xfffd; + *r2 = 0xfffd; + } else { + r -= 0x10000; + *r1 = 0xd800 + ((r>>10)&0x3ff); + *r2 = 0xdc00 + (r&0x3ff); + } + }; + + lbModule *m = p->module; + + auto tav = type_and_value_of_expr(ce->args[0]); + GB_ASSERT(tav.value.kind == ExactValue_String); + String value = tav.value.value_string; + + LLVMTypeRef llvm_u16 = lb_type(m, t_u16); + + isize max_len = value.len*2 + 1; + LLVMValueRef *buffer = gb_alloc_array(temporary_allocator(), LLVMValueRef, max_len); + isize n = 0; + while (value.len > 0) { + Rune r = 0; + isize w = gb_utf8_decode(value.text, value.len, &r); + value.text += w; + value.len -= w; + if ((0 <= r && r < 0xd800) || (0xe000 <= r && r < 0x10000)) { + buffer[n++] = LLVMConstInt(llvm_u16, cast(u16)r, false); + } else if (0x10000 <= r && r <= 0x10ffff) { + u16 r1, r2; + encode_surrogate_pair(r, &r1, &r2); + buffer[n++] = LLVMConstInt(llvm_u16, r1, false); + buffer[n++] = LLVMConstInt(llvm_u16, r2, false); + } else { + buffer[n++] = LLVMConstInt(llvm_u16, 0xfffd, false); + } + } + + buffer[n++] = LLVMConstInt(llvm_u16, 0, false); + + LLVMValueRef array = LLVMConstArray(llvm_u16, buffer, cast(unsigned int)n); + + char *name = nullptr; + { + isize max_len = 7+8+1; + name = gb_alloc_array(permanent_allocator(), char, max_len); + u32 id = m->gen->global_array_index.fetch_add(1); + isize len = gb_snprintf(name, max_len, "csbs$%x", id); + len -= 1; + } + LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(array), name); + LLVMSetInitializer(global_data, array); + LLVMSetLinkage(global_data, LLVMInternalLinkage); + + + + LLVMValueRef indices[] = { + LLVMConstInt(lb_type(m, t_u32), 0, false), + LLVMConstInt(lb_type(m, t_u32), 0, false), + }; + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildInBoundsGEP(p->builder, global_data, indices, gb_count_of(indices), ""); + return res; + + } + + case BuiltinProc_wasm_memory_grow: + { + char const *name = "llvm.wasm.memory.grow"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_uintptr), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[2] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_uintptr).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + case BuiltinProc_wasm_memory_size: + { + char const *name = "llvm.wasm.memory.size"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_uintptr), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name)); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 016e464b8..2afb5300b 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1652,13 +1652,16 @@ void lb_build_if_stmt(lbProcedure *p, Ast *node) { } lbValue cond = lb_build_cond(p, is->cond, then, else_); + // Note `cond.value` only set for non-and/or conditions and const negs so that the `LLVMIsConstant()` + // and `LLVMConstIntGetZExtValue()` calls below will be valid and `LLVMInstructionEraseFromParent()` + // will target the correct (& only) branch statement if (is->label != nullptr) { lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr); tl->is_block = true; } - if (LLVMIsConstant(cond.value)) { + if (cond.value && LLVMIsConstant(cond.value)) { // NOTE(bill): Do a compile time short circuit for when the condition is constantly known. // This done manually rather than relying on the SSA passes because sometimes the SSA passes // miss some even if they are constantly known, especially with few optimization passes. @@ -1766,6 +1769,8 @@ void lb_build_for_stmt(lbProcedure *p, Ast *node) { } void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, lbValue const &value) { + GB_ASSERT(op != Token_Eq); + Type *lhs_type = lb_addr_type(lhs); Type *array_type = base_type(lhs_type); GB_ASSERT(is_type_array_like(array_type)); @@ -1795,7 +1800,6 @@ void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, } indices[index_count++] = index; } - gb_sort_array(indices, index_count, gb_i32_cmp(0)); lbValue lhs_ptrs[4] = {}; lbValue x_loads[4] = {}; @@ -1840,7 +1844,6 @@ void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, } indices[index_count++] = index; } - gb_sort_array(indices.data, index_count, gb_i32_cmp(0)); lbValue lhs_ptrs[4] = {}; lbValue x_loads[4] = {}; @@ -1868,11 +1871,7 @@ void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, lbValue x = lb_addr_get_ptr(p, lhs); - - if (inline_array_arith) { - #if 1 - #if 1 unsigned n = cast(unsigned)count; auto lhs_ptrs = slice_make<lbValue>(temporary_allocator(), n); @@ -1896,50 +1895,6 @@ void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, for (unsigned i = 0; i < n; i++) { lb_emit_store(p, lhs_ptrs[i], ops[i]); } - - #else - lbValue y = lb_address_from_load_or_generate_local(p, rhs); - - unsigned n = cast(unsigned)count; - - auto lhs_ptrs = slice_make<lbValue>(temporary_allocator(), n); - auto rhs_ptrs = slice_make<lbValue>(temporary_allocator(), n); - auto x_loads = slice_make<lbValue>(temporary_allocator(), n); - auto y_loads = slice_make<lbValue>(temporary_allocator(), n); - auto ops = slice_make<lbValue>(temporary_allocator(), n); - - for (unsigned i = 0; i < n; i++) { - lhs_ptrs[i] = lb_emit_array_epi(p, x, i); - } - for (unsigned i = 0; i < n; i++) { - rhs_ptrs[i] = lb_emit_array_epi(p, y, i); - } - for (unsigned i = 0; i < n; i++) { - x_loads[i] = lb_emit_load(p, lhs_ptrs[i]); - } - for (unsigned i = 0; i < n; i++) { - y_loads[i] = lb_emit_load(p, rhs_ptrs[i]); - } - for (unsigned i = 0; i < n; i++) { - ops[i] = lb_emit_arith(p, op, x_loads[i], y_loads[i], elem_type); - } - for (unsigned i = 0; i < n; i++) { - lb_emit_store(p, lhs_ptrs[i], ops[i]); - } - #endif - #else - lbValue y = lb_address_from_load_or_generate_local(p, rhs); - - for (i64 i = 0; i < count; i++) { - lbValue a_ptr = lb_emit_array_epi(p, x, i); - lbValue b_ptr = lb_emit_array_epi(p, y, i); - - lbValue a = lb_emit_load(p, a_ptr); - lbValue b = lb_emit_load(p, b_ptr); - lbValue c = lb_emit_arith(p, op, a, b, elem_type); - lb_emit_store(p, a_ptr, c); - } - #endif } else { lbValue y = lb_address_from_load_or_generate_local(p, rhs); @@ -2039,6 +1994,13 @@ void lb_build_stmt(lbProcedure *p, Ast *node) { out |= StateFlag_no_bounds_check; out &= ~StateFlag_bounds_check; } + if (in & StateFlag_no_type_assert) { + out |= StateFlag_no_type_assert; + out &= ~StateFlag_type_assert; + } else if (in & StateFlag_type_assert) { + out |= StateFlag_type_assert; + out &= ~StateFlag_no_type_assert; + } p->state_flags = out; } @@ -2188,6 +2150,7 @@ void lb_build_stmt(lbProcedure *p, Ast *node) { lb_emit_defer_stmts(p, lbDeferExit_Branch, block); } lb_emit_jump(p, block); + lb_start_block(p, lb_create_block(p, "unreachable")); case_end; } } @@ -2219,10 +2182,6 @@ void lb_build_defer_stmt(lbProcedure *p, lbDefer const &d) { lb_start_block(p, b); if (d.kind == lbDefer_Node) { lb_build_stmt(p, d.stmt); - } else if (d.kind == lbDefer_Instr) { - // NOTE(bill): Need to make a new copy - LLVMValueRef instr = LLVMInstructionClone(d.instr.value); - LLVMInsertIntoBuilder(p->builder, instr); } else if (d.kind == lbDefer_Proc) { lb_emit_call(p, d.proc.deferred, d.proc.result_as_args); } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index decb57702..7d73956e8 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1,11 +1,10 @@ isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=true) { - isize index = type_info_index(info, type, false); + auto *set = &info->minimum_dependency_type_info_set; + isize index = type_info_index(info, type, err_on_not_found); if (index >= 0) { - auto *set = &info->minimum_dependency_type_info_set; - for_array(i, set->entries) { - if (set->entries[i].ptr == index) { - return i+1; - } + isize i = ptr_entry_index(set, index); + if (i >= 0) { + return i+1; } } if (err_on_not_found) { @@ -15,6 +14,8 @@ isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=tr } lbValue lb_typeid(lbModule *m, Type *type) { + GB_ASSERT(!build_context.disallow_rtti); + type = default_type(type); u64 id = cast(u64)lb_type_info_index(m->info, type); @@ -89,6 +90,8 @@ lbValue lb_typeid(lbModule *m, Type *type) { } lbValue lb_type_info(lbModule *m, Type *type) { + GB_ASSERT(!build_context.disallow_rtti); + type = default_type(type); isize index = lb_type_info_index(m->info, type); @@ -107,6 +110,8 @@ lbValue lb_type_info(lbModule *m, Type *type) { } lbValue lb_get_type_info_ptr(lbModule *m, Type *type) { + GB_ASSERT(!build_context.disallow_rtti); + i32 index = cast(i32)lb_type_info_index(m->info, type); GB_ASSERT(index >= 0); // gb_printf_err("%d %s\n", index, type_to_string(type)); @@ -156,6 +161,10 @@ lbValue lb_type_info_member_tags_offset(lbProcedure *p, isize count) { void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info data + if (build_context.disallow_rtti) { + return; + } + lbModule *m = p->module; CheckerInfo *info = m->info; @@ -455,7 +464,7 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da case Type_EnumeratedArray: { tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_enumerated_array_ptr); - LLVMValueRef vals[6] = { + LLVMValueRef vals[7] = { lb_get_type_info_ptr(m, t->EnumeratedArray.elem).value, lb_get_type_info_ptr(m, t->EnumeratedArray.index).value, lb_const_int(m, t_int, type_size_of(t->EnumeratedArray.elem)).value, @@ -464,6 +473,8 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da // Unions LLVMConstNull(lb_type(m, t_type_info_enum_value)), LLVMConstNull(lb_type(m, t_type_info_enum_value)), + + lb_const_bool(m, t_bool, t->EnumeratedArray.is_sparse).value, }; lbValue res = {}; @@ -630,7 +641,7 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_union_ptr); { - LLVMValueRef vals[7] = {}; + LLVMValueRef vals[8] = {}; isize variant_count = gb_max(0, t->Union.variants.count); lbValue memory_types = lb_type_info_member_types_offset(p, variant_count); @@ -664,8 +675,9 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da } vals[4] = lb_const_bool(m, t_bool, t->Union.custom_align != 0).value; - vals[5] = lb_const_bool(m, t_bool, t->Union.no_nil).value; - vals[6] = lb_const_bool(m, t_bool, t->Union.maybe).value; + vals[5] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_no_nil).value; + vals[6] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_maybe).value; + vals[7] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_shared_nil).value; for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 0350f7287..037171637 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -626,6 +626,12 @@ lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type, TokenPos p lbValue value_ = lb_address_from_load_or_generate_local(p, value); + if ((p->state_flags & StateFlag_no_type_assert) != 0 && !is_tuple) { + // just do a bit cast of the data at the front + lbValue ptr = lb_emit_conv(p, value_, alloc_type_pointer(type)); + return lb_emit_load(p, ptr); + } + lbValue tag = {}; lbValue dst_tag = {}; lbValue cond = {}; @@ -666,23 +672,29 @@ lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type, TokenPos p lb_start_block(p, end_block); if (!is_tuple) { - { - // NOTE(bill): Panic on invalid conversion - Type *dst_type = tuple->Tuple.variables[0]->type; + GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0); + // NOTE(bill): Panic on invalid conversion + Type *dst_type = tuple->Tuple.variables[0]->type; + + isize arg_count = 7; + if (build_context.disallow_rtti) { + arg_count = 4; + } - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - auto args = array_make<lbValue>(permanent_allocator(), 7); - args[0] = ok; + lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); + auto args = array_make<lbValue>(permanent_allocator(), arg_count); + args[0] = ok; - args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(m, t_i32, pos.line); - args[3] = lb_const_int(m, t_i32, pos.column); + args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(m, t_i32, pos.line); + args[3] = lb_const_int(m, t_i32, pos.column); + if (!build_context.disallow_rtti) { args[4] = lb_typeid(m, src_type); args[5] = lb_typeid(m, dst_type); args[6] = lb_emit_conv(p, value_, t_rawptr); - lb_emit_runtime_call(p, "type_assertion_check2", args); } + lb_emit_runtime_call(p, "type_assertion_check2", args); return lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 0)); } @@ -706,6 +718,13 @@ lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *type, TokenPos } Type *dst_type = tuple->Tuple.variables[0]->type; + if ((p->state_flags & StateFlag_no_type_assert) != 0 && !is_tuple) { + // just do a bit cast of the data at the front + lbValue ptr = lb_emit_struct_ev(p, value, 0); + ptr = lb_emit_conv(p, ptr, alloc_type_pointer(type)); + return lb_addr(ptr); + } + lbAddr v = lb_add_local_generated(p, tuple, true); lbValue dst_typeid = lb_typeid(m, dst_type); @@ -731,18 +750,24 @@ lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *type, TokenPos if (!is_tuple) { // NOTE(bill): Panic on invalid conversion - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - auto args = array_make<lbValue>(permanent_allocator(), 7); + + isize arg_count = 7; + if (build_context.disallow_rtti) { + arg_count = 4; + } + auto args = array_make<lbValue>(permanent_allocator(), arg_count); args[0] = ok; args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); args[2] = lb_const_int(m, t_i32, pos.line); args[3] = lb_const_int(m, t_i32, pos.column); - args[4] = any_typeid; - args[5] = dst_typeid; - args[6] = lb_emit_struct_ev(p, value, 0);; + if (!build_context.disallow_rtti) { + args[4] = any_typeid; + args[5] = dst_typeid; + args[6] = lb_emit_struct_ev(p, value, 0); + } lb_emit_runtime_call(p, "type_assertion_check2", args); return lb_addr(lb_emit_struct_ep(p, v.addr, 0)); @@ -1200,7 +1225,7 @@ lbValue lb_emit_array_epi(lbProcedure *p, lbValue s, isize index) { } lbValue lb_emit_ptr_offset(lbProcedure *p, lbValue ptr, lbValue index) { - index = lb_correct_endianness(p, index); + index = lb_emit_conv(p, index, t_int); LLVMValueRef indices[1] = {index.value}; lbValue res = {}; res.type = ptr.type; @@ -1362,7 +1387,7 @@ lbValue lb_slice_elem(lbProcedure *p, lbValue slice) { return lb_emit_struct_ev(p, slice, 0); } lbValue lb_slice_len(lbProcedure *p, lbValue slice) { - GB_ASSERT(is_type_slice(slice.type)); + GB_ASSERT(is_type_slice(slice.type) || is_type_relative_slice(slice.type)); return lb_emit_struct_ev(p, slice, 1); } lbValue lb_dynamic_array_elem(lbProcedure *p, lbValue da) { @@ -1494,7 +1519,7 @@ lbValue lb_emit_mul_add(lbProcedure *p, lbValue a, lbValue b, lbValue c, Type *t case TargetArch_arm64: // possible break; - case TargetArch_386: + case TargetArch_i386: case TargetArch_wasm32: case TargetArch_wasm64: is_possible = false; @@ -1768,7 +1793,7 @@ LLVMValueRef llvm_get_inline_asm(LLVMTypeRef func_type, String const &str, Strin return LLVMGetInlineAsm(func_type, cast(char *)str.text, cast(size_t)str.len, cast(char *)clobbers.text, cast(size_t)clobbers.len, - /*HasSideEffects*/true, /*IsAlignStack*/false, + has_side_effects, is_align_stack, dialect #if LLVM_VERSION_MAJOR >= 13 , /*CanThrow*/false @@ -1808,3 +1833,197 @@ void lb_set_wasm_export_attributes(LLVMValueRef value, String export_name) { LLVMSetVisibility(value, LLVMDefaultVisibility); LLVMAddTargetDependentFunctionAttr(value, "wasm-export-name", alloc_cstring(permanent_allocator(), export_name)); } + + +lbValue lb_lookup_runtime_procedure(lbModule *m, String const &name); + + +lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { + lbAddr *found = string_map_get(&p->module->objc_selectors, name); + if (found) { + return *found; + } else { + lbModule *default_module = &p->module->gen->default_module; + Entity *e = nullptr; + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); + + lbValue ptr = lb_find_value_from_entity(p->module, e); + lbAddr local_addr = lb_addr(ptr); + + string_map_set(&default_module->objc_selectors, name, default_addr); + if (default_module != p->module) { + string_map_set(&p->module->objc_selectors, name, local_addr); + } + return local_addr; + } +} + +lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + + auto tav = ce->args[0]->tav; + GB_ASSERT(tav.value.kind == ExactValue_String); + String name = tav.value.value_string; + return lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, name)); +} + +lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + lbModule *m = p->module; + + auto tav = ce->args[0]->tav; + GB_ASSERT(tav.value.kind == ExactValue_String); + String name = tav.value.value_string; + lbAddr dst = lb_handle_objc_find_or_register_selector(p, name); + + auto args = array_make<lbValue>(permanent_allocator(), 1); + args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); + lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); + lb_addr_store(p, dst, ptr); + + return lb_addr_load(p, dst); +} + +lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { + lbAddr *found = string_map_get(&p->module->objc_classes, name); + if (found) { + return *found; + } else { + lbModule *default_module = &p->module->gen->default_module; + Entity *e = nullptr; + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); + + lbValue ptr = lb_find_value_from_entity(p->module, e); + lbAddr local_addr = lb_addr(ptr); + + string_map_set(&default_module->objc_classes, name, default_addr); + if (default_module != p->module) { + string_map_set(&p->module->objc_classes, name, local_addr); + } + return local_addr; + } +} + +lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + + auto tav = ce->args[0]->tav; + GB_ASSERT(tav.value.kind == ExactValue_String); + String name = tav.value.value_string; + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); +} + +lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + lbModule *m = p->module; + + auto tav = ce->args[0]->tav; + GB_ASSERT(tav.value.kind == ExactValue_String); + String name = tav.value.value_string; + lbAddr dst = lb_handle_objc_find_or_register_class(p, name); + + auto args = array_make<lbValue>(permanent_allocator(), 3); + args[0] = lb_const_nil(m, t_objc_Class); + args[1] = lb_const_nil(m, t_objc_Class); + args[2] = lb_const_int(m, t_uint, 0); + lbValue ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + lb_addr_store(p, dst, ptr); + + return lb_addr_load(p, dst); +} + + +lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { + TypeAndValue const &tav = type_and_value_of_expr(expr); + if (tav.mode == Addressing_Type) { + Type *type = tav.type; + GB_ASSERT_MSG(type->kind == Type_Named, "%s", type_to_string(type)); + Entity *e = type->Named.type_name; + GB_ASSERT(e->kind == Entity_TypeName); + String name = e->TypeName.objc_class_name; + + lbAddr *found = string_map_get(&p->module->objc_classes, name); + if (found) { + return lb_addr_load(p, *found); + } else { + lbModule *default_module = &p->module->gen->default_module; + Entity *e = nullptr; + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &e); + + lbValue ptr = lb_find_value_from_entity(p->module, e); + lbAddr local_addr = lb_addr(ptr); + + string_map_set(&default_module->objc_classes, name, default_addr); + if (default_module != p->module) { + string_map_set(&p->module->objc_classes, name, local_addr); + } + return lb_addr_load(p, local_addr); + } + } + + return lb_build_expr(p, expr); +} + +lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + + lbModule *m = p->module; + CheckerInfo *info = m->info; + ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr); + GB_ASSERT(data.proc_type != nullptr); + + GB_ASSERT(ce->args.count >= 3); + auto args = array_make<lbValue>(permanent_allocator(), 0, ce->args.count-1); + + lbValue id = lb_handle_objc_id(p, ce->args[1]); + Ast *sel_expr = ce->args[2]; + GB_ASSERT(sel_expr->tav.value.kind == ExactValue_String); + lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, sel_expr->tav.value.value_string)); + + array_add(&args, id); + array_add(&args, sel); + for (isize i = 3; i < ce->args.count; i++) { + lbValue arg = lb_build_expr(p, ce->args[i]); + array_add(&args, arg); + } + + + lbValue the_proc = {}; + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend")); break; + case ObjcMsg_fpret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret")); break; + case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break; + case ObjcMsg_stret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret")); break; + } + + the_proc = lb_emit_conv(p, the_proc, data.proc_type); + + return lb_emit_call(p, the_proc, args); +} + + + + +LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { + GB_ASSERT(value.kind == ExactValue_Integer); + i64 v = exact_value_to_i64(value); + switch (v) { + case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingUnordered; + case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingMonotonic; + case OdinAtomicMemoryOrder_acquire: return LLVMAtomicOrderingAcquire; + case OdinAtomicMemoryOrder_release: return LLVMAtomicOrderingRelease; + case OdinAtomicMemoryOrder_acq_rel: return LLVMAtomicOrderingAcquireRelease; + case OdinAtomicMemoryOrder_seq_cst: return LLVMAtomicOrderingSequentiallyConsistent; + } + GB_PANIC("Unknown atomic ordering"); + return LLVMAtomicOrderingSequentiallyConsistent; +} + + +LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { + ExactValue value = type_and_value_of_expr(expr).value; + return llvm_atomic_ordering_from_odin(value); +} diff --git a/src/main.cpp b/src/main.cpp index 7b4bc92ee..7b0364149 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,7 +46,6 @@ gb_global Timings global_timings = {0}; #include "checker.cpp" #include "docs.cpp" - #include "llvm_backend.cpp" #if defined(GB_SYSTEM_OSX) @@ -57,16 +56,8 @@ gb_global Timings global_timings = {0}; #endif #include "query_data.cpp" - - -#if defined(GB_SYSTEM_WINDOWS) -// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. -#include "microsoft_craziness.h" -#endif - #include "bug_report.cpp" - // NOTE(bill): 'name' is used in debugging and profiling modes i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { isize const cmd_cap = 64<<20; // 64 MiB should be more than enough @@ -74,16 +65,16 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { isize cmd_len = 0; va_list va; i32 exit_code = 0; - + va_start(va, fmt); cmd_len = gb_snprintf_va(cmd_line, cmd_cap-1, fmt, va); va_end(va); - + #if defined(GB_SYSTEM_WINDOWS) STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)}; PROCESS_INFORMATION pi = {0}; String16 wcmd = {}; - + start_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; start_info.wShowWindow = SW_SHOW; start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); @@ -117,44 +108,48 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { gb_printf_err("%s\n\n", cmd_line); } exit_code = system(cmd_line); + if (WIFEXITED(exit_code)) { + exit_code = WEXITSTATUS(exit_code); + } #endif - + if (exit_code) { exit(exit_code); } - + return exit_code; } - - i32 linker_stage(lbGenerator *gen) { i32 result = 0; Timings *timings = &global_timings; - String output_base = gen->output_base; + String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + debugf("Linking %.*s\n", LIT(output_filename)); + + // TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`. if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); - + #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s", LIT(build_context.ODIN_ROOT), - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s", + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #endif return result; } if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { -#ifdef GB_SYSTEM_UNIX +#if defined(GB_SYSTEM_UNIX) result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n", LIT(target_os_names[build_context.metrics.os]), @@ -178,28 +173,11 @@ i32 linker_stage(lbGenerator *gen) { gbString lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(lib_str)); - char const *output_ext = "exe"; gbString link_settings = gb_string_make_reserve(heap_allocator(), 256); defer (gb_string_free(link_settings)); - - // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. - Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); - - if (find_result.windows_sdk_version == 0) { - gb_printf_err("Windows SDK not found.\n"); - exit(1); - } - - if (build_context.ignore_microsoft_magic) { - find_result = {}; - } - // Add library search paths. - if (find_result.vs_library_path.len > 0) { - GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); - GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); - + if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) { String path = {}; auto add_path = [&](String path) { if (path[path.len-1] == '\\') { @@ -207,16 +185,16 @@ i32 linker_stage(lbGenerator *gen) { } link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path)); }; - add_path(find_result.windows_sdk_um_library_path); - add_path(find_result.windows_sdk_ucrt_library_path); - add_path(find_result.vs_library_path); + add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename); + add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename); + add_path(build_context.build_paths[BuildPath_VS_LIB].basename); } - - + + StringSet libs = {}; string_set_init(&libs, heap_allocator(), 64); defer (string_set_destroy(&libs)); - + StringSet asm_files = {}; string_set_init(&asm_files, heap_allocator(), 64); defer (string_set_destroy(&asm_files)); @@ -241,22 +219,22 @@ i32 linker_stage(lbGenerator *gen) { string_set_add(&libs, lib); } } - + for_array(i, libs.entries) { String lib = libs.entries[i].value; lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); } - - + + if (build_context.build_mode == BuildMode_DynamicLibrary) { - output_ext = "dll"; link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); } if (build_context.pdb_filepath != "") { - link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(build_context.pdb_filepath)); + String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); + link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); } if (build_context.no_crt) { @@ -268,7 +246,7 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.ODIN_DEBUG) { link_settings = gb_string_append_fmt(link_settings, " /DEBUG"); } - + for_array(i, asm_files.entries) { String asm_file = asm_files.entries[i].value; String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); @@ -283,7 +261,7 @@ i32 linker_stage(lbGenerator *gen) { LIT(obj_file), LIT(build_context.extra_assembler_flags) ); - + if (result) { return result; } @@ -297,27 +275,35 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } + String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]); + defer (gb_free(heap_allocator(), vs_exe_path.text)); + char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; if (!build_context.use_lld) { // msvc if (build_context.has_resource) { + String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); + String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); + defer (gb_free(heap_allocator(), rc_path.text)); + defer (gb_free(heap_allocator(), res_path.text)); + result = system_exec_command_line_app("msvc-link", - "\"rc.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"", - LIT(output_base), - LIT(build_context.resource_filepath) + "\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"", + LIT(res_path), + LIT(rc_path) ); - + if (result) { return result; } result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s " + "\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext, + LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), @@ -326,13 +312,13 @@ i32 linker_stage(lbGenerator *gen) { ); } else { result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s " + "\"%.*slink.exe\" %s -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(find_result.vs_exe_path), object_files, LIT(output_base), output_ext, + LIT(vs_exe_path), object_files, LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), @@ -340,27 +326,27 @@ i32 linker_stage(lbGenerator *gen) { lib_str ); } - + if (result) { return result; } - + } else { // lld result = system_exec_command_line_app("msvc-lld-link", - "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s.%s\" %s " + "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(build_context.ODIN_ROOT), object_files, LIT(output_base),output_ext, + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), lib_str ); - + if (result) { return result; } @@ -384,7 +370,7 @@ i32 linker_stage(lbGenerator *gen) { // NOTE(zangent): Sometimes, you have to use -framework on MacOS. // This allows you to specify '-f' in a #foreign_system_library, // without having to implement any new syntax specifically for MacOS. - #if defined(GB_SYSTEM_OSX) + if (build_context.metrics.os == TargetOs_darwin) { if (string_ends_with(lib, str_lit(".framework"))) { // framework thingie String lib_name = lib; @@ -400,25 +386,25 @@ i32 linker_stage(lbGenerator *gen) { // dynamic or static system lib, just link regularly searching system library paths lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); } - #else + } else { // NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path, // since those are statically linked to at link time. shared libraries (.so) has to be // available at runtime wherever the executable is run, so we make require those to be // local to the executable (unless the system collection is used, in which case we search // the system library paths for the library file). - if (string_ends_with(lib, str_lit(".a"))) { - // static libs, absolute full path relative to the file in which the lib was imported from + if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { + // static libs and object files, absolute full path relative to the file in which the lib was imported from lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); } else if (string_ends_with(lib, str_lit(".so"))) { // dynamic lib, relative path to executable // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtimeto the executable + // at runtime to the executable lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); } else { // dynamic or static system lib, just link regularly searching system library paths lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); } - #endif + } } gbString object_files = gb_string_make(heap_allocator(), ""); @@ -428,80 +414,74 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } - // Unlike the Win32 linker code, the output_ext includes the dot, because - // typically executable files on *NIX systems don't have extensions. - String output_ext = {}; gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); - char const *linker; + + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); + } + + // NOTE(dweiler): We use clang as a frontend for the linker as there are + // other runtime and compiler support libraries that need to be linked in + // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. + // These are not always typically inside /lib, /lib64, or /usr versions + // of that, e.g libgcc.a is in /usr/lib/gcc/{version}, and can vary on + // the distribution of Linux even. The gcc or clang specs is the only + // reliable way to query this information to call ld directly. if (build_context.build_mode == BuildMode_DynamicLibrary) { - // NOTE(tetra, 2020-11-06): __$startup_runtime must be called at DLL load time. - // Clang, for some reason, won't let us pass the '-init' flag that lets us do this, - // so use ld instead. - // :UseLDForShared - linker = "ld"; - link_settings = gb_string_appendc(link_settings, "-init '__$startup_runtime' "); - // Shared libraries are .dylib on MacOS and .so on Linux. - #if defined(GB_SYSTEM_OSX) - output_ext = STR_LIT(".dylib"); - link_settings = gb_string_appendc(link_settings, "-dylib -dynamic "); - #else - output_ext = STR_LIT(".so"); - link_settings = gb_string_appendc(link_settings, "-shared "); - #endif - } else { - #if defined(GB_SYSTEM_OSX) - linker = "ld"; - #else - // TODO(zangent): Figure out how to make ld work on Linux. - // It probably has to do with including the entire CRT, but - // that's quite a complicated issue to solve while remaining distro-agnostic. - // Clang can figure out linker flags for us, and that's good enough _for now_. - linker = "clang -Wno-unused-command-line-argument"; - #endif - } - - if (build_context.metrics.os == TargetOs_linux) { + // NOTE(dweiler): Let the frontend know we're building a shared library + // so it doesn't generate symbols which cannot be relocated. + link_settings = gb_string_appendc(link_settings, "-shared "); + + // NOTE(dweiler): _odin_entry_point must be called at initialization + // time of the shared object, similarly, _odin_exit_point must be called + // at deinitialization. We can pass both -init and -fini to the linker by + // using a comma separated list of arguments to -Wl. + // + // This previously used ld but ld cannot actually build a shared library + // correctly this way since all the other dependencies provided implicitly + // by the compiler frontend are still needed and most of the command + // line arguments prepared previously are incompatible with ld. + link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' "); + link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); + } else if (build_context.metrics.os != TargetOs_openbsd) { + // OpenBSD defaults to PIE executable. do not pass -no-pie for it. link_settings = gb_string_appendc(link_settings, "-no-pie "); } + gbString platform_lib_str = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(platform_lib_str)); + if (build_context.metrics.os == TargetOs_darwin) { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib"); + } else { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm"); + } - if (build_context.out_filepath.len > 0) { - //NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that - isize pos = string_extension_position(build_context.out_filepath); - if (pos > 0) { - output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len); + if (build_context.metrics.os == TargetOs_darwin) { + // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. + // NOTE: If you change this (although this minimum is as low as you can go with Odin working) + // make sure to also change the 'mtriple' param passed to 'opt' + if (build_context.metrics.arch == TargetArch_arm64) { + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); + } else { + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.8.0 "); } + // This points the linker to where the entry point is + link_settings = gb_string_appendc(link_settings, " -e _main "); } - result = system_exec_command_line_app("ld-link", - "%s %s -o \"%.*s%.*s\" %s " - " %s " - " %.*s " - " %.*s " - " %s " - #if defined(GB_SYSTEM_OSX) - // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. - // NOTE: If you change this (although this minimum is as low as you can go with Odin working) - // make sure to also change the 'mtriple' param passed to 'opt' - #if defined(GB_CPU_ARM) - " -macosx_version_min 11.0.0 " - #else - " -macosx_version_min 10.8.0 " - #endif - // This points the linker to where the entry point is - " -e _main " - #endif - , linker, object_files, LIT(output_base), LIT(output_ext), - #if defined(GB_SYSTEM_OSX) - "-lSystem -lm -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib", - #else - "-lc -lm", - #endif - lib_str, - LIT(build_context.link_flags), - LIT(build_context.extra_linker_flags), - link_settings); - + gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument "); + defer (gb_string_free(link_command_line)); + + link_command_line = gb_string_appendc(link_command_line, object_files); + link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str); + link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags)); + link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags)); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings); + + result = system_exec_command_line_app("ld-link", link_command_line); + if (result) { return result; } @@ -510,10 +490,8 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", - "dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext) - ); - + result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + if (result) { return result; } @@ -571,54 +549,26 @@ void usage(String argv0) { print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s command [arguments]", LIT(argv0)); print_usage_line(0, "Commands:"); - print_usage_line(1, "build compile .odin file, or directory of .odin files, as an executable."); - print_usage_line(1, " one must contain the program's entry point, all must be in the same package."); - print_usage_line(1, "run same as 'build', but also then runs the newly compiled executable."); - print_usage_line(1, "check parse and type check .odin file"); - print_usage_line(1, "query parse, type check, and output a .json file containing information about the program"); - print_usage_line(1, "doc generate documentation .odin file, or directory of .odin files"); - print_usage_line(1, "version print version"); - print_usage_line(1, "report print information useful to reporting a bug"); + print_usage_line(1, "build compile directory of .odin files, as an executable."); + print_usage_line(1, " one must contain the program's entry point, all must be in the same package."); + print_usage_line(1, "run same as 'build', but also then runs the newly compiled executable."); + print_usage_line(1, "check parse, and type check a directory of .odin files"); + print_usage_line(1, "query parse, type check, and output a .json file containing information about the program"); + print_usage_line(1, "strip-semicolon parse, type check, and remove unneeded semicolons from the entire program"); + print_usage_line(1, "test build ands runs procedures with the attribute @(test) in the initial package"); + print_usage_line(1, "doc generate documentation on a directory of .odin files"); + print_usage_line(1, "version print version"); + print_usage_line(1, "report print information useful to reporting a bug"); print_usage_line(0, ""); print_usage_line(0, "For further details on a command, use -help after the command name"); print_usage_line(1, "e.g. odin build -help"); } - -bool string_is_valid_identifier(String str) { - if (str.len <= 0) return false; - - isize rune_count = 0; - - isize w = 0; - isize offset = 0; - while (offset < str.len) { - Rune r = 0; - w = utf8_decode(str.text, str.len, &r); - if (r == GB_RUNE_INVALID) { - return false; - } - - if (rune_count == 0) { - if (!rune_is_letter(r)) { - return false; - } - } else { - if (!rune_is_letter(r) && !rune_is_digit(r)) { - return false; - } - } - rune_count += 1; - offset += w; - } - - return true; -} - enum BuildFlagKind { BuildFlag_Invalid, BuildFlag_Help, + BuildFlag_SingleFile, BuildFlag_OutFile, BuildFlag_OptimizationLevel, @@ -654,6 +604,10 @@ enum BuildFlagKind { BuildFlag_ExtraLinkerFlags, BuildFlag_ExtraAssemblerFlags, BuildFlag_Microarch, + BuildFlag_TargetFeatures, + + BuildFlag_RelocMode, + BuildFlag_DisableRedZone, BuildFlag_TestName, @@ -662,6 +616,8 @@ enum BuildFlagKind { BuildFlag_InsertSemicolon, BuildFlag_StrictStyle, BuildFlag_StrictStyleInitOnly, + BuildFlag_ForeignErrorProcedures, + BuildFlag_DisallowRTTI, BuildFlag_Compact, BuildFlag_GlobalDefinitions, @@ -674,9 +630,9 @@ enum BuildFlagKind { BuildFlag_IgnoreWarnings, BuildFlag_WarningsAsErrors, BuildFlag_VerboseErrors, - + // internal use only - BuildFlag_InternalIgnoreLazy, + BuildFlag_InternalIgnoreLazy, #if defined(GB_SYSTEM_WINDOWS) BuildFlag_IgnoreVsSearch, @@ -771,69 +727,80 @@ ExactValue build_param_to_exact_value(String name, String param) { bool parse_build_flags(Array<String> args) { auto build_flags = array_make<BuildFlag>(heap_allocator(), 0, BuildFlag_COUNT); - add_flag(&build_flags, BuildFlag_Help, str_lit("help"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build &~ Command_test); - add_flag(&build_flags, BuildFlag_OptimizationLevel, str_lit("opt"), BuildFlagParam_Integer, Command__does_build); - add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("o"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("O"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); - add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); - add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_ThreadCount, str_lit("thread-count"), BuildFlagParam_Integer, Command_all); - add_flag(&build_flags, BuildFlag_KeepTempFiles, str_lit("keep-temp-files"), BuildFlagParam_None, Command__does_build|Command_strip_semicolon); - add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); - add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message - add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); - add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_UseSeparateModules,str_lit("use-separate-modules"),BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_ThreadedChecker, str_lit("threaded-checker"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_VetExtra, str_lit("vet-extra"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_UseLLVMApi, str_lit("llvm-api"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_ExtraLinkerFlags, str_lit("extra-linker-flags"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_ExtraAssemblerFlags, str_lit("extra-assembler-flags"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_Microarch, str_lit("microarch"), BuildFlagParam_String, Command__does_build); - - add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test); - - add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_InsertSemicolon, str_lit("insert-semicolon"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_StrictStyle, str_lit("strict-style"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_StrictStyleInitOnly, str_lit("strict-style-init-only"), BuildFlagParam_None, Command__does_check); - add_flag(&build_flags, BuildFlag_Compact, str_lit("compact"), BuildFlagParam_None, Command_query); - add_flag(&build_flags, BuildFlag_GlobalDefinitions, str_lit("global-definitions"), BuildFlagParam_None, Command_query); - add_flag(&build_flags, BuildFlag_GoToDefinitions, str_lit("go-to-definitions"), BuildFlagParam_None, Command_query); - - add_flag(&build_flags, BuildFlag_Short, str_lit("short"), BuildFlagParam_None, Command_doc); - add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc); - add_flag(&build_flags, BuildFlag_DocFormat, str_lit("doc-format"), BuildFlagParam_None, Command_doc); - - add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all); - - add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_Help, str_lit("help"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_SingleFile, str_lit("file"), BuildFlagParam_None, Command__does_build | Command__does_check); + add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build &~ Command_test); + add_flag(&build_flags, BuildFlag_OptimizationLevel, str_lit("opt"), BuildFlagParam_Integer, Command__does_build); + add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("o"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("O"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); + add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); + add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_ThreadCount, str_lit("thread-count"), BuildFlagParam_Integer, Command_all); + add_flag(&build_flags, BuildFlag_KeepTempFiles, str_lit("keep-temp-files"), BuildFlagParam_None, Command__does_build | Command_strip_semicolon); + add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); + add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message + add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); + add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_ThreadedChecker, str_lit("threaded-checker"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetExtra, str_lit("vet-extra"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_UseLLVMApi, str_lit("llvm-api"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ExtraLinkerFlags, str_lit("extra-linker-flags"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_ExtraAssemblerFlags, str_lit("extra-assembler-flags"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_Microarch, str_lit("microarch"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_TargetFeatures, str_lit("target-features"), BuildFlagParam_String, Command__does_build); + + add_flag(&build_flags, BuildFlag_RelocMode, str_lit("reloc-mode"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_DisableRedZone, str_lit("disable-red-zone"), BuildFlagParam_None, Command__does_build); + + add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test); + + add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_InsertSemicolon, str_lit("insert-semicolon"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_StrictStyle, str_lit("strict-style"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_StrictStyleInitOnly, str_lit("strict-style-init-only"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ForeignErrorProcedures, str_lit("foreign-error-procedures"), BuildFlagParam_None, Command__does_check); + + add_flag(&build_flags, BuildFlag_DisallowRTTI, str_lit("disallow-rtti"), BuildFlagParam_None, Command__does_check); + + + add_flag(&build_flags, BuildFlag_Compact, str_lit("compact"), BuildFlagParam_None, Command_query); + add_flag(&build_flags, BuildFlag_GlobalDefinitions, str_lit("global-definitions"), BuildFlagParam_None, Command_query); + add_flag(&build_flags, BuildFlag_GoToDefinitions, str_lit("go-to-definitions"), BuildFlagParam_None, Command_query); + + + add_flag(&build_flags, BuildFlag_Short, str_lit("short"), BuildFlagParam_None, Command_doc); + add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc); + add_flag(&build_flags, BuildFlag_DocFormat, str_lit("doc-format"), BuildFlagParam_None, Command_doc); + + add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all); + + add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); #if defined(GB_SYSTEM_WINDOWS) - add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_WindowsPdbName, str_lit("pdb-name"), BuildFlagParam_String, Command__does_build); - add_flag(&build_flags, BuildFlag_Subsystem, str_lit("subsystem"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_WindowsPdbName, str_lit("pdb-name"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_Subsystem, str_lit("subsystem"), BuildFlagParam_String, Command__does_build); #endif @@ -1280,7 +1247,7 @@ bool parse_build_flags(Array<String> args) { } else { gb_printf_err("Unknown build mode '%.*s'\n", LIT(str)); gb_printf_err("Valid build modes:\n"); - gb_printf_err("\tdll, shared\n"); + gb_printf_err("\tdll, shared, dynamic\n"); gb_printf_err("\tobj, object\n"); gb_printf_err("\texe\n"); gb_printf_err("\tasm, assembly, assembler\n"); @@ -1355,7 +1322,7 @@ bool parse_build_flags(Array<String> args) { GB_ASSERT(value.kind == ExactValue_String); build_context.extra_linker_flags = value.value_string; break; - case BuildFlag_ExtraAssemblerFlags: + case BuildFlag_ExtraAssemblerFlags: GB_ASSERT(value.kind == ExactValue_String); build_context.extra_assembler_flags = value.value_string; break; @@ -1365,6 +1332,37 @@ bool parse_build_flags(Array<String> args) { string_to_lower(&build_context.microarch); break; } + case BuildFlag_TargetFeatures: { + GB_ASSERT(value.kind == ExactValue_String); + build_context.target_features = value.value_string; + string_to_lower(&build_context.target_features); + break; + } + case BuildFlag_RelocMode: { + GB_ASSERT(value.kind == ExactValue_String); + String v = value.value_string; + if (v == "default") { + build_context.reloc_mode = RelocMode_Default; + } else if (v == "static") { + build_context.reloc_mode = RelocMode_Static; + } else if (v == "pic") { + build_context.reloc_mode = RelocMode_PIC; + } else if (v == "dynamic-no-pic") { + build_context.reloc_mode = RelocMode_DynamicNoPIC; + } else { + gb_printf_err("-reloc-mode flag expected one of the following\n"); + gb_printf_err("\tdefault\n"); + gb_printf_err("\tstatic\n"); + gb_printf_err("\tpic\n"); + gb_printf_err("\tdynamic-no-pic\n"); + bad_flags = true; + } + + break; + } + case BuildFlag_DisableRedZone: + build_context.disable_red_zone = true; + break; case BuildFlag_TestName: { GB_ASSERT(value.kind == ExactValue_String); { @@ -1383,9 +1381,15 @@ bool parse_build_flags(Array<String> args) { case BuildFlag_DisallowDo: build_context.disallow_do = true; break; + case BuildFlag_DisallowRTTI: + build_context.disallow_rtti = true; + break; case BuildFlag_DefaultToNilAllocator: build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR = true; break; + case BuildFlag_ForeignErrorProcedures: + build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true; + break; case BuildFlag_InsertSemicolon: { gb_printf_err("-insert-semicolon flag is not required any more\n"); bad_flags = true; @@ -1486,6 +1490,10 @@ bool parse_build_flags(Array<String> args) { gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path)); bad_flags = true; break; + } else if (!gb_file_exists((const char *)path.text)) { + gb_printf_err("Invalid -resource path %.*s, file does not exist.\n", LIT(path)); + bad_flags = true; + break; } build_context.resource_filepath = substring(path, 0, string_extension_position(path)); build_context.has_resource = true; @@ -1500,6 +1508,11 @@ bool parse_build_flags(Array<String> args) { String path = value.value_string; path = string_trim_whitespace(path); if (is_build_flag_path_valid(path)) { + if (path_is_directory(path)) { + gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path)); + bad_flags = true; + break; + } // #if defined(GB_SYSTEM_WINDOWS) // String ext = path_extension(path); // if (ext != ".pdb") { @@ -1817,7 +1830,7 @@ void show_timings(Checker *c, Timings *t) { void remove_temp_files(lbGenerator *gen) { if (build_context.keep_temp_files) return; - + TIME_SECTION("remove keep temp files"); for_array(i, gen->output_temp_paths) { @@ -1841,32 +1854,47 @@ void remove_temp_files(lbGenerator *gen) { void print_show_help(String const arg0, String const &command) { print_usage_line(0, "%.*s is a tool for managing Odin source code", LIT(arg0)); - print_usage_line(0, "Usage"); + print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command)); print_usage_line(0, ""); if (command == "build") { - print_usage_line(1, "build compile .odin file, or directory of .odin files, as an executable."); - print_usage_line(1, " one must contain the program's entry point, all must be in the same package."); + print_usage_line(1, "build Compile directory of .odin files as an executable."); + print_usage_line(2, "One must contain the program's entry point, all must be in the same package."); + print_usage_line(2, "Use `-file` to build a single file instead."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "odin build . # Build package in current directory"); + print_usage_line(3, "odin build <dir> # Build package in <dir>"); + print_usage_line(3, "odin build filename.odin -file # Build single-file package, must contain entry point."); } else if (command == "run") { - print_usage_line(1, "run same as 'build', but also then runs the newly compiled executable."); + print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable."); + print_usage_line(2, "Append an empty flag and then the args, '-- <args>', to specify args for the output."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "odin run . # Build and run package in current directory"); + print_usage_line(3, "odin run <dir> # Build and run package in <dir>"); + print_usage_line(3, "odin run filename.odin -file # Build and run single-file package, must contain entry point."); } else if (command == "check") { - print_usage_line(1, "check parse and type check .odin file(s)"); + print_usage_line(1, "check Parse and type check directory of .odin files"); + print_usage_line(2, "Examples:"); + print_usage_line(3, "odin check . # Type check package in current directory"); + print_usage_line(3, "odin check <dir> # Type check package in <dir>"); + print_usage_line(3, "odin check filename.odin -file # Type check single-file package, must contain entry point."); } else if (command == "test") { - print_usage_line(1, "test build ands runs procedures with the attribute @(test) in the initial package"); + print_usage_line(1, "test Build ands runs procedures with the attribute @(test) in the initial package"); } else if (command == "query") { - print_usage_line(1, "query [experimental] parse, type check, and output a .json file containing information about the program"); + print_usage_line(1, "query [experimental] Parse, type check, and output a .json file containing information about the program"); } else if (command == "doc") { - print_usage_line(1, "doc generate documentation from a .odin file, or directory of .odin files"); + print_usage_line(1, "doc generate documentation from a directory of .odin files"); print_usage_line(2, "Examples:"); - print_usage_line(3, "odin doc core/path"); - print_usage_line(3, "odin doc core/path core/path/filepath"); + print_usage_line(3, "odin doc . # Generate documentation on package in current directory"); + print_usage_line(3, "odin doc <dir> # Generate documentation on package in <dir>"); + print_usage_line(3, "odin doc filename.odin -file # Generate documentation on single-file package."); } else if (command == "version") { print_usage_line(1, "version print version"); } else if (command == "strip-semicolon") { print_usage_line(1, "strip-semicolon"); - print_usage_line(2, "parse and type check .odin file(s) and then remove unneeded semicolons from the entire project"); - } + print_usage_line(2, "Parse and type check .odin file(s) and then remove unneeded semicolons from the entire project"); + } bool doc = command == "doc"; bool build = command == "build"; @@ -1880,6 +1908,13 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "Flags"); print_usage_line(0, ""); + if (check) { + print_usage_line(1, "-file"); + print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); + print_usage_line(2, "This means that `<dir>/a.odin` won't have access to `<dir>/b.odin`'s contents."); + print_usage_line(0, ""); + } + if (doc) { print_usage_line(1, "-short"); print_usage_line(2, "Show shortened documentation for the packages"); @@ -1970,6 +2005,7 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-define:<name>=<expression>"); print_usage_line(2, "Defines a global constant with a value"); print_usage_line(2, "Example: -define:SPAM=123"); + print_usage_line(2, "To use: #config(SPAM, default_value)"); print_usage_line(0, ""); } @@ -2071,7 +2107,7 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-extra-linker-flags:<string>"); print_usage_line(2, "Adds extra linker specific flags in a string"); print_usage_line(0, ""); - + print_usage_line(1, "-extra-assembler-flags:<string>"); print_usage_line(2, "Adds extra assembler specific flags in a string"); print_usage_line(0, ""); @@ -2083,6 +2119,18 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-microarch:sandybridge"); print_usage_line(3, "-microarch:native"); print_usage_line(0, ""); + + print_usage_line(1, "-reloc-mode:<string>"); + print_usage_line(2, "Specifies the reloc mode"); + print_usage_line(2, "Options:"); + print_usage_line(3, "default"); + print_usage_line(3, "static"); + print_usage_line(3, "pic"); + print_usage_line(3, "dynamic-no-pic"); + print_usage_line(0, ""); + + print_usage_line(1, "-disable-red-zone"); + print_usage_line(2, "Disable red zone on a supported freestanding target"); } if (check) { @@ -2097,7 +2145,7 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-strict-style"); print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons"); print_usage_line(0, ""); - + print_usage_line(1, "-strict-style-init-only"); print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons, only on the initial project"); print_usage_line(0, ""); @@ -2113,6 +2161,11 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-verbose-errors"); print_usage_line(2, "Prints verbose error messages showing the code on that line and the location in that line"); print_usage_line(0, ""); + + print_usage_line(1, "-foreign-error-procedures"); + print_usage_line(2, "States that the error procedues used in the runtime are defined in a separate translation unit"); + print_usage_line(0, ""); + } if (run_or_build) { @@ -2262,7 +2315,7 @@ gbFileError write_file_with_stripped_tokens(gbFile *f, AstFile *file, i64 *writt } written += to_write; prev_offset = token_pos_end(*token).offset; - } + } if (token->flags & TokenFlag_Replace) { if (token->kind == Token_Ellipsis) { if (!gb_file_write(f, "..=", 3)) { @@ -2281,7 +2334,7 @@ gbFileError write_file_with_stripped_tokens(gbFile *f, AstFile *file, i64 *writt } written += to_write; } - + if (written_) *written_ = written; return err; } @@ -2292,14 +2345,14 @@ int strip_semicolons(Parser *parser) { AstPackage *pkg = parser->packages[i]; file_count += pkg->files.count; } - + auto generated_files = array_make<StripSemicolonFile>(permanent_allocator(), 0, file_count); - + for_array(i, parser->packages) { AstPackage *pkg = parser->packages[i]; for_array(j, pkg->files) { AstFile *file = pkg->files[j]; - + bool nothing_to_change = true; for_array(i, file->tokens) { Token *token = &file->tokens[i]; @@ -2308,29 +2361,29 @@ int strip_semicolons(Parser *parser) { break; } } - + if (nothing_to_change) { continue; } - + String old_fullpath = copy_string(permanent_allocator(), file->fullpath); - + // assumes .odin extension String fullpath_base = substring(old_fullpath, 0, old_fullpath.len-5); - + String old_fullpath_backup = concatenate_strings(permanent_allocator(), fullpath_base, str_lit("~backup.odin-temp")); String new_fullpath = concatenate_strings(permanent_allocator(), fullpath_base, str_lit("~temp.odin-temp")); - + array_add(&generated_files, StripSemicolonFile{old_fullpath, old_fullpath_backup, new_fullpath, file}); } } - + gb_printf_err("File count to be stripped of unneeded tokens: %td\n", generated_files.count); - - + + isize generated_count = 0; bool failed = false; - + for_array(i, generated_files) { auto *file = &generated_files[i]; char const *filename = cast(char const *)file->new_fullpath.text; @@ -2338,15 +2391,15 @@ int strip_semicolons(Parser *parser) { defer (if (err != gbFileError_None) { failed = true; }); - - gbFile f = {}; + + gbFile f = {}; err = gb_file_create(&f, filename); if (err) { break; } defer (err = gb_file_close(&f)); generated_count += 1; - + i64 written = 0; defer (err = gb_file_truncate(&f, written)); @@ -2367,23 +2420,23 @@ int strip_semicolons(Parser *parser) { } return 1; } - + isize overwritten_files = 0; - + for_array(i, generated_files) { auto *file = &generated_files[i]; - + char const *old_fullpath = cast(char const *)file->old_fullpath.text; char const *old_fullpath_backup = cast(char const *)file->old_fullpath_backup.text; char const *new_fullpath = cast(char const *)file->new_fullpath.text; - + debugf("Copy '%s' to '%s'\n", old_fullpath, old_fullpath_backup); if (!gb_file_copy(old_fullpath, old_fullpath_backup, false)) { gb_printf_err("failed to copy '%s' to '%s'\n", old_fullpath, old_fullpath_backup); failed = true; break; } - + debugf("Copy '%s' to '%s'\n", new_fullpath, old_fullpath); if (!gb_file_copy(new_fullpath, old_fullpath, false)) { @@ -2400,19 +2453,19 @@ int strip_semicolons(Parser *parser) { if (!gb_file_remove(old_fullpath_backup)) { gb_printf_err("failed to remove '%s'\n", old_fullpath_backup); } - + overwritten_files++; } - + if (!build_context.keep_temp_files) { for_array(i, generated_files) { auto *file = &generated_files[i]; char const *filename = nullptr; filename = cast(char const *)file->new_fullpath.text; - + debugf("Remove '%s'\n", filename); GB_ASSERT_MSG(gb_file_remove(filename), "unable to delete file %s", filename); - + filename = cast(char const *)file->old_fullpath_backup.text; debugf("Remove '%s'\n", filename); if (gb_file_exists(filename) && !gb_file_remove(filename)) { @@ -2423,14 +2476,12 @@ int strip_semicolons(Parser *parser) { } } } - + gb_printf_err("Files stripped of unneeded token: %td\n", generated_files.count); - - - return cast(int)failed; -} + return cast(int)failed; +} int main(int arg_count, char const **arg_ptr) { if (arg_count < 2) { @@ -2442,10 +2493,11 @@ int main(int arg_count, char const **arg_ptr) { defer (timings_destroy(&global_timings)); MAIN_TIME_SECTION("initialization"); - + virtual_memory_init(); mutex_init(&fullpath_mutex); mutex_init(&hash_exact_value_mutex); + mutex_init(&global_type_name_objc_metadata_mutex); init_string_buffer_memory(); init_string_interner(); @@ -2471,6 +2523,7 @@ int main(int arg_count, char const **arg_ptr) { String command = args[1]; String init_filename = {}; String run_args_string = {}; + isize last_non_run_arg = args.count; bool run_output = false; if (command == "run" || command == "test") { @@ -2486,7 +2539,6 @@ int main(int arg_count, char const **arg_ptr) { Array<String> run_args = array_make<String>(heap_allocator(), 0, arg_count); defer (array_free(&run_args)); - isize last_non_run_arg = args.count; for_array(i, args) { if (args[i] == "--") { last_non_run_arg = i; @@ -2499,6 +2551,7 @@ int main(int arg_count, char const **arg_ptr) { args = array_slice(args, 0, last_non_run_arg); run_args_string = string_join_and_quote(heap_allocator(), run_args); + init_filename = args[2]; run_output = true; @@ -2586,11 +2639,40 @@ int main(int arg_count, char const **arg_ptr) { return 1; } + init_filename = copy_string(permanent_allocator(), init_filename); + if (init_filename == "-help" || init_filename == "--help") { build_context.show_help = true; } + if (init_filename.len > 0 && !build_context.show_help) { + // The command must be build, run, test, check, or another that takes a directory or filename. + if (!path_is_directory(init_filename)) { + // Input package is a filename. We allow this only if `-file` was given, otherwise we exit with an error message. + bool single_file_package = false; + for_array(i, args) { + if (i >= 3 && i <= last_non_run_arg && args[i] == "-file") { + single_file_package = true; + break; + } + } + + if (!single_file_package) { + gb_printf_err("ERROR: `%.*s %.*s` takes a package as its first argument.\n", LIT(args[0]), LIT(command)); + gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename)); + gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n"); + return 1; + } else { + String const ext = str_lit(".odin"); + if (!string_ends_with(init_filename, ext)) { + gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); + return 1; + } + } + } + } + build_context.command = command; if (!parse_build_flags(args)) { @@ -2605,19 +2687,30 @@ int main(int arg_count, char const **arg_ptr) { // NOTE(bill): add 'shared' directory if it is not already set if (!find_library_collection_path(str_lit("shared"), nullptr)) { add_library_collection(str_lit("shared"), - get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); + get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); } - init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr); // if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) { // print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0])); // return 1; // } + // Set and check build paths... + if (!init_build_paths(init_filename)) { + return 1; + } + + if (build_context.show_debug_messages) { + for_array(i, build_context.build_paths) { + String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]); + debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path)); + } + } + init_global_thread_pool(); defer (thread_pool_destroy(&global_thread_pool)); - + init_universal(); // TODO(bill): prevent compiling without a linker @@ -2631,6 +2724,8 @@ int main(int arg_count, char const **arg_ptr) { } defer (destroy_parser(parser)); + // TODO(jeroen): Remove the `init_filename` param. + // Let's put that on `build_context.build_paths[0]` instead. if (parse_packages(parser, init_filename) != ParseFile_None) { return 1; } @@ -2649,7 +2744,7 @@ int main(int arg_count, char const **arg_ptr) { if (any_errors()) { return 1; } - + if (build_context.command_kind == Command_strip_semicolon) { return strip_semicolons(parser); } @@ -2703,22 +2798,20 @@ int main(int arg_count, char const **arg_ptr) { } remove_temp_files(gen); - + if (build_context.show_timings) { show_timings(checker, &global_timings); } if (run_output) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + #if defined(GB_SYSTEM_WINDOWS) - return system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(gen->output_base), LIT(run_args_string)); + return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string)); #else - //NOTE(thebirk): This whole thing is a little leaky - String output_ext = {}; - String complete_path = concatenate_strings(permanent_allocator(), gen->output_base, output_ext); - complete_path = path_to_full_path(permanent_allocator(), complete_path); - return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string)); + return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); #endif } - return 0; } diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index 02f14dda3..b4f815284 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -460,7 +460,7 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res wchar_t *library_path = nullptr; if (build_context.metrics.arch == TargetArch_amd64) { library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_386) { + } else if (build_context.metrics.arch == TargetArch_i386) { library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x86\\"); } else { continue; @@ -472,7 +472,7 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res wchar_t *link_exe_path = nullptr; if (build_context.metrics.arch == TargetArch_amd64) { link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx64\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_386) { + } else if (build_context.metrics.arch == TargetArch_i386) { link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx86\\x86\\"); } else { continue; @@ -529,7 +529,7 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res if (build_context.metrics.arch == TargetArch_amd64) { lib_path = concat(buffer, L"VC\\Lib\\amd64\\"); - } else if (build_context.metrics.arch == TargetArch_386) { + } else if (build_context.metrics.arch == TargetArch_i386) { lib_path = concat(buffer, L"VC\\Lib\\"); } else { continue; @@ -542,7 +542,7 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res if (os_file_exists(vcruntime_filename)) { if (build_context.metrics.arch == TargetArch_amd64) { result->vs_exe_path = concat(buffer, L"VC\\bin\\"); - } else if (build_context.metrics.arch == TargetArch_386) { + } else if (build_context.metrics.arch == TargetArch_i386) { // result->vs_exe_path = concat(buffer, L"VC\\bin\\amd64_x86\\"); result->vs_exe_path = concat(buffer, L"VC\\bin\\x86_amd64\\"); } else { @@ -573,7 +573,7 @@ Find_Result find_visual_studio_and_windows_sdk() { if (build_context.metrics.arch == TargetArch_amd64) { result.windows_sdk_um_library_path = concat(result.windows_sdk_root, L"um\\x64\\"); result.windows_sdk_ucrt_library_path = concat(result.windows_sdk_root, L"ucrt\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_386) { + } else if (build_context.metrics.arch == TargetArch_i386) { result.windows_sdk_um_library_path = concat(result.windows_sdk_root, L"um\\x86\\"); result.windows_sdk_ucrt_library_path = concat(result.windows_sdk_root, L"ucrt\\x86\\"); } diff --git a/src/parser.cpp b/src/parser.cpp index cbd4d61d5..df7f908a6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -57,6 +57,9 @@ isize ast_node_size(AstKind kind) { return align_formula_isize(gb_size_of(AstCommonStuff) + ast_variant_sizes[kind], gb_align_of(void *)); } + +gb_global std::atomic<isize> global_total_node_memory_allocated; + // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++ Ast *alloc_ast_node(AstFile *f, AstKind kind) { gbAllocator a = ast_allocator(f); @@ -66,6 +69,9 @@ Ast *alloc_ast_node(AstFile *f, AstKind kind) { Ast *node = cast(Ast *)gb_alloc(a, size); node->kind = kind; node->file_id = f ? f->id : 0; + + global_total_node_memory_allocated += size; + return node; } @@ -183,6 +189,11 @@ Ast *clone_ast(Ast *node) { n->FieldValue.value = clone_ast(n->FieldValue.value); break; + case Ast_EnumFieldValue: + n->EnumFieldValue.name = clone_ast(n->EnumFieldValue.name); + n->EnumFieldValue.value = clone_ast(n->EnumFieldValue.value); + break; + case Ast_TernaryIfExpr: n->TernaryIfExpr.x = clone_ast(n->TernaryIfExpr.x); n->TernaryIfExpr.cond = clone_ast(n->TernaryIfExpr.cond); @@ -693,6 +704,16 @@ Ast *ast_field_value(AstFile *f, Ast *field, Ast *value, Token eq) { return result; } + +Ast *ast_enum_field_value(AstFile *f, Ast *name, Ast *value, CommentGroup *docs, CommentGroup *comment) { + Ast *result = alloc_ast_node(f, Ast_EnumFieldValue); + result->EnumFieldValue.name = name; + result->EnumFieldValue.value = value; + result->EnumFieldValue.docs = docs; + result->EnumFieldValue.comment = comment; + return result; +} + Ast *ast_compound_lit(AstFile *f, Ast *type, Array<Ast *> const &elems, Token open, Token close) { Ast *result = alloc_ast_node(f, Ast_CompoundLit); result->CompoundLit.type = type; @@ -944,7 +965,7 @@ Ast *ast_field(AstFile *f, Array<Ast *> const &names, Ast *type, Ast *default_va result->Field.default_value = default_value; result->Field.flags = flags; result->Field.tag = tag; - result->Field.docs = docs; + result->Field.docs = docs; result->Field.comment = comment; return result; } @@ -1050,15 +1071,14 @@ Ast *ast_struct_type(AstFile *f, Token token, Slice<Ast *> fields, isize field_c } -Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> const &variants, Ast *polymorphic_params, Ast *align, bool no_nil, bool maybe, +Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> const &variants, Ast *polymorphic_params, Ast *align, UnionTypeKind kind, Token where_token, Array<Ast *> const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_UnionType); result->UnionType.token = token; result->UnionType.variants = slice_from_array(variants); result->UnionType.polymorphic_params = polymorphic_params; result->UnionType.align = align; - result->UnionType.no_nil = no_nil; - result->UnionType.maybe = maybe; + result->UnionType.kind = kind; result->UnionType.where_token = where_token; result->UnionType.where_clauses = slice_from_array(where_clauses); return result; @@ -1234,7 +1254,7 @@ CommentGroup *consume_comment_group(AstFile *f, isize n, isize *end_line_) { return comments; } -void comsume_comment_groups(AstFile *f, Token prev) { +void consume_comment_groups(AstFile *f, Token prev) { if (f->curr_token.kind == Token_Comment) { CommentGroup *comment = nullptr; isize end_line = 0; @@ -1278,7 +1298,7 @@ Token advance_token(AstFile *f) { if (ok) { switch (f->curr_token.kind) { case Token_Comment: - comsume_comment_groups(f, prev); + consume_comment_groups(f, prev); break; case Token_Semicolon: if (ignore_newlines(f) && f->curr_token.string == "\n") { @@ -1517,7 +1537,7 @@ void fix_advance_to_next_stmt(AstFile *f) { Token expect_closing(AstFile *f, TokenKind kind, String context) { if (f->curr_token.kind != kind && f->curr_token.kind == Token_Semicolon && - f->curr_token.string == "\n") { + (f->curr_token.string == "\n" || f->curr_token.kind == Token_EOF)) { Token tok = f->prev_token; tok.pos.column += cast(i32)tok.string.len; syntax_error(tok, "Missing ',' before newline in %.*s", LIT(context)); @@ -1539,6 +1559,7 @@ void assign_removal_flag_to_semicolon(AstFile *f) { switch (curr_token->kind) { case Token_CloseBrace: case Token_CloseParen: + case Token_EOF: ok = true; break; } @@ -1555,7 +1576,7 @@ void assign_removal_flag_to_semicolon(AstFile *f) { } } -void expect_semicolon(AstFile *f, Ast *s) { +void expect_semicolon(AstFile *f) { Token prev_token = {}; if (allow_token(f, Token_Semicolon)) { @@ -1580,17 +1601,17 @@ void expect_semicolon(AstFile *f, Ast *s) { if (f->curr_token.kind == Token_EOF) { return; } - - if (s != nullptr) { - return; - } switch (f->curr_token.kind) { case Token_EOF: return; } - String p = token_to_string(f->curr_token); - syntax_error(prev_token, "Expected ';', got %.*s", LIT(p)); - fix_advance_to_next_stmt(f); + + if (f->curr_token.pos.line == f->prev_token.pos.line) { + String p = token_to_string(f->curr_token); + prev_token.pos = token_pos_end(prev_token); + syntax_error(prev_token, "Expected ';', got %.*s", LIT(p)); + fix_advance_to_next_stmt(f); + } } @@ -1689,6 +1710,46 @@ Array<Ast *> parse_element_list(AstFile *f) { return elems; } +CommentGroup *consume_line_comment(AstFile *f) { + CommentGroup *comment = f->line_comment; + if (f->line_comment == f->lead_comment) { + f->lead_comment = nullptr; + } + f->line_comment = nullptr; + return comment; + +} + +Array<Ast *> parse_enum_field_list(AstFile *f) { + auto elems = array_make<Ast *>(heap_allocator()); + + while (f->curr_token.kind != Token_CloseBrace && + f->curr_token.kind != Token_EOF) { + CommentGroup *docs = f->lead_comment; + CommentGroup *comment = nullptr; + Ast *name = parse_value(f); + Ast *value = nullptr; + if (f->curr_token.kind == Token_Eq) { + Token eq = expect_token(f, Token_Eq); + value = parse_value(f); + } + + comment = consume_line_comment(f); + + Ast *elem = ast_enum_field_value(f, name, value, docs, comment); + array_add(&elems, elem); + + if (!allow_token(f, Token_Comma)) { + break; + } + + if (!elem->EnumFieldValue.comment) { + elem->EnumFieldValue.comment = consume_line_comment(f); + } + } + + return elems; +} Ast *parse_literal_value(AstFile *f, Ast *type) { Array<Ast *> elems = {}; @@ -1793,6 +1854,8 @@ void parse_proc_tags(AstFile *f, u64 *tags) { ELSE_IF_ADD_TAG(require_results) ELSE_IF_ADD_TAG(bounds_check) ELSE_IF_ADD_TAG(no_bounds_check) + ELSE_IF_ADD_TAG(type_assert) + ELSE_IF_ADD_TAG(no_type_assert) else { syntax_error(tag_expr, "Unknown procedure type tag #%.*s", LIT(tag_name)); } @@ -1803,6 +1866,10 @@ void parse_proc_tags(AstFile *f, u64 *tags) { if ((*tags & ProcTag_bounds_check) && (*tags & ProcTag_no_bounds_check)) { syntax_error(f->curr_token, "You cannot apply both #bounds_check and #no_bounds_check to a procedure"); } + + if ((*tags & ProcTag_type_assert) && (*tags & ProcTag_no_type_assert)) { + syntax_error(f->curr_token, "You cannot apply both #type_assert and #no_type_assert to a procedure"); + } } @@ -1950,11 +2017,23 @@ Ast *parse_check_directive_for_statement(Ast *s, Token const &tag_token, u16 sta syntax_error(tag_token, "#bounds_check and #no_bounds_check cannot be applied together"); } break; + case StateFlag_type_assert: + if ((s->state_flags & StateFlag_no_type_assert) != 0) { + syntax_error(tag_token, "#type_assert and #no_type_assert cannot be applied together"); + } + break; + case StateFlag_no_type_assert: + if ((s->state_flags & StateFlag_type_assert) != 0) { + syntax_error(tag_token, "#type_assert and #no_type_assert cannot be applied together"); + } + break; } switch (state_flag) { case StateFlag_bounds_check: case StateFlag_no_bounds_check: + case StateFlag_type_assert: + case StateFlag_no_type_assert: switch (s->kind) { case Ast_BlockStmt: case Ast_IfStmt: @@ -2063,6 +2142,22 @@ Ast *parse_operand(AstFile *f, bool lhs) { return original_type; } else if (name.string == "partial") { Ast *tag = ast_basic_directive(f, token, name); + Ast *original_expr = parse_expr(f, lhs); + Ast *expr = unparen_expr(original_expr); + switch (expr->kind) { + case Ast_ArrayType: + syntax_error(expr, "#partial has been replaced with #sparse for non-contiguous enumerated array types"); + break; + case Ast_CompoundLit: + expr->CompoundLit.tag = tag; + break; + default: + syntax_error(expr, "Expected a compound literal after #%.*s, got %.*s", LIT(name.string), LIT(ast_strings[expr->kind])); + break; + } + return original_expr; + } else if (name.string == "sparse") { + Ast *tag = ast_basic_directive(f, token, name); Ast *original_type = parse_type(f); Ast *type = unparen_expr(original_type); switch (type->kind) { @@ -2078,6 +2173,12 @@ Ast *parse_operand(AstFile *f, bool lhs) { } else if (name.string == "no_bounds_check") { Ast *operand = parse_expr(f, lhs); return parse_check_directive_for_statement(operand, name, StateFlag_no_bounds_check); + } else if (name.string == "type_assert") { + Ast *operand = parse_expr(f, lhs); + return parse_check_directive_for_statement(operand, name, StateFlag_type_assert); + } else if (name.string == "no_type_assert") { + Ast *operand = parse_expr(f, lhs); + return parse_check_directive_for_statement(operand, name, StateFlag_no_type_assert); } else if (name.string == "relative") { Ast *tag = ast_basic_directive(f, token, name); tag = parse_call_expr(f, tag); @@ -2174,6 +2275,12 @@ Ast *parse_operand(AstFile *f, bool lhs) { if (tags & ProcTag_bounds_check) { body->state_flags |= StateFlag_bounds_check; } + if (tags & ProcTag_no_type_assert) { + body->state_flags |= StateFlag_no_type_assert; + } + if (tags & ProcTag_type_assert) { + body->state_flags |= StateFlag_type_assert; + } return ast_proc_lit(f, type, body, tags, where_token, where_clauses); } else if (allow_token(f, Token_do)) { @@ -2367,6 +2474,9 @@ Ast *parse_operand(AstFile *f, bool lhs) { Ast *align = nullptr; bool no_nil = false; bool maybe = false; + bool shared_nil = false; + + UnionTypeKind union_kind = UnionType_Normal; Token start_token = f->curr_token; @@ -2393,6 +2503,11 @@ Ast *parse_operand(AstFile *f, bool lhs) { syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); } no_nil = true; + } else if (tag.string == "shared_nil") { + if (shared_nil) { + syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); + } + shared_nil = true; } else if (tag.string == "maybe") { if (maybe) { syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); @@ -2405,6 +2520,21 @@ Ast *parse_operand(AstFile *f, bool lhs) { if (no_nil && maybe) { syntax_error(f->curr_token, "#maybe and #no_nil cannot be applied together"); } + if (no_nil && shared_nil) { + syntax_error(f->curr_token, "#shared_nil and #no_nil cannot be applied together"); + } + if (shared_nil && maybe) { + syntax_error(f->curr_token, "#maybe and #shared_nil cannot be applied together"); + } + + + if (maybe) { + union_kind = UnionType_maybe; + } else if (no_nil) { + union_kind = UnionType_no_nil; + } else if (shared_nil) { + union_kind = UnionType_shared_nil; + } skip_possible_newline_for_literal(f); @@ -2436,7 +2566,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { Token close = expect_closing_brace_of_field_list(f); - return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, maybe, where_token, where_clauses); + return ast_union_type(f, token, variants, polymorphic_params, align, union_kind, where_token, where_clauses); } break; case Token_enum: { @@ -2449,7 +2579,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { skip_possible_newline_for_literal(f); Token open = expect_token(f, Token_OpenBrace); - Array<Ast *> values = parse_element_list(f); + Array<Ast *> values = parse_enum_field_list(f); Token close = expect_closing_brace_of_field_list(f); return ast_enum_type(f, token, base_type, values); @@ -2908,64 +3038,62 @@ i32 token_precedence(AstFile *f, TokenKind t) { Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) { Ast *expr = parse_unary_expr(f, lhs); - for (i32 prec = token_precedence(f, f->curr_token.kind); prec >= prec_in; prec--) { - for (;;) { - Token op = f->curr_token; - i32 op_prec = token_precedence(f, op.kind); - if (op_prec != prec) { - // NOTE(bill): This will also catch operators that are not valid "binary" operators - break; + for (;;) { + Token op = f->curr_token; + i32 op_prec = token_precedence(f, op.kind); + if (op_prec < prec_in) { + // NOTE(bill): This will also catch operators that are not valid "binary" operators + break; + } + Token prev = f->prev_token; + switch (op.kind) { + case Token_if: + case Token_when: + if (prev.pos.line < op.pos.line) { + // NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition + goto loop_end; } - Token prev = f->prev_token; + break; + } + expect_operator(f); // NOTE(bill): error checks too + + if (op.kind == Token_Question) { + Ast *cond = expr; + // Token_Question + Ast *x = parse_expr(f, lhs); + Token token_c = expect_token(f, Token_Colon); + Ast *y = parse_expr(f, lhs); + expr = ast_ternary_if_expr(f, x, cond, y); + } else if (op.kind == Token_if || op.kind == Token_when) { + Ast *x = expr; + Ast *cond = parse_expr(f, lhs); + Token tok_else = expect_token(f, Token_else); + Ast *y = parse_expr(f, lhs); + switch (op.kind) { case Token_if: + expr = ast_ternary_if_expr(f, x, cond, y); + break; case Token_when: - if (prev.pos.line < op.pos.line) { - // NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition - goto loop_end; - } + expr = ast_ternary_when_expr(f, x, cond, y); break; } - expect_operator(f); // NOTE(bill): error checks too - - if (op.kind == Token_Question) { - Ast *cond = expr; - // Token_Question - Ast *x = parse_expr(f, lhs); - Token token_c = expect_token(f, Token_Colon); - Ast *y = parse_expr(f, lhs); - expr = ast_ternary_if_expr(f, x, cond, y); - } else if (op.kind == Token_if || op.kind == Token_when) { - Ast *x = expr; - Ast *cond = parse_expr(f, lhs); - Token tok_else = expect_token(f, Token_else); - Ast *y = parse_expr(f, lhs); - - switch (op.kind) { - case Token_if: - expr = ast_ternary_if_expr(f, x, cond, y); - break; - case Token_when: - expr = ast_ternary_when_expr(f, x, cond, y); - break; - } + } else { + Ast *right = parse_binary_expr(f, false, op_prec+1); + if (right == nullptr) { + syntax_error(op, "Expected expression on the right-hand side of the binary operator '%.*s'", LIT(op.string)); + } + if (op.kind == Token_or_else) { + // NOTE(bill): easier to handle its logic different with its own AST kind + expr = ast_or_else_expr(f, expr, op, right); } else { - Ast *right = parse_binary_expr(f, false, prec+1); - if (right == nullptr) { - syntax_error(op, "Expected expression on the right-hand side of the binary operator '%.*s'", LIT(op.string)); - } - if (op.kind == Token_or_else) { - // NOTE(bill): easier to handle its logic different with its own AST kind - expr = ast_or_else_expr(f, expr, op, right); - } else { - expr = ast_binary_expr(f, op, expr, right); - } + expr = ast_binary_expr(f, op, expr, right); } - - lhs = false; } - loop_end:; + + lhs = false; } + loop_end:; return expr; } @@ -3075,7 +3203,7 @@ Ast *parse_foreign_block(AstFile *f, Token token) { Ast *body = ast_block_stmt(f, decls, open, close); Ast *decl = ast_foreign_block_decl(f, token, foreign_library, body, docs); - expect_semicolon(f, decl); + expect_semicolon(f); return decl; } @@ -3121,15 +3249,11 @@ Ast *parse_value_decl(AstFile *f, Array<Ast *> names, CommentGroup *docs) { } if (f->expr_level >= 0) { - Ast *end = nullptr; - if (!is_mutable && values.count > 0) { - end = values[values.count-1]; - } if (f->curr_token.kind == Token_CloseBrace && f->curr_token.pos.line == f->prev_token.pos.line) { } else { - expect_semicolon(f, end); + expect_semicolon(f); } } @@ -3311,12 +3435,18 @@ ProcCallingConvention string_to_calling_convention(String s) { if (s == "fast") return ProcCC_FastCall; if (s == "none") return ProcCC_None; if (s == "naked") return ProcCC_Naked; + + if (s == "win64") return ProcCC_Win64; + if (s == "sysv") return ProcCC_SysV; + if (s == "system") { if (build_context.metrics.os == TargetOs_windows) { return ProcCC_StdCall; } return ProcCC_CDecl; } + + return ProcCC_Invalid; } @@ -3403,12 +3533,13 @@ enum FieldPrefixKind : i32 { FieldPrefix_Unknown = -1, FieldPrefix_Invalid = 0, - FieldPrefix_using, + FieldPrefix_using, // implies #subtype FieldPrefix_const, FieldPrefix_no_alias, FieldPrefix_c_vararg, FieldPrefix_auto_cast, FieldPrefix_any_int, + FieldPrefix_subtype, // does not imply `using` semantics }; struct ParseFieldPrefixMapping { @@ -3425,6 +3556,7 @@ gb_global ParseFieldPrefixMapping parse_field_prefix_mappings[] = { {str_lit("c_vararg"), Token_Hash, FieldPrefix_c_vararg, FieldFlag_c_vararg}, {str_lit("const"), Token_Hash, FieldPrefix_const, FieldFlag_const}, {str_lit("any_int"), Token_Hash, FieldPrefix_any_int, FieldFlag_any_int}, + {str_lit("subtype"), Token_Hash, FieldPrefix_subtype, FieldFlag_subtype}, }; @@ -4029,11 +4161,7 @@ Ast *parse_return_stmt(AstFile *f) { advance_token(f); } - Ast *end = nullptr; - if (results.count > 0) { - end = results[results.count-1]; - } - expect_semicolon(f, end); + expect_semicolon(f); return ast_return_stmt(f, token, results); } @@ -4284,7 +4412,7 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) { syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly"); } - expect_semicolon(f, s); + expect_semicolon(f); return s; } @@ -4342,7 +4470,7 @@ Ast *parse_foreign_decl(AstFile *f) { } else { s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); } - expect_semicolon(f, s); + expect_semicolon(f); return s; } } @@ -4481,7 +4609,7 @@ Ast *parse_stmt(AstFile *f) { case Token_Not: case Token_And: s = parse_simple_stmt(f, StmtAllowFlag_Label); - expect_semicolon(f, s); + expect_semicolon(f); return s; @@ -4509,7 +4637,7 @@ Ast *parse_stmt(AstFile *f) { label = parse_ident(f); } s = ast_branch_stmt(f, token, label); - expect_semicolon(f, s); + expect_semicolon(f); return s; } @@ -4524,12 +4652,12 @@ Ast *parse_stmt(AstFile *f) { Array<Ast *> list = parse_lhs_expr_list(f); if (list.count == 0) { syntax_error(token, "Illegal use of 'using' statement"); - expect_semicolon(f, nullptr); + expect_semicolon(f); return ast_bad_stmt(f, token, f->curr_token); } if (f->curr_token.kind != Token_Colon) { - expect_semicolon(f, list[list.count-1]); + expect_semicolon(f); return ast_using_stmt(f, token, list); } expect_token_after(f, Token_Colon, "identifier list"); @@ -4561,6 +4689,12 @@ Ast *parse_stmt(AstFile *f) { } else if (tag == "no_bounds_check") { s = parse_stmt(f); return parse_check_directive_for_statement(s, name, StateFlag_no_bounds_check); + } else if (tag == "type_assert") { + s = parse_stmt(f); + return parse_check_directive_for_statement(s, name, StateFlag_type_assert); + } else if (tag == "no_type_assert") { + s = parse_stmt(f); + return parse_check_directive_for_statement(s, name, StateFlag_no_type_assert); } else if (tag == "partial") { s = parse_stmt(f); switch (s->kind) { @@ -4580,13 +4714,13 @@ Ast *parse_stmt(AstFile *f) { } else if (tag == "assert" || tag == "panic") { Ast *t = ast_basic_directive(f, hash_token, name); Ast *stmt = ast_expr_stmt(f, parse_call_expr(f, t)); - expect_semicolon(f, stmt); + expect_semicolon(f); return stmt; } else if (name.string == "force_inline" || name.string == "force_no_inline") { Ast *expr = parse_force_inlining_operand(f, name); Ast *stmt = ast_expr_stmt(f, expr); - expect_semicolon(f, stmt); + expect_semicolon(f); return stmt; } else if (tag == "unroll") { return parse_unrolled_for_loop(f, name); @@ -4608,7 +4742,7 @@ Ast *parse_stmt(AstFile *f) { case Token_Semicolon: s = ast_empty_stmt(f, token); - expect_semicolon(f, nullptr); + expect_semicolon(f); return s; } @@ -4736,12 +4870,6 @@ ParseFileError init_ast_file(AstFile *f, String fullpath, TokenPos *err_pos) { f->prev_token = f->tokens[f->prev_token_index]; f->curr_token = f->tokens[f->curr_token_index]; - isize const page_size = 4*1024; - isize block_size = 2*f->tokens.count*gb_size_of(Ast); - block_size = ((block_size + page_size-1)/page_size) * page_size; - block_size = gb_clamp(block_size, page_size, DEFAULT_MINIMUM_BLOCK_SIZE); - f->arena.minimum_block_size = block_size; - array_init(&f->comments, heap_allocator(), 0, 0); array_init(&f->imports, heap_allocator(), 0, 0); @@ -5362,6 +5490,15 @@ isize calc_decl_count(Ast *decl) { count += calc_decl_count(decl->BlockStmt.stmts.data[i]); } break; + case Ast_WhenStmt: + { + isize inner_count = calc_decl_count(decl->WhenStmt.body); + if (decl->WhenStmt.else_stmt) { + inner_count = gb_max(inner_count, calc_decl_count(decl->WhenStmt.else_stmt)); + } + count += inner_count; + } + break; case Ast_ValueDecl: count = decl->ValueDecl.names.count; break; @@ -5389,7 +5526,7 @@ bool parse_file(Parser *p, AstFile *f) { String filepath = f->tokenizer.fullpath; String base_dir = dir_from_path(filepath); if (f->curr_token.kind == Token_Comment) { - comsume_comment_groups(f, f->prev_token); + consume_comment_groups(f, f->prev_token); } CommentGroup *docs = f->lead_comment; @@ -5403,6 +5540,15 @@ bool parse_file(Parser *p, AstFile *f) { if (f->package_token.kind != Token_package) { return false; } + if (docs != nullptr) { + TokenPos end = token_pos_end(docs->list[docs->list.count-1]); + if (end.line == f->package_token.pos.line || end.line+1 == f->package_token.pos.line) { + // Okay + } else { + docs = nullptr; + } + } + Token package_name = expect_token_after(f, Token_Ident, "package"); if (package_name.kind == Token_Ident) { if (package_name.string == "_") { @@ -5426,8 +5572,17 @@ bool parse_file(Parser *p, AstFile *f) { if (!parse_build_tag(tok, lc)) { return false; } - } else if (lc == "+private") { - f->flags |= AstFile_IsPrivate; + } else if (string_starts_with(lc, str_lit("+private"))) { + f->flags |= AstFile_IsPrivatePkg; + String command = string_trim_starts_with(lc, str_lit("+private ")); + command = string_trim_whitespace(command); + if (lc == "+private") { + f->flags |= AstFile_IsPrivatePkg; + } else if (command == "package") { + f->flags |= AstFile_IsPrivatePkg; + } else if (command == "file") { + f->flags |= AstFile_IsPrivateFile; + } } else if (lc == "+lazy") { if (build_context.ignore_lazy) { // Ignore @@ -5445,7 +5600,7 @@ bool parse_file(Parser *p, AstFile *f) { } Ast *pd = ast_package_decl(f, f->package_token, package_name, docs, f->line_comment); - expect_semicolon(f, pd); + expect_semicolon(f); f->pkg_decl = pd; if (f->error_count == 0) { @@ -5579,8 +5734,24 @@ ParseFileError parse_packages(Parser *p, String init_filename) { error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return ParseFile_WrongExtension; } + } else if (init_fullpath.len != 0) { + String path = init_fullpath; + if (path[path.len-1] == '/') { + path.len -= 1; + } + if ((build_context.command_kind & Command__does_build) && + build_context.build_mode == BuildMode_Executable) { + String short_path = filename_from_path(path); + char *cpath = alloc_cstring(heap_allocator(), short_path); + defer (gb_free(heap_allocator(), cpath)); + + if (gb_file_exists(cpath)) { + error_line("Please specify the executable name with -out:<string> as a directory exists with the same name in the current working directory"); + return ParseFile_DirectoryAlreadyExists; + } + } } - + { // Add these packages serially and then process them parallel mutex_lock(&p->wait_mutex); diff --git a/src/parser.hpp b/src/parser.hpp index b83822cbf..c7b4fd0d8 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -46,6 +46,7 @@ enum ParseFileError { ParseFile_InvalidToken, ParseFile_GeneralError, ParseFile_FileTooLarge, + ParseFile_DirectoryAlreadyExists, ParseFile_Count, }; @@ -78,9 +79,11 @@ struct ImportedFile { }; enum AstFileFlag : u32 { - AstFile_IsPrivate = 1<<0, - AstFile_IsTest = 1<<1, - AstFile_IsLazy = 1<<2, + AstFile_IsPrivatePkg = 1<<0, + AstFile_IsPrivateFile = 1<<1, + + AstFile_IsTest = 1<<3, + AstFile_IsLazy = 1<<4, }; enum AstDelayQueueKind { @@ -95,8 +98,6 @@ struct AstFile { AstPackage * pkg; Scope * scope; - Arena arena; - Ast * pkg_decl; String fullpath; Tokenizer tokenizer; @@ -226,6 +227,8 @@ enum ProcInlining { enum ProcTag { ProcTag_bounds_check = 1<<0, ProcTag_no_bounds_check = 1<<1, + ProcTag_type_assert = 1<<2, + ProcTag_no_type_assert = 1<<3, ProcTag_require_results = 1<<4, ProcTag_optional_ok = 1<<5, @@ -245,12 +248,30 @@ enum ProcCallingConvention : i32 { ProcCC_InlineAsm = 8, + ProcCC_Win64 = 9, + ProcCC_SysV = 10, + + ProcCC_MAX, ProcCC_ForeignBlockDefault = -1, }; +char const *proc_calling_convention_strings[ProcCC_MAX] = { + "", + "odin", + "contextless", + "cdecl", + "stdcall", + "fastcall", + "none", + "naked", + "inlineasm", + "win64", + "sysv", +}; + ProcCallingConvention default_calling_convention(void) { return ProcCC_Odin; } @@ -258,6 +279,8 @@ ProcCallingConvention default_calling_convention(void) { enum StateFlag : u8 { StateFlag_bounds_check = 1<<0, StateFlag_no_bounds_check = 1<<1, + StateFlag_type_assert = 1<<2, + StateFlag_no_type_assert = 1<<3, StateFlag_BeenHandled = 1<<7, }; @@ -276,6 +299,7 @@ enum FieldFlag : u32 { FieldFlag_auto_cast = 1<<4, FieldFlag_const = 1<<5, FieldFlag_any_int = 1<<6, + FieldFlag_subtype = 1<<7, // Internal use by the parser only FieldFlag_Tags = 1<<10, @@ -283,7 +307,7 @@ enum FieldFlag : u32 { // Parameter List Restrictions FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast|FieldFlag_const|FieldFlag_any_int, - FieldFlag_Struct = FieldFlag_using|FieldFlag_Tags, + FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; enum StmtAllowFlag { @@ -306,6 +330,13 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = { "intel", }; +enum UnionTypeKind : u8 { + UnionType_Normal = 0, + UnionType_maybe = 1, + UnionType_no_nil = 2, + UnionType_shared_nil = 3, +}; + #define AST_KINDS \ AST_KIND(Ident, "identifier", struct { \ Token token; \ @@ -344,6 +375,7 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = { Slice<Ast *> elems; \ Token open, close; \ i64 max_count; \ + Ast *tag; \ }) \ AST_KIND(_ExprBegin, "", bool) \ AST_KIND(BadExpr, "bad expression", struct { Token begin, end; }) \ @@ -383,6 +415,12 @@ AST_KIND(_ExprBegin, "", bool) \ void *sce_temp_data; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ + AST_KIND(EnumFieldValue, "enum field value", struct { \ + Ast *name; \ + Ast *value; \ + CommentGroup *docs; \ + CommentGroup *comment; \ + }) \ AST_KIND(TernaryIfExpr, "ternary if expression", struct { Ast *x, *cond, *y; }) \ AST_KIND(TernaryWhenExpr, "ternary when expression", struct { Ast *x, *cond, *y; }) \ AST_KIND(OrElseExpr, "or_else expression", struct { Ast *x; Token token; Ast *y; }) \ @@ -647,8 +685,7 @@ AST_KIND(_TypeBegin, "", bool) \ Slice<Ast *> variants; \ Ast *polymorphic_params; \ Ast * align; \ - bool maybe; \ - bool no_nil; \ + UnionTypeKind kind; \ Token where_token; \ Slice<Ast *> where_clauses; \ }) \ @@ -769,10 +806,10 @@ gb_inline bool is_ast_when_stmt(Ast *node) { return node->kind == Ast_WhenStmt; } -gb_global gb_thread_local Arena global_ast_arena = {}; +gb_global gb_thread_local Arena global_thread_local_ast_arena = {}; gbAllocator ast_allocator(AstFile *f) { - Arena *arena = f ? &f->arena : &global_ast_arena; + Arena *arena = &global_thread_local_ast_arena; return arena_allocator(arena); } diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 6ef0db215..54c3ec1f1 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -39,6 +39,7 @@ Token ast_token(Ast *node) { case Ast_SliceExpr: return node->SliceExpr.open; case Ast_Ellipsis: return node->Ellipsis.token; case Ast_FieldValue: return node->FieldValue.eq; + case Ast_EnumFieldValue: return ast_token(node->EnumFieldValue.name); case Ast_DerefExpr: return node->DerefExpr.op; case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x); case Ast_TernaryWhenExpr: return ast_token(node->TernaryWhenExpr.x); @@ -178,6 +179,11 @@ Token ast_end_token(Ast *node) { } return node->Ellipsis.token; case Ast_FieldValue: return ast_end_token(node->FieldValue.value); + case Ast_EnumFieldValue: + if (node->EnumFieldValue.value) { + return ast_end_token(node->EnumFieldValue.value); + } + return ast_end_token(node->EnumFieldValue.name); case Ast_DerefExpr: return node->DerefExpr.op; case Ast_TernaryIfExpr: return ast_end_token(node->TernaryIfExpr.y); case Ast_TernaryWhenExpr: return ast_end_token(node->TernaryWhenExpr.y); diff --git a/src/path.cpp b/src/path.cpp new file mode 100644 index 000000000..6f83c39ea --- /dev/null +++ b/src/path.cpp @@ -0,0 +1,394 @@ +/*
+ Path handling utilities.
+*/
+String remove_extension_from_path(String const &s) {
+ for (isize i = s.len-1; i >= 0; i--) {
+ if (s[i] == '.') {
+ return substring(s, 0, i);
+ }
+ }
+ return s;
+}
+
+String remove_directory_from_path(String const &s) {
+ isize len = 0;
+ for (isize i = s.len-1; i >= 0; i--) {
+ if (s[i] == '/' ||
+ s[i] == '\\') {
+ break;
+ }
+ len += 1;
+ }
+ return substring(s, s.len-len, s.len);
+}
+
+bool path_is_directory(String path);
+
+String directory_from_path(String const &s) {
+ if (path_is_directory(s)) {
+ return s;
+ }
+
+ isize i = s.len-1;
+ for (; i >= 0; i--) {
+ if (s[i] == '/' ||
+ s[i] == '\\') {
+ break;
+ }
+ }
+ if (i >= 0) {
+ return substring(s, 0, i);
+ }
+ return substring(s, 0, 0);
+}
+
+#if defined(GB_SYSTEM_WINDOWS)
+ bool path_is_directory(String path) {
+ gbAllocator a = heap_allocator();
+ String16 wstr = string_to_string16(a, path);
+ defer (gb_free(a, wstr.text));
+
+ i32 attribs = GetFileAttributesW(wstr.text);
+ if (attribs < 0) return false;
+
+ return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ }
+
+#else
+ bool path_is_directory(String path) {
+ gbAllocator a = heap_allocator();
+ char *copy = cast(char *)copy_string(a, path).text;
+ defer (gb_free(a, copy));
+
+ struct stat s;
+ if (stat(copy, &s) == 0) {
+ return (s.st_mode & S_IFDIR) != 0;
+ }
+ return false;
+ }
+#endif
+
+
+String path_to_full_path(gbAllocator a, String path) {
+ gbAllocator ha = heap_allocator();
+ char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
+ defer (gb_free(ha, path_c));
+
+ char *fullpath = gb_path_get_full_name(a, path_c);
+ String res = string_trim_whitespace(make_string_c(fullpath));
+#if defined(GB_SYSTEM_WINDOWS)
+ for (isize i = 0; i < res.len; i++) {
+ if (res.text[i] == '\\') {
+ res.text[i] = '/';
+ }
+ }
+#endif
+ return copy_string(a, res);
+}
+
+struct Path {
+ String basename;
+ String name;
+ String ext;
+};
+
+// NOTE(Jeroen): Naively turns a Path into a string.
+String path_to_string(gbAllocator a, Path path) {
+ if (path.basename.len + path.name.len + path.ext.len == 0) {
+ return make_string(nullptr, 0);
+ }
+
+ isize len = path.basename.len + 1 + path.name.len + 1;
+ if (path.ext.len > 0) {
+ len += path.ext.len + 1;
+ }
+
+ u8 *str = gb_alloc_array(a, u8, len);
+
+ isize i = 0;
+ gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
+ gb_memmove(str+i, "/", 1); i += 1;
+ gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len;
+ if (path.ext.len > 0) {
+ gb_memmove(str+i, ".", 1); i += 1;
+ gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len;
+ }
+ str[i] = 0;
+
+ String res = make_string(str, i);
+ res = string_trim_whitespace(res);
+ return res;
+}
+
+// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
+String path_to_full_path(gbAllocator a, Path path) {
+ String temp = path_to_string(heap_allocator(), path);
+ defer (gb_free(heap_allocator(), temp.text));
+
+ return path_to_full_path(a, temp);
+}
+
+// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
+// and then breaks it into its components to make a Path.
+Path path_from_string(gbAllocator a, String const &path) {
+ Path res = {};
+
+ if (path.len == 0) return res;
+
+ String fullpath = path_to_full_path(a, path);
+ defer (gb_free(heap_allocator(), fullpath.text));
+
+ res.basename = directory_from_path(fullpath);
+ res.basename = copy_string(a, res.basename);
+
+ if (path_is_directory(fullpath)) {
+ // It's a directory. We don't need to tinker with the name and extension.
+ // It could have a superfluous trailing `/`. Remove it if so.
+ if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
+ res.basename.len--;
+ }
+ return res;
+ }
+
+ isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
+ res.name = substring(fullpath, name_start, fullpath.len);
+ res.name = remove_extension_from_path(res.name);
+ res.name = copy_string(a, res.name);
+
+ res.ext = path_extension(fullpath, false); // false says not to include the dot.
+ res.ext = copy_string(a, res.ext);
+ return res;
+}
+
+// NOTE(Jeroen): Takes a path String and returns the last path element.
+String last_path_element(String const &path) {
+ isize count = 0;
+ u8 * start = (u8 *)(&path.text[path.len - 1]);
+ for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
+ count++;
+ start--;
+ }
+ if (count > 0) {
+ start++; // Advance past the `/` and return the substring.
+ String res = make_string(start, count);
+ return res;
+ }
+ // Must be a root path like `/` or `C:/`, return empty String.
+ return STR_LIT("");
+}
+
+bool path_is_directory(Path path) {
+ String path_string = path_to_full_path(heap_allocator(), path);
+ defer (gb_free(heap_allocator(), path_string.text));
+
+ return path_is_directory(path_string);
+}
+
+struct FileInfo {
+ String name;
+ String fullpath;
+ i64 size;
+ bool is_dir;
+};
+
+enum ReadDirectoryError {
+ ReadDirectory_None,
+
+ ReadDirectory_InvalidPath,
+ ReadDirectory_NotExists,
+ ReadDirectory_Permission,
+ ReadDirectory_NotDir,
+ ReadDirectory_Empty,
+ ReadDirectory_Unknown,
+
+ ReadDirectory_COUNT,
+};
+
+i64 get_file_size(String path) {
+ char *c_str = alloc_cstring(heap_allocator(), path);
+ defer (gb_free(heap_allocator(), c_str));
+
+ gbFile f = {};
+ gbFileError err = gb_file_open(&f, c_str);
+ defer (gb_file_close(&f));
+ if (err != gbFileError_None) {
+ return -1;
+ }
+ return gb_file_size(&f);
+}
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+ GB_ASSERT(fi != nullptr);
+
+ gbAllocator a = heap_allocator();
+
+ while (path.len > 0) {
+ Rune end = path[path.len-1];
+ if (end == '/') {
+ path.len -= 1;
+ } else if (end == '\\') {
+ path.len -= 1;
+ } else {
+ break;
+ }
+ }
+
+ if (path.len == 0) {
+ return ReadDirectory_InvalidPath;
+ }
+ {
+ char *c_str = alloc_cstring(a, path);
+ defer (gb_free(a, c_str));
+
+ gbFile f = {};
+ gbFileError file_err = gb_file_open(&f, c_str);
+ defer (gb_file_close(&f));
+
+ switch (file_err) {
+ case gbFileError_Invalid: return ReadDirectory_InvalidPath;
+ case gbFileError_NotExists: return ReadDirectory_NotExists;
+ // case gbFileError_Permission: return ReadDirectory_Permission;
+ }
+ }
+
+ if (!path_is_directory(path)) {
+ return ReadDirectory_NotDir;
+ }
+
+
+ char *new_path = gb_alloc_array(a, char, path.len+3);
+ defer (gb_free(a, new_path));
+
+ gb_memmove(new_path, path.text, path.len);
+ gb_memmove(new_path+path.len, "/*", 2);
+ new_path[path.len+2] = 0;
+
+ String np = make_string(cast(u8 *)new_path, path.len+2);
+ String16 wstr = string_to_string16(a, np);
+ defer (gb_free(a, wstr.text));
+
+ WIN32_FIND_DATAW file_data = {};
+ HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
+ if (find_file == INVALID_HANDLE_VALUE) {
+ return ReadDirectory_Unknown;
+ }
+ defer (FindClose(find_file));
+
+ array_init(fi, a, 0, 100);
+
+ do {
+ wchar_t *filename_w = file_data.cFileName;
+ i64 size = cast(i64)file_data.nFileSizeLow;
+ size |= (cast(i64)file_data.nFileSizeHigh) << 32;
+ String name = string16_to_string(a, make_string16_c(filename_w));
+ if (name == "." || name == "..") {
+ gb_free(a, name.text);
+ continue;
+ }
+
+ String filepath = {};
+ filepath.len = path.len+1+name.len;
+ filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+ defer (gb_free(a, filepath.text));
+ gb_memmove(filepath.text, path.text, path.len);
+ gb_memmove(filepath.text+path.len, "/", 1);
+ gb_memmove(filepath.text+path.len+1, name.text, name.len);
+
+ FileInfo info = {};
+ info.name = name;
+ info.fullpath = path_to_full_path(a, filepath);
+ info.size = size;
+ info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ array_add(fi, info);
+ } while (FindNextFileW(find_file, &file_data));
+
+ if (fi->count == 0) {
+ return ReadDirectory_Empty;
+ }
+
+ return ReadDirectory_None;
+}
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
+
+#include <dirent.h>
+
+ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+ GB_ASSERT(fi != nullptr);
+
+ gbAllocator a = heap_allocator();
+
+ char *c_path = alloc_cstring(a, path);
+ defer (gb_free(a, c_path));
+
+ DIR *dir = opendir(c_path);
+ if (!dir) {
+ switch (errno) {
+ case ENOENT:
+ return ReadDirectory_NotExists;
+ case EACCES:
+ return ReadDirectory_Permission;
+ case ENOTDIR:
+ return ReadDirectory_NotDir;
+ default:
+ // ENOMEM: out of memory
+ // EMFILE: per-process limit on open fds reached
+ // ENFILE: system-wide limit on total open files reached
+ return ReadDirectory_Unknown;
+ }
+ GB_PANIC("unreachable");
+ }
+
+ array_init(fi, a, 0, 100);
+
+ for (;;) {
+ struct dirent *entry = readdir(dir);
+ if (entry == nullptr) {
+ break;
+ }
+
+ String name = make_string_c(entry->d_name);
+ if (name == "." || name == "..") {
+ continue;
+ }
+
+ String filepath = {};
+ filepath.len = path.len+1+name.len;
+ filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+ defer (gb_free(a, filepath.text));
+ gb_memmove(filepath.text, path.text, path.len);
+ gb_memmove(filepath.text+path.len, "/", 1);
+ gb_memmove(filepath.text+path.len+1, name.text, name.len);
+ filepath.text[filepath.len] = 0;
+
+
+ struct stat dir_stat = {};
+
+ if (stat((char *)filepath.text, &dir_stat)) {
+ continue;
+ }
+
+ if (S_ISDIR(dir_stat.st_mode)) {
+ continue;
+ }
+
+ i64 size = dir_stat.st_size;
+
+ FileInfo info = {};
+ info.name = name;
+ info.fullpath = path_to_full_path(a, filepath);
+ info.size = size;
+ array_add(fi, info);
+ }
+
+ if (fi->count == 0) {
+ return ReadDirectory_Empty;
+ }
+
+ return ReadDirectory_None;
+}
+#else
+#error Implement read_directory
+#endif
+
diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index ca7df3b53..b45997916 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -138,6 +138,15 @@ gb_inline bool ptr_set_exists(PtrSet<T> *s, T ptr) { return index != MAP_SENTINEL; } +template <typename T> +gb_inline isize ptr_entry_index(PtrSet<T> *s, T ptr) { + isize index = ptr_set__find(s, ptr).entry_index; + if (index != MAP_SENTINEL) { + return index; + } + return -1; +} + // Returns true if it already exists template <typename T> T ptr_set_add(PtrSet<T> *s, T ptr) { diff --git a/src/queue.cpp b/src/queue.cpp index d69a2845c..ee8b1b086 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -72,14 +72,38 @@ void mpmc_destroy(MPMCQueue<T> *q) { template <typename T> +bool mpmc_internal_grow(MPMCQueue<T> *q) { + mutex_lock(&q->mutex); + i32 old_size = q->mask+1; + i32 new_size = old_size*2; + resize_array_raw(&q->nodes, q->allocator, old_size, new_size); + if (q->nodes == nullptr) { + GB_PANIC("Unable to resize enqueue: %td -> %td", old_size, new_size); + mutex_unlock(&q->mutex); + return false; + } + resize_array_raw(&q->indices, q->allocator, old_size, new_size); + if (q->indices == nullptr) { + GB_PANIC("Unable to resize enqueue: %td -> %td", old_size, new_size); + mutex_unlock(&q->mutex); + return false; + } + mpmc_internal_init_indices(q->indices, old_size, new_size); + q->mask = new_size-1; + mutex_unlock(&q->mutex); + return true; +} + +template <typename T> i32 mpmc_enqueue(MPMCQueue<T> *q, T const &data) { GB_ASSERT(q->mask != 0); i32 head_idx = q->head_idx.load(std::memory_order_relaxed); for (;;) { - auto node = &q->nodes[head_idx & q->mask]; - auto node_idx_ptr = &q->indices[head_idx & q->mask]; + i32 index = head_idx & q->mask; + auto node = &q->nodes[index]; + auto node_idx_ptr = &q->indices[index]; i32 node_idx = node_idx_ptr->load(std::memory_order_acquire); i32 diff = node_idx - head_idx; @@ -91,24 +115,9 @@ i32 mpmc_enqueue(MPMCQueue<T> *q, T const &data) { return q->count.fetch_add(1, std::memory_order_release); } } else if (diff < 0) { - mutex_lock(&q->mutex); - i32 old_size = q->mask+1; - i32 new_size = old_size*2; - resize_array_raw(&q->nodes, q->allocator, old_size, new_size); - if (q->nodes == nullptr) { - GB_PANIC("Unable to resize enqueue: %td -> %td", old_size, new_size); - mutex_unlock(&q->mutex); - return -1; - } - resize_array_raw(&q->indices, q->allocator, old_size, new_size); - if (q->indices == nullptr) { - GB_PANIC("Unable to resize enqueue: %td -> %td", old_size, new_size); - mutex_unlock(&q->mutex); + if (!mpmc_internal_grow(q)) { return -1; } - mpmc_internal_init_indices(q->indices, old_size, new_size); - q->mask = new_size-1; - mutex_unlock(&q->mutex); } else { head_idx = q->head_idx.load(std::memory_order_relaxed); } diff --git a/src/string.cpp b/src/string.cpp index 800378689..616761265 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -10,10 +10,6 @@ struct String { u8 * text; isize len; - // u8 &operator[](isize i) { - // GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i); - // return text[i]; - // } u8 const &operator[](isize i) const { GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i); return text[i]; @@ -33,10 +29,6 @@ struct String { struct String16 { wchar_t *text; isize len; - wchar_t &operator[](isize i) { - GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i); - return text[i]; - } wchar_t const &operator[](isize i) const { GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i); return text[i]; @@ -195,8 +187,6 @@ template <isize N> bool operator > (String const &a, char const (&b)[N]) { retu template <isize N> bool operator <= (String const &a, char const (&b)[N]) { return str_le(a, make_string(cast(u8 *)b, N-1)); } template <isize N> bool operator >= (String const &a, char const (&b)[N]) { return str_ge(a, make_string(cast(u8 *)b, N-1)); } - - gb_inline bool string_starts_with(String const &s, String const &prefix) { if (prefix.len > s.len) { return false; @@ -230,6 +220,16 @@ gb_inline bool string_ends_with(String const &s, u8 suffix) { return s[s.len-1] == suffix; } + + +gb_inline String string_trim_starts_with(String const &s, String const &prefix) { + if (string_starts_with(s, prefix)) { + return substring(s, prefix.len, s.len); + } + return s; +} + + gb_inline isize string_extension_position(String const &str) { isize dot_pos = -1; isize i = str.len; @@ -245,15 +245,14 @@ gb_inline isize string_extension_position(String const &str) { return dot_pos; } -String path_extension(String const &str) { +String path_extension(String const &str, bool include_dot = true) { isize pos = string_extension_position(str); if (pos < 0) { return make_string(nullptr, 0); } - return substring(str, pos, str.len); + return substring(str, include_dot ? pos : pos + 1, str.len); } - String string_trim_whitespace(String str) { while (str.len > 0 && rune_is_whitespace(str[str.len-1])) { str.len--; @@ -299,38 +298,6 @@ String filename_from_path(String s) { return make_string(nullptr, 0); } -String remove_extension_from_path(String const &s) { - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '.') { - return substring(s, 0, i); - } - } - return s; -} - -String remove_directory_from_path(String const &s) { - isize len = 0; - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - len += 1; - } - return substring(s, s.len-len, s.len); -} - -String directory_from_path(String const &s) { - isize i = s.len-1; - for (; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - } - return substring(s, 0, i); -} - String concatenate_strings(gbAllocator a, String const &x, String const &y) { isize len = x.len+y.len; u8 *data = gb_alloc_array(a, u8, len+1); @@ -773,3 +740,34 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu return 2; } + + +bool string_is_valid_identifier(String str) { + if (str.len <= 0) return false; + + isize rune_count = 0; + + isize w = 0; + isize offset = 0; + while (offset < str.len) { + Rune r = 0; + w = utf8_decode(str.text, str.len, &r); + if (r == GB_RUNE_INVALID) { + return false; + } + + if (rune_count == 0) { + if (!rune_is_letter(r)) { + return false; + } + } else { + if (!rune_is_letter(r) && !rune_is_digit(r)) { + return false; + } + } + rune_count += 1; + offset += w; + } + + return true; +} diff --git a/src/threading.cpp b/src/threading.cpp index e9412b411..63e3415b2 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -68,6 +68,40 @@ void yield_thread(void); void yield_process(void); +struct MutexGuard { + MutexGuard() = delete; + MutexGuard(MutexGuard const &) = delete; + + MutexGuard(BlockingMutex *bm) : bm{bm} { + mutex_lock(this->bm); + } + MutexGuard(RecursiveMutex *rm) : rm{rm} { + mutex_lock(this->rm); + } + MutexGuard(BlockingMutex &bm) : bm{&bm} { + mutex_lock(this->bm); + } + MutexGuard(RecursiveMutex &rm) : rm{&rm} { + mutex_lock(this->rm); + } + ~MutexGuard() { + if (this->bm) { + mutex_unlock(this->bm); + } else if (this->rm) { + mutex_unlock(this->rm); + } + } + + operator bool() const { return true; } + + BlockingMutex *bm; + RecursiveMutex *rm; +}; + +#define MUTEX_GUARD_BLOCK(m) if (MutexGuard GB_DEFER_3(_mutex_guard_){m}) +#define MUTEX_GUARD(m) MutexGuard GB_DEFER_3(_mutex_guard_){m} + + #if defined(GB_SYSTEM_WINDOWS) struct BlockingMutex { SRWLOCK srwlock; @@ -296,6 +330,8 @@ u32 thread_current_id(void) { __asm__("mov %%gs:0x08,%0" : "=r"(thread_id)); #elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); +#elif defined(GB_SYSTEM_LINUX) + thread_id = gettid(); #else #error Unsupported architecture for thread_current_id() #endif @@ -315,6 +351,8 @@ gb_inline void yield_thread(void) { #endif #elif defined(GB_CPU_X86) _mm_pause(); +#elif defined(GB_CPU_ARM) + __asm__ volatile ("yield" : : : "memory"); #else #error Unknown architecture #endif @@ -448,7 +486,7 @@ void thread_set_name(Thread *t, char const *name) { #elif defined(GB_SYSTEM_OSX) // TODO(bill): Test if this works pthread_setname_np(name); -#elif defined(GB_SYSTEM_FREEBSD) +#elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) pthread_set_name_np(t->posix_handle, name); #else // TODO(bill): Test if this works diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 624aea2aa..40bc5c220 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -201,14 +201,6 @@ struct TokenPos { i32 column; // starting at 1 }; -// temporary -char *token_pos_to_string(TokenPos const &pos) { - gbString s = gb_string_make_reserve(temporary_allocator(), 128); - String file = get_file_path_string(pos.file_id); - s = gb_string_append_fmt(s, "%.*s(%d:%d)", LIT(file), pos.line, pos.column); - return s; -} - i32 token_pos_cmp(TokenPos const &a, TokenPos const &b) { if (a.offset != b.offset) { return (a.offset < b.offset) ? -1 : +1; @@ -264,419 +256,6 @@ bool token_is_newline(Token const &tok) { return tok.kind == Token_Semicolon && tok.string == "\n"; } - -struct ErrorCollector { - TokenPos prev; - std::atomic<i64> count; - std::atomic<i64> warning_count; - std::atomic<bool> in_block; - BlockingMutex mutex; - BlockingMutex error_out_mutex; - BlockingMutex string_mutex; - RecursiveMutex block_mutex; - - Array<u8> error_buffer; - Array<String> errors; -}; - -gb_global ErrorCollector global_error_collector; - -#define MAX_ERROR_COLLECTOR_COUNT (36) - - -bool any_errors(void) { - return global_error_collector.count.load() != 0; -} - -void init_global_error_collector(void) { - mutex_init(&global_error_collector.mutex); - mutex_init(&global_error_collector.block_mutex); - mutex_init(&global_error_collector.error_out_mutex); - mutex_init(&global_error_collector.string_mutex); - array_init(&global_error_collector.errors, heap_allocator()); - array_init(&global_error_collector.error_buffer, heap_allocator()); - array_init(&global_file_path_strings, heap_allocator(), 1, 4096); - array_init(&global_files, heap_allocator(), 1, 4096); -} - - -bool set_file_path_string(i32 index, String const &path) { - bool ok = false; - GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); - - if (index >= global_file_path_strings.count) { - array_resize(&global_file_path_strings, index+1); - } - String prev = global_file_path_strings[index]; - if (prev.len == 0) { - global_file_path_strings[index] = path; - ok = true; - } - - mutex_unlock(&global_error_collector.string_mutex); - return ok; -} - -bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { - bool ok = false; - GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); - - if (index >= global_files.count) { - array_resize(&global_files, index+1); - } - AstFile *prev = global_files[index]; - if (prev == nullptr) { - global_files[index] = file; - ok = true; - } - - mutex_unlock(&global_error_collector.string_mutex); - return ok; -} - -String get_file_path_string(i32 index) { - GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); - - String path = {}; - if (index < global_file_path_strings.count) { - path = global_file_path_strings[index]; - } - - mutex_unlock(&global_error_collector.string_mutex); - return path; -} - -AstFile *thread_safe_get_ast_file_from_id(i32 index) { - GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); - - AstFile *file = nullptr; - if (index < global_files.count) { - file = global_files[index]; - } - - mutex_unlock(&global_error_collector.string_mutex); - return file; -} - - - -void begin_error_block(void) { - mutex_lock(&global_error_collector.block_mutex); - global_error_collector.in_block.store(true); -} - -void end_error_block(void) { - if (global_error_collector.error_buffer.count > 0) { - isize n = global_error_collector.error_buffer.count; - u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, global_error_collector.error_buffer.data, n); - text[n] = 0; - String s = {text, n}; - array_add(&global_error_collector.errors, s); - global_error_collector.error_buffer.count = 0; - } - - global_error_collector.in_block.store(false); - mutex_unlock(&global_error_collector.block_mutex); -} - -#define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) - - -#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) -typedef ERROR_OUT_PROC(ErrorOutProc); - -ERROR_OUT_PROC(default_error_out_va) { - gbFile *f = gb_file_get_standard(gbFileStandard_Error); - - char buf[4096] = {}; - isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); - isize n = len-1; - if (global_error_collector.in_block) { - isize cap = global_error_collector.error_buffer.count + n; - array_reserve(&global_error_collector.error_buffer, cap); - u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count; - gb_memmove(data, buf, n); - global_error_collector.error_buffer.count += n; - } else { - mutex_lock(&global_error_collector.error_out_mutex); - { - u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, buf, n); - text[n] = 0; - array_add(&global_error_collector.errors, make_string(text, n)); - } - mutex_unlock(&global_error_collector.error_out_mutex); - - } - gb_file_write(f, buf, n); -} - - -ErrorOutProc *error_out_va = default_error_out_va; - -// NOTE: defined in build_settings.cpp -bool global_warnings_as_errors(void); -bool global_ignore_warnings(void); -bool show_error_line(void); -gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); - -void error_out(char const *fmt, ...) { - va_list va; - va_start(va, fmt); - error_out_va(fmt, va); - va_end(va); -} - - -bool show_error_on_line(TokenPos const &pos, TokenPos end) { - if (!show_error_line()) { - return false; - } - - i32 offset = 0; - gbString the_line = get_file_line_as_string(pos, &offset); - defer (gb_string_free(the_line)); - - if (the_line != nullptr) { - String line = make_string(cast(u8 const *)the_line, gb_string_length(the_line)); - - // TODO(bill): This assumes ASCII - - enum { - MAX_LINE_LENGTH = 76, - MAX_TAB_WIDTH = 8, - ELLIPSIS_PADDING = 8 - }; - - error_out("\n\t"); - if (line.len+MAX_TAB_WIDTH+ELLIPSIS_PADDING > MAX_LINE_LENGTH) { - i32 const half_width = MAX_LINE_LENGTH/2; - i32 left = cast(i32)(offset); - i32 right = cast(i32)(line.len - offset); - left = gb_min(left, half_width); - right = gb_min(right, half_width); - - line.text += offset-left; - line.len -= offset+right-left; - - line = string_trim_whitespace(line); - - offset = left + ELLIPSIS_PADDING/2; - - error_out("... %.*s ...", LIT(line)); - } else { - error_out("%.*s", LIT(line)); - } - error_out("\n\t"); - - for (i32 i = 0; i < offset; i++) { - error_out(" "); - } - error_out("^"); - if (end.file_id == pos.file_id) { - if (end.line > pos.line) { - for (i32 i = offset; i < line.len; i++) { - error_out("~"); - } - } else if (end.line == pos.line && end.column > pos.column) { - i32 length = gb_min(end.offset - pos.offset, cast(i32)(line.len-offset)); - for (i32 i = 1; i < length-1; i++) { - error_out("~"); - } - if (length > 1) { - error_out("^"); - } - } - } - - error_out("\n\n"); - return true; - } - return false; -} - -void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { - global_error_collector.count.fetch_add(1); - - mutex_lock(&global_error_collector.mutex); - // NOTE(bill): Duplicate error, skip it - if (pos.line == 0) { - error_out("Error: %s\n", gb_bprintf_va(fmt, va)); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out("%s %s\n", - token_pos_to_string(pos), - gb_bprintf_va(fmt, va)); - show_error_on_line(pos, end); - } - mutex_unlock(&global_error_collector.mutex); - if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { - gb_exit(1); - } -} - -void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { - if (global_warnings_as_errors()) { - error_va(pos, end, fmt, va); - return; - } - global_error_collector.warning_count.fetch_add(1); - mutex_lock(&global_error_collector.mutex); - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (pos.line == 0) { - error_out("Warning: %s\n", gb_bprintf_va(fmt, va)); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out("%s Warning: %s\n", - token_pos_to_string(pos), - gb_bprintf_va(fmt, va)); - show_error_on_line(pos, end); - } - } - mutex_unlock(&global_error_collector.mutex); -} - - -void error_line_va(char const *fmt, va_list va) { - error_out_va(fmt, va); -} - -void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { - mutex_lock(&global_error_collector.mutex); - global_error_collector.count++; - // NOTE(bill): Duplicate error, skip it - if (pos.line == 0) { - error_out("Error: %s", gb_bprintf_va(fmt, va)); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out("%s %s", - token_pos_to_string(pos), - gb_bprintf_va(fmt, va)); - } - mutex_unlock(&global_error_collector.mutex); - if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { - gb_exit(1); - } -} - - -void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { - mutex_lock(&global_error_collector.mutex); - global_error_collector.count++; - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out("%s Syntax Error: %s\n", - token_pos_to_string(pos), - gb_bprintf_va(fmt, va)); - show_error_on_line(pos, end); - } else if (pos.line == 0) { - error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va)); - } - - mutex_unlock(&global_error_collector.mutex); - if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) { - gb_exit(1); - } -} - -void syntax_warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { - if (global_warnings_as_errors()) { - syntax_error_va(pos, end, fmt, va); - return; - } - mutex_lock(&global_error_collector.mutex); - global_error_collector.warning_count++; - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out("%s Syntax Warning: %s\n", - token_pos_to_string(pos), - gb_bprintf_va(fmt, va)); - show_error_on_line(pos, end); - } else if (pos.line == 0) { - error_out("Warning: %s\n", gb_bprintf_va(fmt, va)); - } - } - mutex_unlock(&global_error_collector.mutex); -} - - - -void warning(Token const &token, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - warning_va(token.pos, {}, fmt, va); - va_end(va); -} - -void error(Token const &token, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - error_va(token.pos, {}, fmt, va); - va_end(va); -} - -void error(TokenPos pos, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - Token token = {}; - token.pos = pos; - error_va(pos, {}, fmt, va); - va_end(va); -} - -void error_line(char const *fmt, ...) { - va_list va; - va_start(va, fmt); - error_line_va(fmt, va); - va_end(va); -} - - -void syntax_error(Token const &token, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - syntax_error_va(token.pos, {}, fmt, va); - va_end(va); -} - -void syntax_error(TokenPos pos, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - syntax_error_va(pos, {}, fmt, va); - va_end(va); -} - -void syntax_warning(Token const &token, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - syntax_warning_va(token.pos, {}, fmt, va); - va_end(va); -} - - -void compiler_error(char const *fmt, ...) { - va_list va; - - va_start(va, fmt); - gb_printf_err("Internal Compiler Error: %s\n", - gb_bprintf_va(fmt, va)); - va_end(va); - gb_exit(1); -} - - - - - gb_inline bool token_is_literal(TokenKind t) { return gb_is_between(t, Token__LiteralBegin+1, Token__LiteralEnd-1); } @@ -695,6 +274,8 @@ gb_inline bool token_is_shift(TokenKind t) { gb_inline void print_token(Token t) { gb_printf("%.*s\n", LIT(t.string)); } +#include "error.cpp" + enum TokenizerInitError { TokenizerInit_None, diff --git a/src/types.cpp b/src/types.cpp index 2b7ea93dc..b4dc17256 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -165,9 +165,8 @@ struct TypeUnion { i16 tag_size; bool is_polymorphic; - bool is_poly_specialized : 1; - bool no_nil : 1; - bool maybe : 1; + bool is_poly_specialized; + UnionTypeKind kind; }; struct TypeProc { @@ -186,7 +185,6 @@ struct TypeProc { bool c_vararg; bool is_polymorphic; bool is_poly_specialized; - bool has_proc_default_values; bool has_named_results; bool diverging; // no return bool return_by_pointer; @@ -221,6 +219,7 @@ struct TypeProc { ExactValue *max_value; \ i64 count; \ TokenKind op; \ + bool is_sparse; \ }) \ TYPE_KIND(Slice, struct { Type *elem; }) \ TYPE_KIND(DynamicArray, struct { Type *elem; }) \ @@ -362,6 +361,7 @@ enum TypeInfoFlag : u32 { enum : int { MATRIX_ELEMENT_COUNT_MIN = 1, MATRIX_ELEMENT_COUNT_MAX = 16, + MATRIX_ELEMENT_MAX_SIZE = MATRIX_ELEMENT_COUNT_MAX * (2 * 8), // complex128 }; @@ -391,6 +391,7 @@ struct Selection { bool indirect; // Set if there was a pointer deref anywhere down the line u8 swizzle_count; // maximum components = 4 u8 swizzle_indices; // 2 bits per component, representing which swizzle index + bool pseudo_field; }; Selection empty_selection = {0}; @@ -683,6 +684,38 @@ gb_global Type *t_map_header = nullptr; gb_global Type *t_equal_proc = nullptr; gb_global Type *t_hasher_proc = nullptr; +gb_global Type *t_objc_object = nullptr; +gb_global Type *t_objc_selector = nullptr; +gb_global Type *t_objc_class = nullptr; + +gb_global Type *t_objc_id = nullptr; +gb_global Type *t_objc_SEL = nullptr; +gb_global Type *t_objc_Class = nullptr; + +enum OdinAtomicMemoryOrder : i32 { + OdinAtomicMemoryOrder_relaxed = 0, // unordered + OdinAtomicMemoryOrder_consume = 1, // monotonic + OdinAtomicMemoryOrder_acquire = 2, + OdinAtomicMemoryOrder_release = 3, + OdinAtomicMemoryOrder_acq_rel = 4, + OdinAtomicMemoryOrder_seq_cst = 5, + OdinAtomicMemoryOrder_COUNT, +}; + +char const *OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_COUNT] = { + "Relaxed", + "Consume", + "Acquire", + "Release", + "Acq_Rel", + "Seq_Cst", +}; + +gb_global Type *t_atomic_memory_order = nullptr; + + + + gb_global RecursiveMutex g_type_mutex; struct TypePath; @@ -690,7 +723,7 @@ struct TypePath; i64 type_size_of (Type *t); i64 type_align_of (Type *t); i64 type_offset_of (Type *t, i32 index); -gbString type_to_string (Type *type); +gbString type_to_string (Type *type, bool shorthand=false); i64 type_size_of_internal(Type *t, TypePath *path); void init_map_internal_types(Type *type); Type * bit_set_to_int(Type *t); @@ -1253,6 +1286,13 @@ bool is_type_quaternion(Type *t) { } return false; } +bool is_type_complex_or_quaternion(Type *t) { + t = core_type(t); + if (t->kind == Type_Basic) { + return (t->Basic.flags & (BasicFlag_Complex|BasicFlag_Quaternion)) != 0; + } + return false; +} bool is_type_f16(Type *t) { t = core_type(t); if (t->kind == Type_Basic) { @@ -1575,6 +1615,24 @@ Type *core_array_type(Type *t) { } } +i32 type_math_rank(Type *t) { + i32 rank = 0; + for (;;) { + t = base_type(t); + switch (t->kind) { + case Type_Array: + rank += 1; + t = t->Array.elem; + break; + case Type_Matrix: + rank += 2; + t = t->Matrix.elem; + break; + default: + return rank; + } + } +} Type *base_complex_elem_type(Type *t) { @@ -1627,7 +1685,7 @@ bool is_type_map(Type *t) { bool is_type_union_maybe_pointer(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.maybe) { + if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { if (t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; return is_type_pointer(v) || is_type_multi_pointer(v); @@ -1639,7 +1697,7 @@ bool is_type_union_maybe_pointer(Type *t) { bool is_type_union_maybe_pointer_original_alignment(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.maybe) { + if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { if (t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; if (is_type_pointer(v) || is_type_multi_pointer(v)) { @@ -2131,7 +2189,7 @@ bool type_has_nil(Type *t) { case Type_Map: return true; case Type_Union: - return !t->Union.no_nil; + return t->Union.kind != UnionType_no_nil; case Type_Struct: if (is_type_soa_struct(t)) { switch (t->Struct.soa_kind) { @@ -2160,6 +2218,17 @@ bool elem_type_can_be_constant(Type *t) { return true; } +bool is_type_lock_free(Type *t) { + t = core_type(t); + if (t == t_invalid) { + return false; + } + i64 sz = type_size_of(t); + // TODO(bill): Figure this out correctly + return sz <= build_context.max_align; +} + + bool is_type_comparable(Type *t) { t = base_type(t); @@ -2296,7 +2365,7 @@ String lookup_subtype_polymorphic_field(Type *dst, Type *src) { GB_ASSERT(is_type_struct(src) || is_type_union(src)); for_array(i, src->Struct.fields) { Entity *f = src->Struct.fields[i]; - if (f->kind == Entity_Variable && f->flags & EntityFlag_Using) { + if (f->kind == Entity_Variable && f->flags & EntityFlags_IsSubtype) { if (are_types_identical(dst, f->type)) { return f->token.string; } @@ -2305,7 +2374,7 @@ String lookup_subtype_polymorphic_field(Type *dst, Type *src) { return f->token.string; } } - if (is_type_struct(f->type)) { + if ((f->flags & EntityFlag_Using) != 0 && is_type_struct(f->type)) { String name = lookup_subtype_polymorphic_field(dst, f->type); if (name.len > 0) { return name; @@ -2331,7 +2400,17 @@ Type *strip_type_aliasing(Type *x) { return x; } +bool are_types_identical_internal(Type *x, Type *y, bool check_tuple_names); + bool are_types_identical(Type *x, Type *y) { + return are_types_identical_internal(x, y, false); +} +bool are_types_identical_unique_tuples(Type *x, Type *y) { + return are_types_identical_internal(x, y, true); +} + + +bool are_types_identical_internal(Type *x, Type *y, bool check_tuple_names) { if (x == y) { return true; } @@ -2407,7 +2486,7 @@ bool are_types_identical(Type *x, Type *y) { if (y->kind == Type_Union) { if (x->Union.variants.count == y->Union.variants.count && x->Union.custom_align == y->Union.custom_align && - x->Union.no_nil == y->Union.no_nil) { + x->Union.kind == y->Union.kind) { // NOTE(bill): zeroth variant is nullptr for_array(i, x->Union.variants) { if (!are_types_identical(x->Union.variants[i], y->Union.variants[i])) { @@ -2441,9 +2520,9 @@ bool are_types_identical(Type *x, Type *y) { if (xf->token.string != yf->token.string) { return false; } - bool xf_is_using = (xf->flags&EntityFlag_Using) != 0; - bool yf_is_using = (yf->flags&EntityFlag_Using) != 0; - if (xf_is_using ^ yf_is_using) { + u64 xf_flags = (xf->flags&EntityFlags_IsSubtype); + u64 yf_flags = (yf->flags&EntityFlags_IsSubtype); + if (xf_flags != yf_flags) { return false; } } @@ -2480,6 +2559,11 @@ bool are_types_identical(Type *x, Type *y) { if (xe->kind != ye->kind || !are_types_identical(xe->type, ye->type)) { return false; } + if (check_tuple_names) { + if (xe->token.string != ye->token.string) { + return false; + } + } if (xe->kind == Entity_Constant && !compare_exact_values(Token_CmpEq, xe->Constant.value, ye->Constant.value)) { // NOTE(bill): This is needed for polymorphic procedures return false; @@ -2546,7 +2630,7 @@ i64 union_variant_index(Type *u, Type *v) { for_array(i, u->Union.variants) { Type *vt = u->Union.variants[i]; if (are_types_identical(v, vt)) { - if (u->Union.no_nil) { + if (u->Union.kind == UnionType_no_nil) { return cast(i64)(i+0); } else { return cast(i64)(i+1); @@ -2570,6 +2654,17 @@ i64 union_tag_size(Type *u) { // TODO(bill): Is this an okay approach? i64 max_align = 1; + + if (u->Union.variants.count < 1ull<<8) { + max_align = 1; + } else if (u->Union.variants.count < 1ull<<16) { + max_align = 2; + } else if (u->Union.variants.count < 1ull<<32) { + max_align = 4; + } else { + GB_PANIC("how many variants do you have?!"); + } + for_array(i, u->Union.variants) { Type *variant_type = u->Union.variants[i]; i64 align = type_align_of(variant_type); @@ -2730,6 +2825,7 @@ Selection lookup_field_from_index(Type *type, i64 index) { } Entity *scope_lookup_current(Scope *s, String const &name); +bool has_type_got_objc_class_attribute(Type *t); Selection lookup_field_with_selection(Type *type_, String field_name, bool is_type, Selection sel, bool allow_blank_ident) { GB_ASSERT(type_ != nullptr); @@ -2742,9 +2838,40 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty bool is_ptr = type != type_; sel.indirect = sel.indirect || is_ptr; + Type *original_type = type; + type = base_type(type); if (is_type) { + if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) { + Entity *e = original_type->Named.type_name; + GB_ASSERT(e->kind == Entity_TypeName); + if (e->TypeName.objc_metadata) { + auto *md = e->TypeName.objc_metadata; + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); + for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { + GB_ASSERT(entry.entity->kind == Entity_Procedure); + if (entry.name == field_name) { + sel.entity = entry.entity; + sel.pseudo_field = true; + return sel; + } + } + } + if (type->kind == Type_Struct) { + for_array(i, type->Struct.fields) { + Entity *f = type->Struct.fields[i]; + if (f->flags&EntityFlag_Using) { + sel = lookup_field_with_selection(f->type, field_name, is_type, sel, allow_blank_ident); + if (sel.entity) { + return sel; + } + } + } + } + } + if (is_type_enum(type)) { // NOTE(bill): These may not have been added yet, so check in case for_array(i, type->Enum.fields) { @@ -2791,6 +2918,24 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty } else if (type->kind == Type_Union) { } else if (type->kind == Type_Struct) { + if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) { + Entity *e = original_type->Named.type_name; + GB_ASSERT(e->kind == Entity_TypeName); + if (e->TypeName.objc_metadata) { + auto *md = e->TypeName.objc_metadata; + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); + for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { + GB_ASSERT(entry.entity->kind == Entity_Procedure); + if (entry.name == field_name) { + sel.entity = entry.entity; + sel.pseudo_field = true; + return sel; + } + } + } + } + for_array(i, type->Struct.fields) { Entity *f = type->Struct.fields[i]; if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) { @@ -3696,6 +3841,61 @@ i64 type_offset_of_from_selection(Type *type, Selection sel) { return offset; } +isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isize level = 0, bool src_is_ptr = false) { + Type *prev_src = src; + src = type_deref(src); + if (!src_is_ptr) { + src_is_ptr = src != prev_src; + } + src = base_type(src); + + if (!is_type_struct(src)) { + return 0; + } + + for_array(i, src->Struct.fields) { + Entity *f = src->Struct.fields[i]; + if (f->kind != Entity_Variable || (f->flags&EntityFlags_IsSubtype) == 0) { + continue; + } + + if (are_types_identical(f->type, dst)) { + return level+1; + } + if (src_is_ptr && is_type_pointer(dst)) { + if (are_types_identical(f->type, type_deref(dst))) { + return level+1; + } + } + isize nested_level = check_is_assignable_to_using_subtype(f->type, dst, level+1, src_is_ptr); + if (nested_level > 0) { + return nested_level; + } + } + + return 0; +} + +bool is_type_subtype_of(Type *src, Type *dst) { + if (are_types_identical(src, dst)) { + return true; + } + + return 0 < check_is_assignable_to_using_subtype(src, dst, 0, is_type_pointer(src)); +} + + +bool has_type_got_objc_class_attribute(Type *t) { + return t->kind == Type_Named && t->Named.type_name != nullptr && t->Named.type_name->TypeName.objc_class_name != ""; +} + + + +bool is_type_objc_object(Type *t) { + bool internal_check_is_assignable_to(Type *src, Type *dst); + + return internal_check_is_assignable_to(t, t_objc_object); +} Type *get_struct_field_type(Type *t, isize index) { t = base_type(type_deref(t)); @@ -3767,7 +3967,7 @@ Type *alloc_type_proc_from_types(Type **param_types, unsigned param_count, Type -gbString write_type_to_string(gbString str, Type *type) { +gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) { if (type == nullptr) { return gb_string_appendc(str, "<no type>"); } @@ -3808,6 +4008,9 @@ gbString write_type_to_string(gbString str, Type *type) { break; case Type_EnumeratedArray: + if (type->EnumeratedArray.is_sparse) { + str = gb_string_appendc(str, "#sparse"); + } str = gb_string_append_rune(str, '['); str = write_type_to_string(str, type->EnumeratedArray.index); str = gb_string_append_rune(str, ']'); @@ -3850,8 +4053,11 @@ gbString write_type_to_string(gbString str, Type *type) { case Type_Union: str = gb_string_appendc(str, "union"); - if (type->Union.no_nil != 0) str = gb_string_appendc(str, " #no_nil"); - if (type->Union.maybe != 0) str = gb_string_appendc(str, " #maybe"); + switch (type->Union.kind) { + case UnionType_maybe: str = gb_string_appendc(str, " #maybe"); break; + case UnionType_no_nil: str = gb_string_appendc(str, " #no_nil"); break; + case UnionType_shared_nil: str = gb_string_appendc(str, " #shared_nil"); break; + } if (type->Union.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Union.custom_align); str = gb_string_appendc(str, " {"); for_array(i, type->Union.variants) { @@ -3879,15 +4085,21 @@ gbString write_type_to_string(gbString str, Type *type) { if (type->Struct.is_raw_union) str = gb_string_appendc(str, " #raw_union"); if (type->Struct.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Struct.custom_align); str = gb_string_appendc(str, " {"); - for_array(i, type->Struct.fields) { - Entity *f = type->Struct.fields[i]; - GB_ASSERT(f->kind == Entity_Variable); - if (i > 0) { - str = gb_string_appendc(str, ", "); + + + if (shorthand && type->Struct.fields.count > 16) { + str = gb_string_append_fmt(str, "%lld fields...", cast(long long)type->Struct.fields.count); + } else { + for_array(i, type->Struct.fields) { + Entity *f = type->Struct.fields[i]; + GB_ASSERT(f->kind == Entity_Variable); + if (i > 0) { + str = gb_string_appendc(str, ", "); + } + str = gb_string_append_length(str, f->token.string.text, f->token.string.len); + str = gb_string_appendc(str, ": "); + str = write_type_to_string(str, f->type); } - str = gb_string_append_length(str, f->token.string.text, f->token.string.len); - str = gb_string_appendc(str, ": "); - str = write_type_to_string(str, f->type); } str = gb_string_append_rune(str, '}'); } break; @@ -3926,7 +4138,7 @@ gbString write_type_to_string(gbString str, Type *type) { str = gb_string_appendc(str, " = "); str = write_exact_value_to_string(str, var->Constant.value); } else { - str = gb_string_appendc(str, "="); + str = gb_string_appendc(str, " := "); str = write_exact_value_to_string(str, var->Constant.value); } continue; @@ -3954,14 +4166,10 @@ gbString write_type_to_string(gbString str, Type *type) { str = gb_string_appendc(str, "typeid/"); str = write_type_to_string(str, var->type); } else { - if (var->kind == Entity_TypeName) { - str = gb_string_appendc(str, "$"); - str = gb_string_append_length(str, name.text, name.len); - str = gb_string_appendc(str, "="); - str = write_type_to_string(str, var->type); - } else { - str = gb_string_appendc(str, "typeid"); - } + str = gb_string_appendc(str, "$"); + str = gb_string_append_length(str, name.text, name.len); + str = gb_string_appendc(str, "="); + str = write_type_to_string(str, var->type); } } } @@ -4066,13 +4274,16 @@ gbString write_type_to_string(gbString str, Type *type) { } -gbString type_to_string(Type *type, gbAllocator allocator) { - return write_type_to_string(gb_string_make(allocator, ""), type); +gbString type_to_string(Type *type, gbAllocator allocator, bool shorthand=false) { + return write_type_to_string(gb_string_make(allocator, ""), type, shorthand); } -gbString type_to_string(Type *type) { - return write_type_to_string(gb_string_make(heap_allocator(), ""), type); +gbString type_to_string(Type *type, bool shorthand) { + return write_type_to_string(gb_string_make(heap_allocator(), ""), type, shorthand); } +gbString type_to_string_shorthand(Type *type) { + return type_to_string(type, true); +} |