enum TargetOsKind { TargetOs_Invalid, TargetOs_windows, TargetOs_osx, TargetOs_linux, TargetOs_essence, TargetOs_COUNT, }; enum TargetArchKind { TargetArch_Invalid, TargetArch_amd64, TargetArch_386, TargetArch_COUNT, }; enum TargetEndianKind { TargetEndian_Invalid, TargetEndian_Little, TargetEndian_Big, TargetEndian_COUNT, }; String target_os_names[TargetOs_COUNT] = { str_lit(""), str_lit("windows"), str_lit("osx"), str_lit("linux"), str_lit("essence"), }; String target_arch_names[TargetArch_COUNT] = { str_lit(""), str_lit("amd64"), str_lit("386"), }; String target_endian_names[TargetEndian_COUNT] = { str_lit(""), str_lit("little"), str_lit("big"), }; TargetEndianKind target_endians[TargetArch_COUNT] = { TargetEndian_Invalid, TargetEndian_Little, TargetEndian_Little, }; String const ODIN_VERSION = str_lit("0.9.2"); String cross_compile_target = str_lit(""); String cross_compile_lib_dir = str_lit(""); struct TargetMetrics { TargetOsKind os; TargetArchKind arch; isize word_size; isize max_align; }; // 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 bool ODIN_DEBUG; // Odin in debug mode TargetEndianKind endian_kind; // In bytes i64 word_size; // Size of a pointer, must be >= 4 i64 max_align; // max alignment, must be >= 1 (and typically >= word_size) String command; TargetMetrics metrics; String out_filepath; String resource_filepath; String pdb_filepath; bool has_resource; String opt_flags; String llc_flags; String link_flags; bool is_dll; bool generate_docs; i32 optimization_level; bool show_timings; bool keep_temp_files; bool ignore_unknown_attributes; bool no_bounds_check; bool no_output_files; bool print_query_data; bool no_crt; bool use_lld; bool vet; gbAffinity affinity; isize thread_count; Map defined_values; // Key: }; gb_global BuildContext build_context = {0}; gb_global TargetMetrics target_windows_386 = { TargetOs_windows, TargetArch_386, 4, 8, }; gb_global TargetMetrics target_windows_amd64 = { TargetOs_windows, TargetArch_amd64, 8, 16, }; gb_global TargetMetrics target_linux_386 = { TargetOs_linux, TargetArch_386, 4, 8, }; gb_global TargetMetrics target_linux_amd64 = { TargetOs_linux, TargetArch_amd64, 8, 16, }; gb_global TargetMetrics target_osx_amd64 = { TargetOs_osx, TargetArch_amd64, 8, 16, }; TargetOsKind get_target_os_from_string(String str) { for (isize i = 0; i < TargetOs_COUNT; i++) { if (str_eq_ignore_case(target_os_names[i], str)) { return cast(TargetOsKind)i; } } return TargetOs_Invalid; } TargetArchKind get_target_arch_from_string(String str) { for (isize i = 0; i < TargetArch_COUNT; i++) { if (str_eq_ignore_case(target_arch_names[i], str)) { return cast(TargetArchKind)i; } } return TargetArch_Invalid; } bool is_excluded_target_filename(String name) { String const ext = str_lit(".odin"); String original_name = name; GB_ASSERT(string_ends_with(name, ext)); name = substring(name, 0, name.len-ext.len); String str1 = {}; String str2 = {}; isize n = 0; str1 = name; n = str1.len; for (isize i = str1.len-1; i >= 0 && str1[i] != '_'; i--) { n -= 1; } str1 = substring(str1, n, str1.len); str2 = substring(name, 0, gb_max(n-1, 0)); n = str2.len; for (isize i = str2.len-1; i >= 0 && str2[i] != '_'; i--) { n -= 1; } str2 = substring(str2, n, str2.len); if (str1 == name) { return false; } TargetOsKind os1 = get_target_os_from_string(str1); TargetArchKind arch1 = get_target_arch_from_string(str1); TargetOsKind os2 = get_target_os_from_string(str2); TargetArchKind arch2 = get_target_arch_from_string(str2); if (os1 != TargetOs_Invalid && arch2 != TargetArch_Invalid) { return os1 != build_context.metrics.os || arch2 != build_context.metrics.arch; } else if (arch1 != TargetArch_Invalid && os2 != TargetOs_Invalid) { return arch1 != build_context.metrics.arch || os2 != build_context.metrics.os; } else if (os1 != TargetOs_Invalid) { return os1 != build_context.metrics.os; } else if (arch1 != TargetArch_Invalid) { return arch1 != build_context.metrics.arch; } return false; } struct LibraryCollections { String name; String path; }; gb_global Array library_collections = {0}; void add_library_collection(String name, String path) { // TODO(bill): Check the path is valid and a directory LibraryCollections lc = {name, string_trim_whitespace(path)}; array_add(&library_collections, lc); } bool find_library_collection_path(String name, String *path) { for_array(i, library_collections) { if (library_collections[i].name == name) { if (path) *path = library_collections[i].path; return true; } } return false; } // TODO(bill): OS dependent versions for the BuildContext // join_path // is_dir // is_file // is_abs_path // has_subdir String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1}; String const NIX_SEPARATOR_STRING = {cast(u8 *)"/", 1}; #if defined(GB_SYSTEM_WINDOWS) String odin_root_dir(void) { String path = global_module_path; isize len, i; gbTempArenaMemory tmp; wchar_t *text; if (global_module_path_set) { return global_module_path; } auto path_buf = array_make(heap_allocator(), 300); len = 0; for (;;) { len = GetModuleFileNameW(nullptr, &path_buf[0], cast(int)path_buf.count); if (len == 0) { return make_string(nullptr, 0); } if (len < path_buf.count) { break; } array_resize(&path_buf, 2*path_buf.count + 300); } len += 1; // NOTE(bill): It needs an extra 1 for some reason gb_mutex_lock(&string_buffer_mutex); defer (gb_mutex_unlock(&string_buffer_mutex)); tmp = gb_temp_arena_memory_begin(&string_buffer_arena); defer (gb_temp_arena_memory_end(tmp)); text = gb_alloc_array(string_buffer_allocator, wchar_t, len+1); GetModuleFileNameW(nullptr, text, cast(int)len); path = string16_to_string(heap_allocator(), make_string16(text, len)); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; if (c == '/' || c == '\\') { break; } path.len--; } global_module_path = path; global_module_path_set = true; array_free(&path_buf); return path; } #elif defined(GB_SYSTEM_OSX) #include String odin_root_dir(void) { String path = global_module_path; isize len, i; gbTempArenaMemory tmp; u8 *text; if (global_module_path_set) { return global_module_path; } auto path_buf = array_make(heap_allocator(), 300); len = 0; for (;;) { u32 sz = path_buf.count; int res = _NSGetExecutablePath(&path_buf[0], &sz); if(res == 0) { len = sz; break; } else { array_resize(&path_buf, sz + 1); } } gb_mutex_lock(&string_buffer_mutex); defer (gb_mutex_unlock(&string_buffer_mutex)); tmp = gb_temp_arena_memory_begin(&string_buffer_arena); defer (gb_temp_arena_memory_end(tmp)); text = gb_alloc_array(string_buffer_allocator, u8, len + 1); gb_memmove(text, &path_buf[0], len); path = make_string(text, len); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; if (c == '/' || c == '\\') { break; } path.len--; } global_module_path = path; global_module_path_set = true; // array_free(&path_buf); return path; } #else // NOTE: Linux / Unix is unfinished and not tested very well. #include String odin_root_dir(void) { String path = global_module_path; isize len, i; gbTempArenaMemory tmp; u8 *text; if (global_module_path_set) { return global_module_path; } auto path_buf = array_make(heap_allocator(), 300); defer (array_free(&path_buf)); len = 0; for (;;) { // This is not a 100% reliable system, but for the purposes // of this compiler, it should be _good enough_. // That said, there's no solid 100% method on Linux to get the program's // path without checking this link. Sorry. len = readlink("/proc/self/exe", &path_buf[0], path_buf.count); if(len == 0) { return make_string(nullptr, 0); } if (len < path_buf.count) { break; } array_resize(&path_buf, 2*path_buf.count + 300); } gb_mutex_lock(&string_buffer_mutex); defer (gb_mutex_unlock(&string_buffer_mutex)); tmp = gb_temp_arena_memory_begin(&string_buffer_arena); defer (gb_temp_arena_memory_end(tmp)); text = gb_alloc_array(string_buffer_allocator, u8, len + 1); gb_memmove(text, &path_buf[0], len); path = make_string(text, len); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; if (c == '/' || c == '\\') { break; } path.len--; } global_module_path = path; global_module_path_set = true; return path; } #endif #if defined(GB_SYSTEM_WINDOWS) String path_to_fullpath(gbAllocator a, String s) { String result = {}; gb_mutex_lock(&string_buffer_mutex); defer (gb_mutex_unlock(&string_buffer_mutex)); gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&string_buffer_arena); defer (gb_temp_arena_memory_end(tmp)); String16 string16 = string_to_string16(string_buffer_allocator, s); DWORD len = GetFullPathNameW(&string16[0], 0, nullptr, nullptr); if (len != 0) { wchar_t *text = gb_alloc_array(string_buffer_allocator, wchar_t, len+1); GetFullPathNameW(&string16[0], len, text, nullptr); text[len] = 0; result = string16_to_string(a, make_string16(text, len)); result = string_trim_whitespace(result); // Replace Windows style separators for (isize i = 0; i < result.len; i++) { if (result[i] == '\\') { result[i] = '/'; } } } return result; } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) String path_to_fullpath(gbAllocator a, String s) { char *p; gb_mutex_lock(&string_buffer_mutex); p = realpath(cast(char *)s.text, 0); gb_mutex_unlock(&string_buffer_mutex); if(p == nullptr) return String{}; return make_string_c(p); } #else #error Implement system #endif String get_fullpath_relative(gbAllocator a, String base_dir, String path) { u8 *str = gb_alloc_array(heap_allocator(), u8, base_dir.len+1+path.len+1); defer (gb_free(heap_allocator(), str)); isize i = 0; gb_memmove(str+i, base_dir.text, base_dir.len); i += base_dir.len; gb_memmove(str+i, "/", 1); i += 1; gb_memmove(str+i, path.text, path.len); i += path.len; str[i] = 0; String res = make_string(str, i); res = string_trim_whitespace(res); return path_to_fullpath(a, res); } String get_fullpath_core(gbAllocator a, String path) { String module_dir = odin_root_dir(); String core = str_lit("core/"); isize str_len = module_dir.len + core.len + path.len; u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1); defer (gb_free(heap_allocator(), str)); isize i = 0; gb_memmove(str+i, module_dir.text, module_dir.len); i += module_dir.len; gb_memmove(str+i, core.text, core.len); i += core.len; gb_memmove(str+i, path.text, path.len); i += path.len; str[i] = 0; String res = make_string(str, i); res = string_trim_whitespace(res); return path_to_fullpath(a, res); } void init_build_context(void) { BuildContext *bc = &build_context; gb_affinity_init(&bc->affinity); if (bc->thread_count == 0) { bc->thread_count = gb_max(bc->affinity.thread_count, 1); } bc->ODIN_VENDOR = str_lit("odin"); bc->ODIN_VERSION = ODIN_VERSION; bc->ODIN_ROOT = odin_root_dir(); TargetMetrics metrics = {}; #if defined(GB_ARCH_64_BIT) #if defined(GB_SYSTEM_WINDOWS) metrics = target_windows_amd64; #elif defined(GB_SYSTEM_OSX) metrics = target_osx_amd64; #else metrics = target_linux_amd64; #endif #else #if defined(GB_SYSTEM_WINDOWS) metrics = target_windows_386; #elif defined(GB_SYSTEM_OSX) #error "Unsupported architecture" #else metrics = target_linux_386; #endif #endif if (cross_compile_target.len) { bc->ODIN_OS = cross_compile_target; } GB_ASSERT(metrics.os != TargetOs_Invalid); GB_ASSERT(metrics.arch != TargetArch_Invalid); GB_ASSERT(metrics.word_size > 1); GB_ASSERT(metrics.max_align > 1); 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; bc->link_flags = str_lit(" "); bc->opt_flags = str_lit(" "); gbString llc_flags = gb_string_make_reserve(heap_allocator(), 64); if (bc->ODIN_DEBUG) { // llc_flags = gb_string_appendc(llc_flags, "-debug-compile "); } // 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 // here, so I just #defined the linker flags to keep things concise. if (bc->metrics.arch == TargetArch_amd64) { llc_flags = gb_string_appendc(llc_flags, "-march=x86-64 "); switch (bc->metrics.os) { case TargetOs_windows: bc->link_flags = str_lit("/machine:x64 "); break; case TargetOs_osx: break; case TargetOs_linux: bc->link_flags = str_lit("-arch x86-64 "); break; } } else if (bc->metrics.arch == TargetArch_386) { llc_flags = gb_string_appendc(llc_flags, "-march=x86 "); switch (bc->metrics.os) { case TargetOs_windows: bc->link_flags = str_lit("/machine:x86 "); break; case TargetOs_osx: gb_printf_err("Unsupported architecture\n"); gb_exit(1); break; case TargetOs_linux: bc->link_flags = str_lit("-arch x86 "); break; } } else { gb_printf_err("Unsupported architecture\n");; gb_exit(1); } bc->llc_flags = make_string_c(llc_flags); bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3); gbString opt_flags = gb_string_make_reserve(heap_allocator(), 64); if (bc->optimization_level != 0) { opt_flags = gb_string_append_fmt(opt_flags, "-O%d ", bc->optimization_level); // NOTE(lachsinc): The following options were previously passed during call // to opt in main.cpp:exec_llvm_opt(). // -die: Dead instruction elimination // -memcpyopt: MemCpy optimization } if (bc->ODIN_DEBUG == false) { opt_flags = gb_string_appendc(opt_flags, "-memcpyopt -die "); } // NOTE(lachsinc): This optimization option was previously required to get // around an issue in fmt.odin. Thank bp for tracking it down! Leaving for now until the issue // is resolved and confirmed by Bill. Maybe it should be readded in non-debug builds. // if (bc->ODIN_DEBUG == false) { // opt_flags = gb_string_appendc(opt_flags, "-mem2reg "); // } bc->opt_flags = make_string_c(opt_flags); #undef LINK_FLAG_X64 #undef LINK_FLAG_386 }