mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-30 01:44:36 +00:00
1753 lines
51 KiB
C++
1753 lines
51 KiB
C++
// #define NO_ARRAY_BOUNDS_CHECK
|
|
|
|
#include "common.cpp"
|
|
#include "timings.cpp"
|
|
#include "tokenizer.cpp"
|
|
#include "big_int.cpp"
|
|
#include "exact_value.cpp"
|
|
#include "build_settings.cpp"
|
|
|
|
#include "parser.hpp"
|
|
#include "checker.hpp"
|
|
|
|
#include "parser.cpp"
|
|
#include "docs.cpp"
|
|
#include "checker.cpp"
|
|
#include "ir.cpp"
|
|
#include "ir_opt.cpp"
|
|
#include "ir_print.cpp"
|
|
#include "query_data.cpp"
|
|
|
|
// NOTE(bill): 'name' is used in debugging and profiling modes
|
|
i32 system_exec_command_line_app(char *name, char *fmt, ...) {
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)};
|
|
PROCESS_INFORMATION pi = {0};
|
|
char cmd_line[4*1024] = {0};
|
|
isize cmd_len;
|
|
va_list va;
|
|
gbTempArenaMemory tmp;
|
|
String16 cmd;
|
|
i32 exit_code = 0;
|
|
|
|
start_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
|
start_info.wShowWindow = SW_SHOW;
|
|
start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
va_start(va, fmt);
|
|
cmd_len = gb_snprintf_va(cmd_line, gb_size_of(cmd_line), fmt, va);
|
|
va_end(va);
|
|
|
|
// gb_printf_err("%.*s\n", cast(int)cmd_len, cmd_line);
|
|
|
|
tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
|
|
defer (gb_temp_arena_memory_end(tmp));
|
|
|
|
cmd = string_to_string16(string_buffer_allocator, make_string(cast(u8 *)cmd_line, cmd_len-1));
|
|
if (CreateProcessW(nullptr, cmd.text,
|
|
nullptr, nullptr, true, 0, nullptr, nullptr,
|
|
&start_info, &pi)) {
|
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
GetExitCodeProcess(pi.hProcess, cast(DWORD *)&exit_code);
|
|
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
} else {
|
|
// NOTE(bill): failed to create process
|
|
gb_printf_err("Failed to execute command:\n\t%s\n", cmd_line);
|
|
exit_code = -1;
|
|
}
|
|
|
|
return exit_code;
|
|
|
|
#elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX)
|
|
|
|
char cmd_line[4096] = {0};
|
|
isize cmd_len;
|
|
va_list va;
|
|
String cmd;
|
|
i32 exit_code = 0;
|
|
|
|
va_start(va, fmt);
|
|
cmd_len = gb_snprintf_va(cmd_line, gb_size_of(cmd_line), fmt, va);
|
|
va_end(va);
|
|
cmd = make_string(cast(u8 *)&cmd_line, cmd_len-1);
|
|
|
|
//printf("do: %s\n", cmd_line);
|
|
exit_code = system(&cmd_line[0]);
|
|
|
|
// pid_t pid = fork();
|
|
// int status = 0;
|
|
|
|
// if(pid == 0) {
|
|
// // in child, pid == 0.
|
|
// int ret = execvp(cmd.text, (char* const*) cmd.text);
|
|
|
|
// if(ret == -1) {
|
|
// gb_printf_err("Failed to execute command:\n\t%s\n", cmd_line);
|
|
|
|
// // we're in the child, so returning won't do us any good -- just quit.
|
|
// exit(-1);
|
|
// }
|
|
|
|
// // unreachable
|
|
// abort();
|
|
// } else {
|
|
// // wait for child to finish, then we can continue cleanup
|
|
|
|
// int s = 0;
|
|
// waitpid(pid, &s, 0);
|
|
|
|
// status = WEXITSTATUS(s);
|
|
// }
|
|
|
|
// exit_code = status;
|
|
|
|
return exit_code;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
Array<String> setup_args(int argc, char **argv) {
|
|
gbAllocator a = heap_allocator();
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
int wargc = 0;
|
|
wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc);
|
|
auto args = array_make<String>(a, 0, wargc);
|
|
for (isize i = 0; i < wargc; i++) {
|
|
wchar_t *warg = wargv[i];
|
|
isize wlen = string16_len(warg);
|
|
String16 wstr = make_string16(warg, wlen);
|
|
String arg = string16_to_string(a, wstr);
|
|
if (arg.len > 0) {
|
|
array_add(&args, arg);
|
|
}
|
|
}
|
|
return args;
|
|
#else
|
|
auto args = array_make<String>(a, 0, argc);
|
|
for (isize i = 0; i < argc; i++) {
|
|
String arg = make_string_c(argv[i]);
|
|
if (arg.len > 0) {
|
|
array_add(&args, arg);
|
|
}
|
|
}
|
|
return args;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void print_usage_line(i32 indent, char *fmt, ...) {
|
|
while (indent --> 0) {
|
|
gb_printf_err("\t");
|
|
}
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
gb_printf_err_va(fmt, va);
|
|
va_end(va);
|
|
gb_printf_err("\n");
|
|
}
|
|
|
|
void usage(String argv0) {
|
|
print_usage_line(0, "%.*s is a tool for managing Odin source code", LIT(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 as executable");
|
|
print_usage_line(1, "run compile and run .odin file");
|
|
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, "docs generate documentation for a .odin file");
|
|
print_usage_line(1, "version print version");
|
|
}
|
|
|
|
|
|
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 = gb_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_OutFile,
|
|
BuildFlag_OptimizationLevel,
|
|
BuildFlag_ShowTimings,
|
|
BuildFlag_ThreadCount,
|
|
BuildFlag_KeepTempFiles,
|
|
BuildFlag_Collection,
|
|
BuildFlag_Define,
|
|
BuildFlag_BuildMode,
|
|
BuildFlag_Debug,
|
|
BuildFlag_CrossCompile,
|
|
BuildFlag_CrossLibDir,
|
|
BuildFlag_NoBoundsCheck,
|
|
BuildFlag_NoCRT,
|
|
BuildFlag_UseLLD,
|
|
BuildFlag_Vet,
|
|
BuildFlag_IgnoreUnknownAttributes,
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
BuildFlag_ResourceFile,
|
|
BuildFlag_WindowsPdbName,
|
|
#endif
|
|
|
|
BuildFlag_COUNT,
|
|
};
|
|
|
|
enum BuildFlagParamKind {
|
|
BuildFlagParam_None,
|
|
|
|
BuildFlagParam_Boolean,
|
|
BuildFlagParam_Integer,
|
|
BuildFlagParam_Float,
|
|
BuildFlagParam_String,
|
|
|
|
BuildFlagParam_COUNT,
|
|
};
|
|
|
|
struct BuildFlag {
|
|
BuildFlagKind kind;
|
|
String name;
|
|
BuildFlagParamKind param_kind;
|
|
};
|
|
|
|
void add_flag(Array<BuildFlag> *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind) {
|
|
BuildFlag flag = {kind, name, param_kind};
|
|
array_add(build_flags, flag);
|
|
}
|
|
|
|
ExactValue build_param_to_exact_value(String name, String param) {
|
|
ExactValue value = {};
|
|
if (str_eq_ignore_case(param, str_lit("t")) ||
|
|
str_eq_ignore_case(param, str_lit("true"))) {
|
|
value = exact_value_bool(true);
|
|
} else if (str_eq_ignore_case(param, str_lit("f")) ||
|
|
str_eq_ignore_case(param, str_lit("false"))) {
|
|
value = exact_value_bool(false);
|
|
} else if (param.len > 0) {
|
|
if (param[0] == '"') {
|
|
value = exact_value_string(param);
|
|
if (value.kind == ExactValue_String) {
|
|
String s = value.value_string;
|
|
if (s.len > 1 && s[0] == '"' && s[s.len-1] == '"') {
|
|
value.value_string = substring(s, 1, s.len-1);
|
|
}
|
|
}
|
|
} else if (param[0] == '-' || param[0] == '+' || gb_is_between(param[0], '0', '9')) {
|
|
if (string_contains_char(param, '.')) {
|
|
value = exact_value_float_from_string(param);
|
|
} else {
|
|
value = exact_value_integer_from_string(param);
|
|
}
|
|
if (value.kind == ExactValue_Invalid) {
|
|
gb_printf_err("Invalid flag parameter for '%.*s' = '%.*s'\n", LIT(name), LIT(param));
|
|
}
|
|
}
|
|
} else {
|
|
gb_printf_err("Invalid flag parameter for '%.*s' = '%.*s'\n", LIT(name), LIT(param));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
bool parse_build_flags(Array<String> args) {
|
|
auto build_flags = array_make<BuildFlag>(heap_allocator(), 0, BuildFlag_COUNT);
|
|
add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_OptimizationLevel, str_lit("opt"), BuildFlagParam_Integer);
|
|
add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_ThreadCount, str_lit("thread-count"), BuildFlagParam_Integer);
|
|
add_flag(&build_flags, BuildFlag_KeepTempFiles, str_lit("keep-temp-files"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_CrossCompile, str_lit("cross-compile"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_CrossLibDir, str_lit("cross-lib-dir"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None);
|
|
add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None);
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String);
|
|
add_flag(&build_flags, BuildFlag_WindowsPdbName, str_lit("pdb-name"), BuildFlagParam_String);
|
|
#endif
|
|
|
|
GB_ASSERT(args.count >= 3);
|
|
Array<String> flag_args = array_slice(args, 3, args.count);
|
|
|
|
bool set_flags[BuildFlag_COUNT] = {};
|
|
|
|
bool bad_flags = false;
|
|
for_array(i, flag_args) {
|
|
String flag = flag_args[i];
|
|
if (flag[0] != '-') {
|
|
gb_printf_err("Invalid flag: %.*s\n", LIT(flag));
|
|
continue;
|
|
}
|
|
String name = substring(flag, 1, flag.len);
|
|
isize end = 0;
|
|
for (; end < name.len; end++) {
|
|
if (name[end] == ':') break;
|
|
if (name[end] == '=') break; // IMPORTANT TODO(bill): DEPRECATE THIS!!!!
|
|
}
|
|
name = substring(name, 0, end);
|
|
String param = {};
|
|
if (end < flag.len-1) param = substring(flag, 2+end, flag.len);
|
|
|
|
bool found = false;
|
|
for_array(build_flag_index, build_flags) {
|
|
BuildFlag bf = build_flags[build_flag_index];
|
|
if (bf.name == name) {
|
|
found = true;
|
|
if (set_flags[bf.kind]) {
|
|
gb_printf_err("Previous flag set: '%.*s'\n", LIT(name));
|
|
bad_flags = true;
|
|
} else {
|
|
ExactValue value = {};
|
|
bool ok = false;
|
|
if (bf.param_kind == BuildFlagParam_None) {
|
|
if (param.len == 0) {
|
|
ok = true;
|
|
} else {
|
|
gb_printf_err("Flag '%.*s' was not expecting a parameter '%.*s'\n", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
}
|
|
} else if (param.len == 0) {
|
|
gb_printf_err("Flag missing for '%.*s'\n", LIT(name));
|
|
bad_flags = true;
|
|
} else {
|
|
ok = true;
|
|
switch (bf.param_kind) {
|
|
default:
|
|
ok = false;
|
|
break;
|
|
case BuildFlagParam_Boolean: {
|
|
if (str_eq_ignore_case(param, str_lit("t")) ||
|
|
str_eq_ignore_case(param, str_lit("true")) ||
|
|
param == "1") {
|
|
value = exact_value_bool(true);
|
|
} else if (str_eq_ignore_case(param, str_lit("f")) ||
|
|
str_eq_ignore_case(param, str_lit("false")) ||
|
|
param == "0") {
|
|
value = exact_value_bool(false);
|
|
} else {
|
|
gb_printf_err("Invalid flag parameter for '%.*s' = '%.*s'\n", LIT(name), LIT(param));
|
|
}
|
|
} break;
|
|
case BuildFlagParam_Integer:
|
|
value = exact_value_integer_from_string(param);
|
|
break;
|
|
case BuildFlagParam_Float:
|
|
value = exact_value_float_from_string(param);
|
|
break;
|
|
case BuildFlagParam_String: {
|
|
value = exact_value_string(param);
|
|
if (value.kind == ExactValue_String) {
|
|
String s = value.value_string;
|
|
if (s.len > 1 && s[0] == '"' && s[s.len-1] == '"') {
|
|
value.value_string = substring(s, 1, s.len-1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (ok) {
|
|
switch (bf.param_kind) {
|
|
case BuildFlagParam_None:
|
|
if (value.kind != ExactValue_Invalid) {
|
|
gb_printf_err("%.*s expected no value, got %.*s", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
ok = false;
|
|
}
|
|
break;
|
|
case BuildFlagParam_Boolean:
|
|
if (value.kind != ExactValue_Bool) {
|
|
gb_printf_err("%.*s expected a boolean, got %.*s", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
ok = false;
|
|
}
|
|
break;
|
|
case BuildFlagParam_Integer:
|
|
if (value.kind != ExactValue_Integer) {
|
|
gb_printf_err("%.*s expected an integer, got %.*s", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
ok = false;
|
|
}
|
|
break;
|
|
case BuildFlagParam_Float:
|
|
if (value.kind != ExactValue_Float) {
|
|
gb_printf_err("%.*s expected a floating pointer number, got %.*s", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
ok = false;
|
|
}
|
|
break;
|
|
case BuildFlagParam_String:
|
|
if (value.kind != ExactValue_String) {
|
|
gb_printf_err("%.*s expected a string, got %.*s", LIT(name), LIT(param));
|
|
bad_flags = true;
|
|
ok = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ok) switch (bf.kind) {
|
|
case BuildFlag_OutFile: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String path = value.value_string;
|
|
path = string_trim_whitespace(path);
|
|
if (is_import_path_valid(path)) {
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
String ext = path_extension(path);
|
|
if (ext == ".exe") {
|
|
path = substring(path, 0, string_extension_position(path));
|
|
}
|
|
#endif
|
|
build_context.out_filepath = path;
|
|
} else {
|
|
gb_printf_err("Invalid -out path, got %.*s\n", LIT(path));
|
|
bad_flags = true;
|
|
}
|
|
break;
|
|
}
|
|
case BuildFlag_OptimizationLevel:
|
|
GB_ASSERT(value.kind == ExactValue_Integer);
|
|
build_context.optimization_level = cast(i32)big_int_to_i64(&value.value_integer);
|
|
break;
|
|
case BuildFlag_ShowTimings:
|
|
GB_ASSERT(value.kind == ExactValue_Invalid);
|
|
build_context.show_timings = true;
|
|
break;
|
|
case BuildFlag_ThreadCount: {
|
|
GB_ASSERT(value.kind == ExactValue_Integer);
|
|
isize count = cast(isize)big_int_to_i64(&value.value_integer);
|
|
if (count <= 0) {
|
|
gb_printf_err("%.*s expected a positive non-zero number, got %.*s\n", LIT(name), LIT(param));
|
|
build_context.thread_count = 0;
|
|
} else {
|
|
build_context.thread_count = count;
|
|
}
|
|
break;
|
|
}
|
|
case BuildFlag_KeepTempFiles:
|
|
GB_ASSERT(value.kind == ExactValue_Invalid);
|
|
build_context.keep_temp_files = true;
|
|
break;
|
|
|
|
case BuildFlag_CrossCompile: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
cross_compile_target = value.value_string;
|
|
#if defined(GB_SYSTEM_UNIX) && defined(GB_ARCH_64_BIT)
|
|
if (str_eq_ignore_case(cross_compile_target, str_lit("Essence"))) {
|
|
|
|
} else
|
|
#endif
|
|
{
|
|
gb_printf_err("Unsupported cross compilation target '%.*s'\n", LIT(cross_compile_target));
|
|
gb_printf_err("Currently supported targets: Essence (from 64-bit Unixes only)\n");
|
|
bad_flags = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BuildFlag_CrossLibDir: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
if (cross_compile_lib_dir.len) {
|
|
gb_printf_err("Multiple cross compilation library directories\n");
|
|
bad_flags = true;
|
|
} else {
|
|
cross_compile_lib_dir = concatenate_strings(heap_allocator(), str_lit("-L"), value.value_string);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BuildFlag_Collection: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String str = value.value_string;
|
|
isize eq_pos = -1;
|
|
for (isize i = 0; i < str.len; i++) {
|
|
if (str[i] == '=') {
|
|
eq_pos = i;
|
|
break;
|
|
}
|
|
}
|
|
if (eq_pos < 0) {
|
|
gb_printf_err("Expected 'name=path', got '%.*s'\n", LIT(param));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
String name = substring(str, 0, eq_pos);
|
|
String path = substring(str, eq_pos+1, str.len);
|
|
if (name.len == 0 || path.len == 0) {
|
|
gb_printf_err("Expected 'name=path', got '%.*s'\n", LIT(param));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (!string_is_valid_identifier(name)) {
|
|
gb_printf_err("Library collection name '%.*s' must be a valid identifier\n", LIT(name));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (name == "_") {
|
|
gb_printf_err("Library collection name cannot be an underscore\n");
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (name == "system") {
|
|
gb_printf_err("Library collection name 'system' is reserved\n");
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
String prev_path = {};
|
|
bool found = find_library_collection_path(name, &prev_path);
|
|
if (found) {
|
|
gb_printf_err("Library collection '%.*s' already exists with path '%.*s'\n", LIT(name), LIT(prev_path));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
gbAllocator a = heap_allocator();
|
|
String fullpath = path_to_fullpath(a, path);
|
|
if (!path_is_directory(fullpath)) {
|
|
gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(fullpath));
|
|
gb_free(a, fullpath.text);
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
add_library_collection(name, path);
|
|
|
|
// NOTE(bill): Allow for multiple library collections
|
|
continue;
|
|
}
|
|
|
|
|
|
case BuildFlag_Define: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String str = value.value_string;
|
|
isize eq_pos = -1;
|
|
for (isize i = 0; i < str.len; i++) {
|
|
if (str[i] == '=') {
|
|
eq_pos = i;
|
|
break;
|
|
}
|
|
}
|
|
if (eq_pos < 0) {
|
|
gb_printf_err("Expected 'name=value', got '%.*s'\n", LIT(param));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
String name = substring(str, 0, eq_pos);
|
|
String value = substring(str, eq_pos+1, str.len);
|
|
if (name.len == 0 || value.len == 0) {
|
|
gb_printf_err("Expected 'name=value', got '%.*s'\n", LIT(param));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (!string_is_valid_identifier(name)) {
|
|
gb_printf_err("Defined constant name '%.*s' must be a valid identifier\n", LIT(name));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (name == "_") {
|
|
gb_printf_err("Defined constant name cannot be an underscore\n");
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
HashKey key = hash_string(name);
|
|
|
|
if (map_get(&build_context.defined_values, key) != nullptr) {
|
|
gb_printf_err("Defined constant '%.*s' already exists\n", LIT(name));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
ExactValue v = build_param_to_exact_value(name, value);
|
|
if (v.kind != ExactValue_Invalid) {
|
|
map_set(&build_context.defined_values, key, v);
|
|
} else {
|
|
bad_flags = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
case BuildFlag_BuildMode: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String str = value.value_string;
|
|
|
|
if (build_context.command != "build") {
|
|
gb_printf_err("'build-mode' can only be used with the 'build' command\n");
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
if (str == "dll") {
|
|
build_context.is_dll = true;
|
|
} else if (str == "exe") {
|
|
build_context.is_dll = false;
|
|
} else {
|
|
gb_printf_err("Unknown build mode '%.*s'\n", LIT(str));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case BuildFlag_Debug:
|
|
build_context.ODIN_DEBUG = true;
|
|
break;
|
|
|
|
case BuildFlag_NoBoundsCheck:
|
|
build_context.no_bounds_check = true;
|
|
break;
|
|
|
|
case BuildFlag_NoCRT:
|
|
build_context.no_crt = true;
|
|
break;
|
|
|
|
case BuildFlag_UseLLD:
|
|
build_context.use_lld = true;
|
|
break;
|
|
|
|
case BuildFlag_Vet:
|
|
build_context.vet = true;
|
|
break;
|
|
|
|
case BuildFlag_IgnoreUnknownAttributes:
|
|
build_context.ignore_unknown_attributes = true;
|
|
break;
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
case BuildFlag_ResourceFile: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String path = value.value_string;
|
|
path = string_trim_whitespace(path);
|
|
if (is_import_path_valid(path)) {
|
|
if(!string_ends_with(path, str_lit(".rc"))) {
|
|
gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path));
|
|
bad_flags = true;
|
|
break;
|
|
}
|
|
build_context.resource_filepath = substring(path, 0, string_extension_position(path));
|
|
build_context.has_resource = true;
|
|
} else {
|
|
gb_printf_err("Invalid -resource path, got %.*s\n", LIT(path));
|
|
bad_flags = true;
|
|
}
|
|
break;
|
|
}
|
|
case BuildFlag_WindowsPdbName: {
|
|
GB_ASSERT(value.kind == ExactValue_String);
|
|
String path = value.value_string;
|
|
path = string_trim_whitespace(path);
|
|
if (is_import_path_valid(path)) {
|
|
// #if defined(GB_SYSTEM_WINDOWS)
|
|
// String ext = path_extension(path);
|
|
// if (ext != ".pdb") {
|
|
// path = substring(path, 0, string_extension_position(path));
|
|
// }
|
|
// #endif
|
|
build_context.pdb_filepath = path;
|
|
} else {
|
|
gb_printf_err("Invalid -pdb-name path, got %.*s\n", LIT(path));
|
|
bad_flags = true;
|
|
}
|
|
break;
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
|
|
set_flags[bf.kind] = ok;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
gb_printf_err("Unknown flag: '%.*s'\n", LIT(name));
|
|
bad_flags = true;
|
|
}
|
|
}
|
|
|
|
return !bad_flags;
|
|
}
|
|
|
|
void show_timings(Checker *c, Timings *t) {
|
|
Parser *p = c->parser;
|
|
isize lines = p->total_line_count;
|
|
isize tokens = p->total_token_count;
|
|
isize files = 0;
|
|
isize packages = p->packages.count;
|
|
isize total_file_size = 0;
|
|
for_array(i, p->packages) {
|
|
files += p->packages[i]->files.count;
|
|
for_array(j, p->packages[i]->files) {
|
|
AstFile *file = p->packages[i]->files[j];
|
|
total_file_size += file->tokenizer.end - file->tokenizer.start;
|
|
}
|
|
}
|
|
#if 1
|
|
timings_print_all(t);
|
|
#else
|
|
{
|
|
timings_print_all(t);
|
|
gb_printf("\n");
|
|
gb_printf("Total Lines - %td\n", lines);
|
|
gb_printf("Total Tokens - %td\n", tokens);
|
|
gb_printf("Total Files - %td\n", files);
|
|
gb_printf("Total Packages - %td\n", packages);
|
|
gb_printf("Total File Size - %td\n", total_file_size);
|
|
gb_printf("\n");
|
|
}
|
|
{
|
|
TimeStamp ts = t->sections[0];
|
|
GB_ASSERT(ts.label == "parse files");
|
|
f64 parse_time = time_stamp_as_s(ts, t->freq);
|
|
gb_printf("Parse pass\n");
|
|
gb_printf("LOC/s - %.3f\n", cast(f64)lines/parse_time);
|
|
gb_printf("us/LOC - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
|
|
gb_printf("Tokens/s - %.3f\n", cast(f64)tokens/parse_time);
|
|
gb_printf("us/Token - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
|
|
gb_printf("bytes/s - %.3f\n", cast(f64)total_file_size/parse_time);
|
|
gb_printf("us/bytes - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
|
|
|
|
gb_printf("\n");
|
|
}
|
|
{
|
|
TimeStamp ts = t->sections[1];
|
|
GB_ASSERT(ts.label == "type check");
|
|
f64 parse_time = time_stamp_as_s(ts, t->freq);
|
|
gb_printf("Checker pass\n");
|
|
gb_printf("LOC/s - %.3f\n", cast(f64)lines/parse_time);
|
|
gb_printf("us/LOC - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
|
|
gb_printf("Tokens/s - %.3f\n", cast(f64)tokens/parse_time);
|
|
gb_printf("us/Token - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
|
|
gb_printf("bytes/s - %.3f\n", cast(f64)total_file_size/parse_time);
|
|
gb_printf("us/bytes - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
|
|
gb_printf("\n");
|
|
}
|
|
{
|
|
f64 total_time = t->total_time_seconds;
|
|
gb_printf("Total pass\n");
|
|
gb_printf("LOC/s - %.3f\n", cast(f64)lines/total_time);
|
|
gb_printf("us/LOC - %.3f\n", 1.0e6*total_time/cast(f64)lines);
|
|
gb_printf("Tokens/s - %.3f\n", cast(f64)tokens/total_time);
|
|
gb_printf("us/Token - %.3f\n", 1.0e6*total_time/cast(f64)tokens);
|
|
gb_printf("bytes/s - %.3f\n", cast(f64)total_file_size/total_time);
|
|
gb_printf("us/bytes - %.3f\n", 1.0e6*total_time/cast(f64)total_file_size);
|
|
gb_printf("\n");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void remove_temp_files(String output_base) {
|
|
if (build_context.keep_temp_files) return;
|
|
|
|
auto data = array_make<u8>(heap_allocator(), output_base.len + 10);
|
|
defer (array_free(&data));
|
|
|
|
isize n = output_base.len;
|
|
gb_memmove(data.data, output_base.text, n);
|
|
#define EXT_REMOVE(s) do { \
|
|
gb_memmove(data.data+n, s, gb_size_of(s)); \
|
|
gb_file_remove(cast(char *)data.data); \
|
|
} while (0)
|
|
EXT_REMOVE(".ll");
|
|
EXT_REMOVE(".bc");
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
EXT_REMOVE(".obj");
|
|
EXT_REMOVE(".res");
|
|
#else
|
|
EXT_REMOVE(".o");
|
|
#endif
|
|
|
|
#undef EXT_REMOVE
|
|
}
|
|
|
|
|
|
|
|
int query_data_package_compare(void const *a, void const *b) {
|
|
AstPackage *x = *cast(AstPackage *const *)a;
|
|
AstPackage *y = *cast(AstPackage *const *)b;
|
|
|
|
if (x == y) {
|
|
return 0;
|
|
}
|
|
|
|
if (x != nullptr && y != nullptr) {
|
|
return string_compare(x->name, y->name);
|
|
} else if (x != nullptr && y == nullptr) {
|
|
return -1;
|
|
} else if (x == nullptr && y != nullptr) {
|
|
return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int query_data_definition_compare(void const *a, void const *b) {
|
|
Entity *x = *cast(Entity *const *)a;
|
|
Entity *y = *cast(Entity *const *)b;
|
|
|
|
if (x == y) {
|
|
return 0;
|
|
} else if (x != nullptr && y == nullptr) {
|
|
return -1;
|
|
} else if (x == nullptr && y != nullptr) {
|
|
return +1;
|
|
}
|
|
|
|
if (x->pkg != y->pkg) {
|
|
i32 res = query_data_package_compare(&x->pkg, &y->pkg);
|
|
if (res != 0) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return string_compare(x->token.string, y->token.string);
|
|
}
|
|
|
|
int entity_name_compare(void const *a, void const *b) {
|
|
Entity *x = *cast(Entity *const *)a;
|
|
Entity *y = *cast(Entity *const *)b;
|
|
if (x == y) {
|
|
return 0;
|
|
} else if (x != nullptr && y == nullptr) {
|
|
return -1;
|
|
} else if (x == nullptr && y != nullptr) {
|
|
return +1;
|
|
}
|
|
return string_compare(x->token.string, y->token.string);
|
|
}
|
|
|
|
void generate_and_print_query_data(Checker *c, Timings *timings) {
|
|
query_value_allocator = heap_allocator();
|
|
|
|
|
|
auto *root = query_value_map();
|
|
|
|
if (global_error_collector.errors.count > 0) {
|
|
auto *errors = query_value_array();
|
|
root->add("errors", errors);
|
|
for_array(i, global_error_collector.errors) {
|
|
String err = string_trim_whitespace(global_error_collector.errors[i]);
|
|
errors->add(err);
|
|
}
|
|
|
|
}
|
|
|
|
{ // Packages
|
|
auto *packages = query_value_array();
|
|
root->add("packages", packages);
|
|
|
|
auto sorted_packages = array_make<AstPackage *>(query_value_allocator, 0, c->info.packages.entries.count);
|
|
defer (array_free(&sorted_packages));
|
|
|
|
for_array(i, c->info.packages.entries) {
|
|
AstPackage *pkg = c->info.packages.entries[i].value;
|
|
if (pkg != nullptr) {
|
|
array_add(&sorted_packages, pkg);
|
|
}
|
|
}
|
|
gb_sort_array(sorted_packages.data, sorted_packages.count, query_data_package_compare);
|
|
packages->reserve(sorted_packages.count);
|
|
|
|
for_array(i, sorted_packages) {
|
|
AstPackage *pkg = sorted_packages[i];
|
|
String name = pkg->name;
|
|
String fullpath = pkg->fullpath;
|
|
|
|
auto *files = query_value_array();
|
|
files->reserve(pkg->files.count);
|
|
for_array(j, pkg->files) {
|
|
AstFile *f = pkg->files[j];
|
|
files->add(f->fullpath);
|
|
}
|
|
|
|
auto *package = query_value_map();
|
|
package->reserve(3);
|
|
packages->add(package);
|
|
|
|
package->add("name", pkg->name);
|
|
package->add("fullpath", pkg->fullpath);
|
|
package->add("files", files);
|
|
}
|
|
}
|
|
|
|
if (c->info.definitions.count > 0) {
|
|
auto *definitions = query_value_array();
|
|
root->add("definitions", definitions);
|
|
|
|
auto sorted_definitions = array_make<Entity *>(query_value_allocator, 0, c->info.definitions.count);
|
|
defer (array_free(&sorted_definitions));
|
|
|
|
for_array(i, c->info.definitions) {
|
|
Entity *e = c->info.definitions[i];
|
|
String name = e->token.string;
|
|
if (is_blank_ident(name)) {
|
|
continue;
|
|
}
|
|
if ((e->scope->flags & (ScopeFlag_Pkg|ScopeFlag_File)) == 0) {
|
|
continue;
|
|
}
|
|
if (e->parent_proc_decl != nullptr) {
|
|
continue;
|
|
}
|
|
switch (e->kind) {
|
|
case Entity_Builtin:
|
|
case Entity_Nil:
|
|
case Entity_Label:
|
|
continue;
|
|
}
|
|
if (e->pkg == nullptr) {
|
|
continue;
|
|
}
|
|
if (e->token.pos.line == 0) {
|
|
continue;
|
|
}
|
|
if (e->kind == Entity_Procedure) {
|
|
Type *t = base_type(e->type);
|
|
if (t->kind != Type_Proc) {
|
|
continue;
|
|
}
|
|
if (t->Proc.is_poly_specialized) {
|
|
continue;
|
|
}
|
|
}
|
|
if (e->kind == Entity_TypeName) {
|
|
Type *t = base_type(e->type);
|
|
if (t->kind == Type_Struct) {
|
|
if (t->Struct.is_poly_specialized) {
|
|
continue;
|
|
}
|
|
}
|
|
if (t->kind == Type_Union) {
|
|
if (t->Union.is_poly_specialized) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
array_add(&sorted_definitions, e);
|
|
}
|
|
|
|
gb_sort_array(sorted_definitions.data, sorted_definitions.count, query_data_definition_compare);
|
|
definitions->reserve(sorted_definitions.count);
|
|
|
|
for_array(i, sorted_definitions) {
|
|
Entity *e = sorted_definitions[i];
|
|
String name = e->token.string;
|
|
|
|
auto *def = query_value_map();
|
|
def->reserve(16);
|
|
definitions->add(def);
|
|
|
|
def->add("package", e->pkg->name);
|
|
def->add("name", name);
|
|
def->add("filepath", e->token.pos.file);
|
|
def->add("line", e->token.pos.line);
|
|
def->add("column", e->token.pos.column);
|
|
def->add("file_offset", e->token.pos.offset);
|
|
|
|
switch (e->kind) {
|
|
case Entity_Constant: def->add("kind", str_lit("constant")); break;
|
|
case Entity_Variable: def->add("kind", str_lit("variable")); break;
|
|
case Entity_TypeName: def->add("kind", str_lit("type name")); break;
|
|
case Entity_Procedure: def->add("kind", str_lit("procedure")); break;
|
|
case Entity_ProcGroup: def->add("kind", str_lit("procedure group")); break;
|
|
case Entity_ImportName: def->add("kind", str_lit("import name")); break;
|
|
case Entity_LibraryName: def->add("kind", str_lit("library name")); break;
|
|
default: GB_PANIC("Invalid entity kind to be added");
|
|
}
|
|
|
|
|
|
if (e->type != nullptr && e->type != t_invalid) {
|
|
Type *t = e->type;
|
|
Type *bt = t;
|
|
|
|
switch (e->kind) {
|
|
case Entity_TypeName:
|
|
if (!e->TypeName.is_type_alias) {
|
|
bt = base_type(t);
|
|
}
|
|
break;
|
|
}
|
|
|
|
{
|
|
gbString str = type_to_string(t);
|
|
String type_str = make_string(cast(u8 *)str, gb_string_length(str));
|
|
def->add("type", type_str);
|
|
}
|
|
if (t != bt) {
|
|
gbString str = type_to_string(bt);
|
|
String type_str = make_string(cast(u8 *)str, gb_string_length(str));
|
|
def->add("base_type", type_str);
|
|
}
|
|
{
|
|
String type_kind = {};
|
|
Type *bt = base_type(t);
|
|
switch (bt->kind) {
|
|
case Type_Pointer: type_kind = str_lit("pointer"); break;
|
|
case Type_Opaque: type_kind = str_lit("opaque"); break;
|
|
case Type_Array: type_kind = str_lit("array"); break;
|
|
case Type_Slice: type_kind = str_lit("slice"); break;
|
|
case Type_DynamicArray: type_kind = str_lit("dynamic array"); break;
|
|
case Type_Map: type_kind = str_lit("map"); break;
|
|
case Type_Struct: type_kind = str_lit("struct"); break;
|
|
case Type_Union: type_kind = str_lit("union"); break;
|
|
case Type_Enum: type_kind = str_lit("enum"); break;
|
|
case Type_Proc: type_kind = str_lit("procedure"); break;
|
|
case Type_BitField: type_kind = str_lit("bit field"); break;
|
|
case Type_BitSet: type_kind = str_lit("bit set"); break;
|
|
case Type_SimdVector: type_kind = str_lit("simd vector"); break;
|
|
|
|
case Type_Generic:
|
|
case Type_Tuple:
|
|
case Type_BitFieldValue:
|
|
GB_PANIC("Invalid definition type");
|
|
break;
|
|
}
|
|
if (type_kind.len > 0) {
|
|
def->add("type_kind", type_kind);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (e->kind == Entity_TypeName) {
|
|
def->add("size", type_size_of(e->type));
|
|
def->add("align", type_align_of(e->type));
|
|
|
|
|
|
if (is_type_struct(e->type)) {
|
|
auto *data = query_value_map();
|
|
data->reserve(6);
|
|
|
|
def->add("data", data);
|
|
|
|
Type *t = base_type(e->type);
|
|
GB_ASSERT(t->kind == Type_Struct);
|
|
|
|
if (t->Struct.is_polymorphic) {
|
|
data->add("polymorphic", t->Struct.is_polymorphic);
|
|
}
|
|
if (t->Struct.is_poly_specialized) {
|
|
data->add("polymorphic_specialized", t->Struct.is_poly_specialized);
|
|
}
|
|
if (t->Struct.is_packed) {
|
|
data->add("packed", t->Struct.is_packed);
|
|
}
|
|
if (t->Struct.is_raw_union) {
|
|
data->add("raw_union", t->Struct.is_raw_union);
|
|
}
|
|
|
|
auto *fields = query_value_array();
|
|
data->add("fields", fields);
|
|
fields->reserve(t->Struct.fields.count);
|
|
fields->packed = true;
|
|
|
|
for_array(j, t->Struct.fields) {
|
|
Entity *e = t->Struct.fields[j];
|
|
String name = e->token.string;
|
|
if (is_blank_ident(name)) {
|
|
continue;
|
|
}
|
|
|
|
fields->add(name);
|
|
}
|
|
} else if (is_type_union(e->type)) {
|
|
auto *data = query_value_map();
|
|
data->reserve(4);
|
|
|
|
def->add("data", data);
|
|
Type *t = base_type(e->type);
|
|
GB_ASSERT(t->kind == Type_Union);
|
|
|
|
if (t->Union.is_polymorphic) {
|
|
data->add("polymorphic", t->Union.is_polymorphic);
|
|
}
|
|
if (t->Union.is_poly_specialized) {
|
|
data->add("polymorphic_specialized", t->Union.is_poly_specialized);
|
|
}
|
|
|
|
auto *variants = query_value_array();
|
|
variants->reserve(t->Union.variants.count);
|
|
data->add("variants", variants);
|
|
|
|
for_array(j, t->Union.variants) {
|
|
Type *vt = t->Union.variants[j];
|
|
|
|
gbString str = type_to_string(vt);
|
|
String type_str = make_string(cast(u8 *)str, gb_string_length(str));
|
|
variants->add(type_str);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (e->kind == Entity_Procedure) {
|
|
Type *t = base_type(e->type);
|
|
GB_ASSERT(t->kind == Type_Proc);
|
|
|
|
bool is_polymorphic = t->Proc.is_polymorphic;
|
|
bool is_poly_specialized = t->Proc.is_poly_specialized;
|
|
bool ok = is_polymorphic || is_poly_specialized;
|
|
if (ok) {
|
|
auto *data = query_value_map();
|
|
data->reserve(4);
|
|
|
|
def->add("data", data);
|
|
if (is_polymorphic) {
|
|
data->add("polymorphic", is_polymorphic);
|
|
}
|
|
if (is_poly_specialized) {
|
|
data->add("polymorphic_specialized", is_poly_specialized);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (e->kind == Entity_ProcGroup) {
|
|
auto *procedures = query_value_array();
|
|
procedures->reserve(e->ProcGroup.entities.count);
|
|
|
|
for_array(j, e->ProcGroup.entities) {
|
|
Entity *p = e->ProcGroup.entities[j];
|
|
|
|
auto *procedure = query_value_map();
|
|
procedure->reserve(2);
|
|
procedure->packed = true;
|
|
|
|
procedures->add(procedure);
|
|
|
|
procedure->add("package", p->pkg->name);
|
|
procedure->add("name", p->token.string);
|
|
}
|
|
def->add("procedures", procedures);
|
|
}
|
|
|
|
DeclInfo *di = e->decl_info;
|
|
if (di != nullptr) {
|
|
if (di->is_using) {
|
|
def->add("using", query_value_boolean(true));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (build_context.show_timings) {
|
|
Timings *t = timings;
|
|
timings__stop_current_section(t);
|
|
t->total.finish = time_stamp_time_now();
|
|
isize max_len = gb_min(36, t->total.label.len);
|
|
for_array(i, t->sections) {
|
|
TimeStamp ts = t->sections[i];
|
|
max_len = gb_max(max_len, ts.label.len);
|
|
}
|
|
t->total_time_seconds = time_stamp_as_s(t->total, t->freq);
|
|
|
|
auto *tims = query_value_map();
|
|
tims->reserve(8);
|
|
root->add("timings", tims);
|
|
tims->add("time_unit", str_lit("s"));
|
|
|
|
tims->add(t->total.label, cast(f64)t->total_time_seconds);
|
|
|
|
|
|
Parser *p = c->parser;
|
|
if (p != nullptr) {
|
|
isize lines = p->total_line_count;
|
|
isize tokens = p->total_token_count;
|
|
isize files = 0;
|
|
isize packages = p->packages.count;
|
|
isize total_file_size = 0;
|
|
for_array(i, p->packages) {
|
|
files += p->packages[i]->files.count;
|
|
for_array(j, p->packages[i]->files) {
|
|
AstFile *file = p->packages[i]->files[j];
|
|
total_file_size += file->tokenizer.end - file->tokenizer.start;
|
|
}
|
|
}
|
|
|
|
tims->add("total_lines", lines);
|
|
tims->add("total_tokens", tokens);
|
|
tims->add("total_files", files);
|
|
tims->add("total_packages", packages);
|
|
tims->add("total_file_size", total_file_size);
|
|
|
|
auto *sections = query_value_map();
|
|
sections->reserve(t->sections.count);
|
|
tims->add("sections", sections);
|
|
for_array(i, t->sections) {
|
|
TimeStamp ts = t->sections[i];
|
|
f64 section_time = time_stamp_as_s(ts, t->freq);
|
|
|
|
auto *section = query_value_map();
|
|
section->reserve(2);
|
|
sections->add(ts.label, section);
|
|
section->add("time", section_time);
|
|
section->add("total_fraction", section_time/t->total_time_seconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
print_query_data_as_json(root, true);
|
|
gb_printf("\n");
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 exec_llvm_opt(String output_base) {
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
// For more passes arguments: http://llvm.org/docs/Passes.html
|
|
return system_exec_command_line_app("llvm-opt",
|
|
"\"%.*sbin/opt\" \"%.*s.ll\" -o \"%.*s.bc\" %.*s "
|
|
"",
|
|
LIT(build_context.ODIN_ROOT),
|
|
LIT(output_base), LIT(output_base),
|
|
LIT(build_context.opt_flags));
|
|
#else
|
|
// NOTE(zangent): This is separate because it seems that LLVM tools are packaged
|
|
// with the Windows version, while they will be system-provided on MacOS and GNU/Linux
|
|
return system_exec_command_line_app("llvm-opt",
|
|
"opt \"%.*s.ll\" -o \"%.*s.bc\" %.*s "
|
|
"",
|
|
LIT(output_base), LIT(output_base),
|
|
LIT(build_context.opt_flags));
|
|
#endif
|
|
}
|
|
|
|
i32 exec_llvm_llc(String output_base) {
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
// For more arguments: http://llvm.org/docs/CommandGuide/llc.html
|
|
return system_exec_command_line_app("llvm-llc",
|
|
"\"%.*sbin\\llc\" \"%.*s.bc\" -filetype=obj -O%d "
|
|
"-o \"%.*s.obj\" "
|
|
"%.*s"
|
|
"",
|
|
LIT(build_context.ODIN_ROOT),
|
|
LIT(output_base),
|
|
build_context.optimization_level,
|
|
LIT(output_base),
|
|
LIT(build_context.llc_flags));
|
|
#else
|
|
// NOTE(zangent): Linux / Unix is unfinished and not tested very well.
|
|
// For more arguments: http://llvm.org/docs/CommandGuide/llc.html
|
|
return system_exec_command_line_app("llc",
|
|
"llc \"%.*s.bc\" -filetype=obj -relocation-model=pic -O%d "
|
|
"%.*s "
|
|
"%s"
|
|
"",
|
|
LIT(output_base),
|
|
build_context.optimization_level,
|
|
LIT(build_context.llc_flags),
|
|
str_eq_ignore_case(cross_compile_target, str_lit("Essence")) ? "-mtriple=x86_64-pc-none-elf" : "");
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
int main(int arg_count, char **arg_ptr) {
|
|
if (arg_count < 2) {
|
|
usage(make_string_c(arg_ptr[0]));
|
|
return 1;
|
|
}
|
|
|
|
Timings timings = {0};
|
|
timings_init(&timings, str_lit("Total Time"), 128);
|
|
defer (timings_destroy(&timings));
|
|
|
|
init_string_buffer_memory();
|
|
init_global_error_collector();
|
|
global_big_int_init();
|
|
arena_init(&global_ast_arena, heap_allocator());
|
|
|
|
array_init(&library_collections, heap_allocator());
|
|
// NOTE(bill): 'core' cannot be (re)defined by the user
|
|
add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core")));
|
|
|
|
map_init(&build_context.defined_values, heap_allocator());
|
|
|
|
Array<String> args = setup_args(arg_count, arg_ptr);
|
|
|
|
String command = args[1];
|
|
String init_filename = {};
|
|
String run_args_string = {};
|
|
|
|
bool run_output = false;
|
|
if (command == "run") {
|
|
if (args.count < 3) {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (i <= last_non_run_arg) {
|
|
continue;
|
|
}
|
|
array_add(&run_args, args[i]);
|
|
}
|
|
|
|
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;
|
|
} else if (command == "build") {
|
|
if (args.count < 3) {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
init_filename = args[2];
|
|
} else if (command == "check") {
|
|
if (args.count < 3) {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
build_context.no_output_files = true;
|
|
init_filename = args[2];
|
|
} else if (command == "query") {
|
|
if (args.count < 3) {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
build_context.no_output_files = true;
|
|
build_context.print_query_data = true;
|
|
init_filename = args[2];
|
|
} else if (command == "docs") {
|
|
if (args.count < 3) {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
|
|
init_filename = args[2];
|
|
build_context.generate_docs = true;
|
|
#if 1
|
|
print_usage_line(0, "Documentation generation is not yet supported");
|
|
return 1;
|
|
#endif
|
|
} else if (command == "version") {
|
|
gb_printf("%.*s version %.*s\n", LIT(args[0]), LIT(ODIN_VERSION));
|
|
return 0;
|
|
} else {
|
|
usage(args[0]);
|
|
return 1;
|
|
}
|
|
|
|
build_context.command = command;
|
|
|
|
if (!parse_build_flags(args)) {
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
// 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")));
|
|
}
|
|
|
|
|
|
init_build_context();
|
|
if (build_context.word_size == 4) {
|
|
print_usage_line(0, "%s 32-bit is not yet supported", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
init_universal();
|
|
// TODO(bill): prevent compiling without a linker
|
|
|
|
timings_start_section(&timings, str_lit("parse files"));
|
|
|
|
Parser parser = {0};
|
|
if (!init_parser(&parser)) {
|
|
return 1;
|
|
}
|
|
defer (destroy_parser(&parser));
|
|
|
|
if (parse_packages(&parser, init_filename) != ParseFile_None) {
|
|
return 1;
|
|
}
|
|
|
|
if (build_context.generate_docs) {
|
|
// generate_documentation(&parser);
|
|
return 0;
|
|
}
|
|
timings_start_section(&timings, str_lit("type check"));
|
|
|
|
Checker checker = {0};
|
|
|
|
bool checked_inited = init_checker(&checker, &parser);
|
|
defer (if (checked_inited) {
|
|
destroy_checker(&checker);
|
|
});
|
|
|
|
if (checked_inited) {
|
|
check_parsed_files(&checker);
|
|
}
|
|
|
|
|
|
if (build_context.no_output_files) {
|
|
if (build_context.print_query_data) {
|
|
generate_and_print_query_data(&checker, &timings);
|
|
} else {
|
|
if (build_context.show_timings) {
|
|
show_timings(&checker, &timings);
|
|
}
|
|
}
|
|
|
|
if (global_error_collector.count != 0) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!checked_inited) {
|
|
return 1;
|
|
}
|
|
|
|
irGen ir_gen = {0};
|
|
if (!ir_gen_init(&ir_gen, &checker)) {
|
|
return 1;
|
|
}
|
|
// defer (ir_gen_destroy(&ir_gen));
|
|
|
|
|
|
timings_start_section(&timings, str_lit("llvm ir gen"));
|
|
ir_gen_tree(&ir_gen);
|
|
|
|
timings_start_section(&timings, str_lit("llvm ir opt tree"));
|
|
ir_opt_tree(&ir_gen);
|
|
|
|
timings_start_section(&timings, str_lit("llvm ir print"));
|
|
print_llvm_ir(&ir_gen);
|
|
|
|
|
|
String output_name = ir_gen.output_name;
|
|
String output_base = ir_gen.output_base;
|
|
|
|
build_context.optimization_level = gb_clamp(build_context.optimization_level, 0, 3);
|
|
|
|
i32 exit_code = 0;
|
|
|
|
timings_start_section(&timings, str_lit("llvm-opt"));
|
|
exit_code = exec_llvm_opt(output_base);
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
|
|
timings_start_section(&timings, str_lit("llvm-llc"));
|
|
exit_code = exec_llvm_llc(output_base);
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
timings_start_section(&timings, str_lit("msvc-link"));
|
|
|
|
gbString lib_str = gb_string_make(heap_allocator(), "");
|
|
defer (gb_string_free(lib_str));
|
|
char lib_str_buf[1024] = {0};
|
|
|
|
for_array(i, ir_gen.module.foreign_library_paths) {
|
|
String lib = ir_gen.module.foreign_library_paths[i];
|
|
GB_ASSERT(lib.len < gb_count_of(lib_str_buf)-1);
|
|
isize len = gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
|
|
" \"%.*s\"", LIT(lib));
|
|
lib_str = gb_string_appendc(lib_str, lib_str_buf);
|
|
}
|
|
|
|
char *output_ext = "exe";
|
|
gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
|
|
defer (gb_string_free(link_settings));
|
|
|
|
if (build_context.is_dll) {
|
|
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));
|
|
}
|
|
|
|
if (build_context.no_crt) {
|
|
link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib");
|
|
} else {
|
|
link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt");
|
|
}
|
|
|
|
if (ir_gen.module.generate_debug_info) {
|
|
link_settings = gb_string_append_fmt(link_settings, " /DEBUG");
|
|
}
|
|
if (!build_context.use_lld) { // msvc
|
|
if (build_context.has_resource) {
|
|
exit_code = system_exec_command_line_app("msvc-link",
|
|
"rc /nologo /fo \"%.*s.res\" \"%.*s.rc\"",
|
|
LIT(output_base),
|
|
LIT(build_context.resource_filepath)
|
|
);
|
|
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
|
|
exit_code = system_exec_command_line_app("msvc-link",
|
|
"link \"%.*s.obj\" \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
|
|
"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
|
|
" %.*s "
|
|
" %s "
|
|
"",
|
|
LIT(output_base), LIT(output_base), LIT(output_base), output_ext,
|
|
lib_str, LIT(build_context.link_flags),
|
|
link_settings
|
|
);
|
|
} else {
|
|
exit_code = system_exec_command_line_app("msvc-link",
|
|
"link \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
|
|
"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
|
|
" %.*s "
|
|
" %s "
|
|
"",
|
|
LIT(output_base), LIT(output_base), output_ext,
|
|
lib_str, LIT(build_context.link_flags),
|
|
link_settings
|
|
);
|
|
}
|
|
} else { // lld
|
|
exit_code = system_exec_command_line_app("msvc-link",
|
|
"\"%.*s\\bin\\lld-link\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
|
|
"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
|
|
" %.*s "
|
|
" %s "
|
|
"",
|
|
LIT(build_context.ODIN_ROOT),
|
|
LIT(output_base), LIT(output_base), output_ext,
|
|
lib_str, LIT(build_context.link_flags),
|
|
link_settings
|
|
);
|
|
}
|
|
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
|
|
if (build_context.show_timings) {
|
|
show_timings(&checker, &timings);
|
|
}
|
|
|
|
remove_temp_files(output_base);
|
|
|
|
if (run_output) {
|
|
system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(output_base), LIT(run_args_string));
|
|
}
|
|
#else
|
|
timings_start_section(&timings, str_lit("ld-link"));
|
|
|
|
// NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe
|
|
char cwd[256];
|
|
getcwd(&cwd[0], 256);
|
|
//printf("%s\n", cwd);
|
|
|
|
// NOTE(vassvik): needs to add the root to the library search paths, so that the full filenames of the library
|
|
// files can be passed with -l:
|
|
gbString lib_str = gb_string_make(heap_allocator(), "-L/");
|
|
defer (gb_string_free(lib_str));
|
|
|
|
for_array(i, ir_gen.module.foreign_library_paths) {
|
|
String lib = ir_gen.module.foreign_library_paths[i];
|
|
|
|
// 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 (lib.len > 2 && lib[0] == '-' && lib[1] == 'f') {
|
|
// framework thingie
|
|
lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", (int)(lib.len) - 2, lib.text + 2);
|
|
} else if (string_ends_with(lib, str_lit(".a"))) {
|
|
// static libs, absolute full path relative to the file in which the lib was imported from
|
|
lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib));
|
|
} else if (string_ends_with(lib, str_lit(".dylib"))) {
|
|
// dynamic lib, relative path to 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));
|
|
}
|
|
#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
|
|
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
|
|
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
|
|
}
|
|
|
|
// 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 = {};
|
|
char *link_settings = "";
|
|
char *linker;
|
|
if (build_context.is_dll) {
|
|
// Shared libraries are .dylib on MacOS and .so on Linux.
|
|
#if defined(GB_SYSTEM_OSX)
|
|
output_ext = STR_LIT(".dylib");
|
|
#else
|
|
output_ext = STR_LIT(".so");
|
|
#endif
|
|
|
|
link_settings = "-shared";
|
|
} else {
|
|
// TODO: Do I need anything here?
|
|
link_settings = "";
|
|
}
|
|
|
|
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 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_.
|
|
if (str_eq_ignore_case(cross_compile_target, str_lit("Essence"))) {
|
|
linker = "x86_64-elf-gcc -T core/sys/essence_linker_userland64.ld -ffreestanding -nostdlib -lgcc -g -z max-page-size=0x1000 -Wno-unused-command-line-argument";
|
|
} else {
|
|
linker = "clang -Wno-unused-command-line-argument";
|
|
}
|
|
#endif
|
|
|
|
exit_code = system_exec_command_line_app("ld-link",
|
|
"%s \"%.*s.o\" -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'
|
|
" -macosx_version_min 10.8.0 "
|
|
// This points the linker to where the entry point is
|
|
" -e _main "
|
|
#endif
|
|
, linker, LIT(output_base), LIT(output_base), LIT(output_ext),
|
|
lib_str,
|
|
str_eq_ignore_case(cross_compile_target, str_lit("Essence")) ? "-lfreetype -lglue" : "-lc -lm",
|
|
LIT(build_context.link_flags),
|
|
link_settings,
|
|
LIT(cross_compile_lib_dir)
|
|
);
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
|
|
#if defined(GB_SYSTEM_OSX)
|
|
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
|
|
exit_code = system_exec_command_line_app("dsymutil",
|
|
"dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext)
|
|
);
|
|
|
|
if (exit_code != 0) {
|
|
return exit_code;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
if (build_context.show_timings) {
|
|
show_timings(&checker, &timings);
|
|
}
|
|
|
|
remove_temp_files(output_base);
|
|
|
|
if (run_output) {
|
|
//NOTE(thebirk): This whole thing is a little leaky
|
|
String complete_path = concatenate_strings(heap_allocator(), output_base, output_ext);
|
|
complete_path = path_to_full_path(heap_allocator(), complete_path);
|
|
system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string));
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|