// Copyright (c) 2019 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.

#include "compiler_internal.h"
#include "../build/project.h"
#include "../utils/json.h"
#include <compiler_tests/benchmark.h>
#include "../utils/whereami.h"
#if LLVM_AVAILABLE
#include "c3_llvm.h"
#endif
#include "git_hash.h"
#include <errno.h>

#define MAX_OUTPUT_FILES 1000000
#define MAX_MODULES 100000


CompilerState compiler;

Vmem ast_arena;
Vmem expr_arena;
Vmem decl_arena;
Vmem type_info_arena;
Vmem sourceloc_arena;

static double compiler_init_time;
static double compiler_parsing_time;
static double compiler_sema_time;
static double compiler_exec_time;
static double compiler_ir_gen_time;
static double compiler_codegen_time;
static double compiler_link_time;

const char* c3_suffix_list[3] = { ".c3", ".c3t", ".c3i" };


static const char *out_name(void)
{
	if (compiler.build.output_name) return compiler.build.output_name;
	if (compiler.build.name) return compiler.build.name;
	return NULL;
}

#define START_VMEM_SIZE (sizeof(size_t) == 4 ? 1024 : 4096)

void compiler_init(BuildOptions *build_options)
{
	// Process --path
	if (build_options->path && !dir_change(build_options->path))
	{
		error_exit("Failed to change path to '%s'.", build_options->path);
	}

	FOREACH(const char *, dir, build_options->unchecked_directories)
	{
		(void)check_dir(dir);
	}

	compiler_init_time = -1;
	compiler_parsing_time = -1;
	compiler_sema_time = -1;
	compiler_ir_gen_time = -1;
	compiler_codegen_time = -1;
	compiler_link_time = -1;

	INFO_LOG("Version: %s", COMPILER_VERSION);

	GlobalContext new_context = { .in_panic_mode = false };
	compiler.context = new_context;
	// Skip library detection.
	//compiler.lib_dir = find_lib_dir();
	//DEBUG_LOG("Found std library: %s", compiler.lib_dir);
	htable_init(&compiler.context.modules, 16 * 1024);
	pathtable_init(&compiler.context.path_symbols, INITIAL_SYMBOL_MAP);
	decltable_init(&compiler.context.symbols, INITIAL_SYMBOL_MAP);

	htable_init(&compiler.context.features, 1024);
	htable_init(&compiler.context.compiler_defines, 16 * 1024);
	methodtable_init(&compiler.context.method_extensions, 16 * 1024);
	compiler.context.module_list = NULL;
	compiler.context.method_extension_list = NULL;

	vmem_init(&ast_arena, START_VMEM_SIZE);
	ast_calloc();
	vmem_init(&expr_arena, START_VMEM_SIZE);
	expr_calloc();
	vmem_init(&decl_arena, START_VMEM_SIZE);
	decl_calloc();
	vmem_init(&type_info_arena, START_VMEM_SIZE);
	type_info_calloc();
	vmem_init(&sourceloc_arena, START_VMEM_SIZE);
	sourceloc_calloc();
	// Create zero index value.
	if (build_options->std_lib_dir)
	{
		compiler.context.lib_dir = build_options->std_lib_dir;
	}
	else
	{
		compiler.context.lib_dir = find_lib_dir();
	}

	compiler.context.ansi = build_options->ansi;
	if (build_options->print_env)
	{
		compiler.context.should_print_environment = true;
	}
	if (build_options->print_asm)
	{
		compiler.context.should_print_asm = true;
	}
}

static void compiler_lex(void)
{
	FOREACH(const char *, source, compiler.context.sources)
	{
		bool loaded = false;
		const char *error;
		File *file = source_file_load(source, &loaded, &error);
		if (!file) error_exit("%s", error);
		if (loaded) continue;
		Lexer lexer = { .file = file };
		lexer_init(&lexer);
		OUTF("# %s\n", file->full_path);
		while (lexer_next_token(&lexer))
		{
			TokenType token_type = lexer.token_type;
			OUTF("%s ", token_type_to_string(token_type));
			if (token_type == TOKEN_EOF) break;
		}
		OUTN("");
	}
	exit_compiler(COMPILER_SUCCESS_EXIT);
}



typedef struct CompileData_
{
	void *context;
	const char *object_name;
	Task task;
} CompileData;

#if LLVM_AVAILABLE
void thread_compile_task_llvm(void *compile_data)
{
	CompileData *data = compile_data;
	data->object_name = llvm_codegen(data->context);
}
#else 
void thread_compile_task_llvm(void *compile_data)
{
    error_exit("LLVM backend not available.");
}
#endif 

void thread_compile_task_tb(void *compile_data)
{
	CompileData *data = compile_data;
	data->object_name = tilde_codegen(data->context);
}

#if !TB_AVAILABLE
const char *tilde_codegen(void *context)
{
	error_exit("TB backend not available.");
	UNREACHABLE
}
void **tilde_gen(Module** modules, unsigned module_count)
{
	error_exit("TB backend not available.");
	UNREACHABLE
}

#endif

const char *build_base_name(void)
{
	const char *name = out_name();
	if (name) return name;
	Module **modules = compiler.context.module_list;
	Module *main_module = (modules[0] == compiler.context.core_module && vec_size(modules) > 1) ? modules[1] : modules[0];
	return main_module->short_path;
}

static const char *exe_name(void)
{
	ASSERT(compiler.build.output_name || compiler.build.name || compiler.context.main || compiler.build.no_entry);
	const char *name = out_name();
	if (!name && compiler.build.no_entry)
	{
		name = "out";
	}
	if (!name)
	{
		name = compiler.context.main->unit->module->short_path;
	}
	
	// Use custom extension if specified
	if (compiler.build.extension)
	{
		return str_cat(name, compiler.build.extension);
	}
	
	switch (compiler.build.arch_os_target)
	{
		case WINDOWS_AARCH64:
		case WINDOWS_X64:
		case MINGW_X64:
			return str_cat(name, ".exe");
		default:
			if (arch_is_wasm(compiler.platform.arch)) return str_cat(name, ".wasm");
			return name;
	}
}

static const char *dynamic_lib_name(void)
{
	const char *name = build_base_name();
	
	// Use custom extension if specified
	if (compiler.build.extension)
	{
		return str_cat(name, compiler.build.extension);
	}

	return str_cat(name, compiler.platform.dylib_suffix);
}

const char *static_lib_name(void)
{
	const char *name = build_base_name();
	
	// Use custom extension if specified
	if (compiler.build.extension)
	{
		return str_cat(name, compiler.build.extension);
	}
	
	switch (compiler.build.arch_os_target)
	{
		case WINDOWS_AARCH64:
		case WINDOWS_X64:
		case MINGW_X64:
			return str_cat(name, ".lib");
		default:
			return str_cat(name, ".a");
	}
}

static void free_arenas(void)
{
	if (compiler.build.print_stats)
	{
		printf("-- AST/EXPR/TYPE INFO -- \n");
		printf(" * Ast size:       %u bytes\n", (unsigned)sizeof(Ast));
		printf(" * Decl size:      %u bytes\n", (unsigned)sizeof(Decl));
		printf(" * Expr size:      %u bytes\n", (unsigned)sizeof(Expr));
		printf(" * TypeInfo size:  %u bytes\n", (unsigned)sizeof(TypeInfo));
		printf(" * SourceLoc size: %u bytes\n", (unsigned)sizeof(SourceLoc));
		printf(" - Memory use ----------- \n");

		printf(" * Ast:       %llu kb (%u elements)\n",
			   (unsigned long long)ast_arena.allocated / 1024,
			   (unsigned)(ast_arena.allocated / sizeof(Ast)));
		printf(" * Decl:      %llu kb (%u elements)\n",
			   (unsigned long long)decl_arena.allocated / 1024,
			   (unsigned)(decl_arena.allocated / sizeof(Decl)));
		printf(" * Expr:      %llu kb (%u elements)\n",
			   (unsigned long long)expr_arena.allocated / 1024,
			   (unsigned)(expr_arena.allocated / sizeof(Expr)));
		printf(" * TypeInfo:  %llu kb (%u elements)\n",
			   (unsigned long long)type_info_arena.allocated / 1024,
			   (unsigned)(type_info_arena.allocated / sizeof(TypeInfo)));
		printf(" * SourceLoc: %llu kb (%u elements)\n",
			   (unsigned long long)sourceloc_arena.allocated / 1024,
			   (unsigned)(sourceloc_arena.allocated / sizeof(SourceLoc)));
		printf(" * Total:     %llu kb\n",
			   (unsigned long long)(sourceloc_arena.allocated + decl_arena.allocated + expr_arena.allocated + type_info_arena.allocated + type_info_arena.allocated) / 1024);

	}

	ast_arena_free();
	decl_arena_free();
	expr_arena_free();
	type_info_arena_free();
	sourceloc_arena_free();

	if (compiler.build.print_stats) print_arena_status();
}

static int compile_cfiles(const char *cc, const char **files, const char *flags, const char **include_dirs,
                          const char **out_files, const char *output_subdir)
{
	if (!cc) cc = default_c_compiler();
	int total = 0;
	FOREACH(const char *, file, files)
	{
		out_files[total++] = cc_compiler(cc, file, flags, include_dirs, output_subdir);
	}
	return total;
}

static void compiler_print_bench(void)
{
	if (compiler.build.print_stats)
	{
		puts("--------- Compilation time statistics --------\n");
		double last = compiler_init_time;
		double parse_time = compiler_parsing_time - compiler_init_time;
		if (compiler_parsing_time >= 0) last = compiler_parsing_time;
		double sema_time = compiler_sema_time - compiler_parsing_time;
		if (compiler_sema_time >= 0) last = compiler_sema_time;
		double ir_time = compiler_ir_gen_time - compiler_sema_time;
		if (compiler_ir_gen_time >= 0) last = compiler_ir_gen_time;
		double codegen_time = compiler_codegen_time - compiler_ir_gen_time;
		if (compiler_codegen_time >= 0) last = compiler_codegen_time;
		double link_time = compiler_link_time - compiler_codegen_time;
		if (compiler_link_time >= 0) last = compiler_link_time;
		printf("Frontend -------------------- Time --- %% total\n");
		if (compiler_init_time >= 0)
		{
			compiler_init_time -= compiler.script_time;
			printf("Initialization took: %10.3f ms  %8.1f %%\n", compiler_init_time * 1000, compiler_init_time * 100 / last);
			if (compiler.script_time > 0)
			{
				printf("Scripts took:        %10.3f ms  %8.1f %%\n", compiler.script_time * 1000, compiler.script_time * 100 / last);
			}
		}
		if (compiler_parsing_time >= 0) printf("Parsing took:        %10.3f ms  %8.1f %%\n", parse_time * 1000, parse_time * 100 / last);
		if (compiler_sema_time >= 0)
		{
			printf("Analysis took:       %10.3f ms  %8.1f %%\n", sema_time * 1000, sema_time * 100 / last);
			if (compiler.exec_time > 0)
			{
				printf(" - Scripts took:     %10.3f ms  %8.1f %%\n", compiler.exec_time * 1000, compiler.exec_time * 100 / last);
			}
			printf("TOTAL:               %10.3f ms  %8.1f %%\n", compiler_sema_time * 1000, compiler_sema_time * 100 / last);
			puts("");
		}
		if (compiler_ir_gen_time >= 0)
		{
			printf("Backend --------------------- Time --- %% total\n");
			printf("Ir gen took:         %10.3f ms  %8.1f %%\n", ir_time * 1000, ir_time * 100 / last);
			if (compiler_codegen_time >= 0)
			{
				if (compiler.build.build_threads > 1)
				{
					printf("Codegen took:        %10.3f ms  %8.1f %%  (%d threads)\n", codegen_time * 1000, codegen_time * 100 / last, compiler.build.build_threads);
				}
				else
				{
					printf("Codegen took:        %10.3f ms  %8.1f %%\n", codegen_time * 1000, codegen_time * 100 / last);
				}
			}
			if (compiler_link_time >= 0)
			{
				printf("Linking took:        %10.3f ms  %8.1f %%\n", link_time * 1000, link_time * 100 / last);
			}
			printf("TOTAL:               %10.3f ms  %8.1f %%\n", (last - compiler_sema_time) * 1000, 100 - compiler_sema_time * 100 / last);
		}
		if (last)
		{
			puts("----------------------------------------------");
			printf("TOTAL compile time: %.3f ms.\n", last * 1000);
			puts("----------------------------------------------");
		}
	}
}

void delete_object_files(const char **files, size_t count)
{
	for (size_t i = 0; i < count; i++)
	{
		assert(files);
		file_delete_file(files[i]);
	}
}

void compiler_parse(void)
{
	// Cleanup any errors (could there really be one here?!)
	global_context_clear_errors();

	// Add the standard library
	if (compiler.context.lib_dir && !no_stdlib())
	{
		file_add_wildcard_files(&compiler.context.sources, compiler.context.lib_dir, true, c3_suffix_list, 3);
	}

	// Load and parse all files.
	bool has_error = false;
	if (compiler.build.print_input)
	{
		puts("# input-files-begin");
	}
	FOREACH(const char *, source, compiler.context.sources)
	{
		bool loaded = false;
		const char *error;
		File *file = source_file_load(source, &loaded, &error);
		if (!file) error_exit("%s", error);
		if (loaded) continue;
		if (!parse_file(file)) has_error = true;
		if (compiler.build.print_input) puts(file->full_path);
	}
	if (compiler.build.print_input)
	{
		puts("# input-files-end");
	}
	if (compiler.build.read_stdin)
	{
		if (!parse_stdin()) has_error = true;
	}

	if (has_error)
	{
		if (compiler.build.lsp_output)
		{
			eprintf("> ENDLSP-ERROR\n");
			exit_compiler(COMPILER_SUCCESS_EXIT);
		}
		exit_compiler(EXIT_FAILURE);
	}
	compiler_parsing_time = bench_mark();
}

bool compiler_should_output_file(const char *file)
{
	if (!vec_size(compiler.build.emit_only)) return true;
	FOREACH(const char *, f, compiler.build.emit_only)
	{
		if (str_eq(file, f)) return true;
	}
	return false;
}

static void create_output_dir(const char *dir)
{
	if (!dir) return;
	if (strlen(dir) == 0) return;
	if (file_exists(dir))
	{
		if (!file_is_dir(dir)) error_exit("Output directory is not a directory %s.", dir);
		return;
	}
	scratch_buffer_clear();
	scratch_buffer_append(dir);
	dir_make_recursive(scratch_buffer_to_string());
	if (!file_exists(dir))
	{
		error_exit("Failed to create directory '%s'.", dir);
	}
}


void compiler_compile(void)
{
	if (compiler.build.lsp_output)
	{
		eprintf("> BEGINLSP\n");
	}
	sema_analysis_run();
	if (compiler.build.lsp_output)
	{
		eprintf("> ENDLSP-OK\n");
		exit_compiler(COMPILER_SUCCESS_EXIT);
	}
	compiler_sema_time = bench_mark();
	compiler_exec_time = compiler.exec_time;
	Module **modules = compiler.context.module_list;
	unsigned module_count = vec_size(modules);
	if (module_count > MAX_MODULES)
	{
		error_exit("Too many modules.");
	}
	if (module_count < 1 && !compiler.build.object_files)
	{
		error_exit("No module to compile.");
	}
	if (compiler.build.output_headers)
	{
		if (compiler.build.header_file_dir)
		{
			create_output_dir(compiler.build.header_file_dir);
		}
		header_gen(modules, module_count);
	}

	if (compiler.build.check_only)
	{
		free_arenas();
		return;
	}

	void **gen_contexts;
	void (*task)(void *);

	if ((compiler.build.emit_llvm || compiler.build.test_output || compiler.build.lsp_output))
	{
		create_output_dir(compiler.build.ir_file_dir);
	}
	if (compiler.build.emit_asm)
	{
		create_output_dir(compiler.build.asm_file_dir);
	}
	if (compiler.build.emit_object_files)
	{
		create_output_dir(compiler.build.object_file_dir);
	}
	if (compiler.build.type == TARGET_TYPE_EXECUTABLE && !compiler.context.main && !compiler.build.no_entry)
	{
		error_exit("The 'main' function for the executable could not found, did you forget to add it?\n\n"
				   "- If you're using an alternative entry point you can suppress this message using '--no-entry'.\n"
				   "- If you want to build a library, use 'static-lib' or 'dynamic-lib'.\n"
				   "- If you just want to output object files for later linking, use 'compile-only'.");
	}

	if (compiler.build.type == TARGET_TYPE_STATIC_LIB)
	{
		compiler.build.single_module = SINGLE_MODULE_ON;
	}
	if (compiler.build.emit_asm)
	{
		scratch_buffer_clear();
		scratch_buffer_append(compiler.build.asm_file_dir);
		dir_make_recursive(scratch_buffer_to_string());
	}
	if (compiler.build.emit_object_files)
	{
		scratch_buffer_clear();
		scratch_buffer_append(compiler.build.object_file_dir);
		dir_make_recursive(scratch_buffer_to_string());
	}
	switch (compiler.build.backend)
	{
		case BACKEND_C:
			gen_contexts = c_gen(modules, module_count);
			(void)gen_contexts;
			error_exit("Unfinished C backend!");
		case BACKEND_LLVM:
#if LLVM_AVAILABLE
			gen_contexts = llvm_gen(modules, module_count);
			task = &thread_compile_task_llvm;
#else 
            error_exit("C3C compiled without LLVM!");
#endif
            break;
		case BACKEND_TB:
			gen_contexts = tilde_gen(modules, module_count);
			task = &thread_compile_task_tb;
			break;
		default:
			UNREACHABLE_VOID
	}
	compiler_ir_gen_time = bench_mark();
	const char *output_exe = NULL;
	const char *output_static = NULL;
	const char *output_dynamic = NULL;
	if (!compiler.build.test_output && !compiler.build.benchmark_output)
	{
		switch (compiler.build.type)
		{
			case TARGET_TYPE_BENCHMARK:
				compiler.build.name = "benchmarkrun";
				compiler.build.output_name = compiler.build.runner_output_name ? compiler.build.runner_output_name : compiler.build.name;
				output_exe = exe_name();
				break;
			case TARGET_TYPE_TEST:
				compiler.build.name = "testrun";
				compiler.build.output_name = compiler.build.runner_output_name ? compiler.build.runner_output_name : compiler.build.name;
				output_exe = exe_name();
				break;
			case TARGET_TYPE_EXECUTABLE:
				ASSERT(compiler.context.main || compiler.build.no_entry);
				output_exe = exe_name();
				break;
			case TARGET_TYPE_STATIC_LIB:
				output_static = static_lib_name();
				break;
			case TARGET_TYPE_DYNAMIC_LIB:
				output_dynamic = dynamic_lib_name();
				break;
			case TARGET_TYPE_OBJECT_FILES:
				if (compiler.obj_output)
				{
					OUTF("Object file %s created.\n", compiler.obj_output);
					break;
				}
				OUTF("Object files written to %s.\n", compiler.build.object_file_dir);
				break;
			case TARGET_TYPE_PREPARE:
				break;
			default:
				UNREACHABLE_VOID
		}
	}
	if (compiler.build.emit_llvm)
	{
		OUTF("LLVM files written to %s.\n", compiler.build.ir_file_dir);
	}
	if (compiler.build.emit_asm)
	{
		OUTF("Asm files written to %s.\n", compiler.build.asm_file_dir);
	}
	free_arenas();

	uint32_t output_file_count = vec_size(gen_contexts);
	unsigned external_objfile_count = vec_size(compiler.build.object_files);
	unsigned cfiles = vec_size(compiler.build.csources);
	unsigned cfiles_library = 0;
	FOREACH(LibraryTarget *, lib, compiler.build.ccompiling_libraries)
	{
		cfiles_library += vec_size(lib->csources);
	}
	unsigned total_output = output_file_count + cfiles + cfiles_library + external_objfile_count;

	if (total_output > MAX_OUTPUT_FILES)
	{
		error_exit("Too many output files.");
	}
	if (!total_output)
	{
		if (output_exe)
		{
			error_exit("No output files were generated. This may happen if the program is not "
					   "linked with anything and all the code is optimized away.");
		}
		else
		{
			error_exit("No output files were generated. This may happen if there were no exported functions "
					   "and all the other code was optimized away.");
		}
	}

	CompileData *compile_data = ccalloc(sizeof(CompileData), output_file_count);
	const char **obj_files = cmalloc(sizeof(char*) * total_output);

	if (cfiles)
	{
		int compiled = compile_cfiles(compiler.build.cc, compiler.build.csources,
		                              compiler.build.cflags, compiler.build.cinclude_dirs, &obj_files[output_file_count], "tmp_c_compile");
		ASSERT(cfiles == compiled);
		(void)compiled;
	}
	const char **obj_file_next = &obj_files[output_file_count + cfiles];
	FOREACH(LibraryTarget *, lib, compiler.build.ccompiling_libraries)
	{
		obj_file_next += compile_cfiles(lib->cc ? lib->cc : compiler.build.cc, lib->csources,
		                                lib->cflags, lib->cinclude_dirs, obj_file_next, lib->parent->provides);
	}
	for (unsigned i = 0; i < external_objfile_count; i++)
	{
		obj_file_next[0] = compiler.build.object_files[i];
		obj_file_next++;
	}

	Task **tasks = NULL;
	for (unsigned i = 0; i < output_file_count; i++)
	{
		compile_data[i] = (CompileData) { .context = gen_contexts[i] };
		compile_data[i].task = (Task) { task, &compile_data[i] };
		vec_add(tasks, &compile_data[i].task);
	}

#if USE_PTHREAD
	INFO_LOG("Will use %d thread(s).\n", compiler.build.build_threads);
#endif
	unsigned task_count = vec_size(tasks);
	if (task_count > 0)
	{
		Task *task_last = VECLAST(tasks);
		vec_pop(tasks);
		task_last->task(task_last->arg);
		task_count--;
		if (task_count)
		{
			taskqueue_run((int)(compiler.build.build_threads > task_count ? task_count : compiler.build.build_threads), tasks);
		}
	}
	if (compiler.build.print_output)
	{
		puts("# output-files-begin");
	}
	int index = 0;
	for (unsigned i = output_file_count; i > 0; i--)
	{
		const char *name = compile_data[i - 1].object_name;
		if (!name) output_file_count--;
		obj_files[index++] = name;
		if (compiler.build.print_output)
		{
			puts(name);
		}
	}
	if (compiler.build.print_output)
	{
		puts("# output-files-end");
	}

	output_file_count += cfiles + cfiles_library + external_objfile_count;
	unsigned objfile_delete_count = output_file_count - external_objfile_count;
	free(compile_data);
	compiler_codegen_time = bench_mark();

	if ((output_static || output_dynamic || output_exe) && !output_file_count)
	{
		if (!compiler.build.object_files)
		{
			error_exit("Compilation could not complete due to --no-obj, please try removing it.");
		}
		error_exit("Compilation produced no object files, maybe there was no code?");
	}
	if (vec_size(compiler.build.emit_only)) goto SKIP;
	if (output_exe)
	{
		if (file_path_is_relative(output_exe))
		{
			output_exe = file_append_path(compiler.build.output_dir, output_exe);
		}
		;
		file_create_folders(output_exe);
		bool system_linker_available = link_libc() && compiler.platform.os != OS_TYPE_WIN32;
		if (system_linker_available)
		{
			const char *cc = compiler.build.cc ? compiler.build.cc : default_c_compiler();
			if (!file_executable_in_path(cc)) system_linker_available = false;
		}
		bool use_system_linker = system_linker_available && compiler.build.arch_os_target == default_target;
		switch (compiler.build.linker_type)
		{
			case LINKER_TYPE_CC:
				if (!system_linker_available)
				{
					const char *cc = compiler.build.cc ? compiler.build.cc : default_c_compiler();
					OUTF("C compiler '%s' not found or system linker is unsupported; using built-in linker instead.\n", cc);
					compiler.build.linker_type = LINKER_TYPE_BUILTIN;
					use_system_linker = false;
					break;
				}
				use_system_linker = true;
				break;
			case LINKER_TYPE_BUILTIN:
				use_system_linker = false;
				break;
			default:
				if (!use_system_linker && compiler.build.linker_type == LINKER_TYPE_NOT_SET)
				{
					compiler.build.linker_type = LINKER_TYPE_BUILTIN;
				}
				break;
		}
		if (use_system_linker || compiler.build.linker_type == LINKER_TYPE_CC)
		{
			platform_linker(output_exe, obj_files, output_file_count);
			compiler_link_time = bench_mark();
			compiler_print_bench();
			delete_object_files(obj_files, objfile_delete_count);
		}
		else
		{
			compiler_print_bench();
			if (!obj_format_linking_supported(compiler.platform.object_format) || !linker(output_exe, obj_files,
																						output_file_count))
			{
				eprintf("No linking is performed due to missing linker support.\n");
				compiler.build.run_after_compile = false;
			}
			else
			{
				delete_object_files(obj_files, objfile_delete_count);
			}
		}

		if (compiler.build.run_after_compile)
		{
			DEBUG_LOG("Will run");
			const char *name = output_exe;
			while (name[0] == '.' && name[1] == '/') name += 2;
			scratch_buffer_clear();
			if (compiler.platform.os == OS_TYPE_WIN32)
			{
				size_t len = strlen(name);
				for (unsigned i = 0; i < len; i++)
				{
					if (name[i] == '/')
					{
						if (name[i + 1] == '.' && name[i + 2] == '/')
						{
							i++;
							continue;
						}
						scratch_buffer_append_char('\\');
						continue;
					}
					scratch_buffer_append_char(name[i]);
				}
			}
			else
			{
				if (name[0] != '/') scratch_buffer_append("./");
				scratch_buffer_append(name);
			}
			name = scratch_buffer_to_string();
			const char *full_path = realpath(scratch_buffer_to_string(), NULL);
			if (!full_path)
			{
				error_exit("The binary '%s' was unexpectedly not found.", scratch_buffer_to_string());
			}
			OUTF("Launching %s", name);
			FOREACH(const char *, arg, compiler.build.args)
			{
				OUTF(" %s", arg);
			}
			if (compiler.build.run_dir)
			{
				OUTF(" from directory %s", compiler.build.run_dir);
				dir_change(compiler.build.run_dir);
			}
			OUTN("");
			int ret = run_subprocess(full_path, compiler.build.args);
			if (compiler.build.delete_after_run)
			{
				file_delete_file(full_path);
			}
			if (ret < 0) exit_compiler(EXIT_FAILURE);
			OUTF("Program completed with exit code %d.\n", ret);
			if (ret != 0) exit_compiler(ret);
		}
	}
	else if (output_static)
	{
		if (file_path_is_relative(output_static))
		{
			output_static = file_append_path(compiler.build.output_dir, output_static);
		}
		file_create_folders(output_static);
		if (!static_lib_linker(output_static, obj_files, output_file_count))
		{
			error_exit("Failed to produce static library '%s'.", output_static);
		}
		delete_object_files(obj_files, objfile_delete_count);
		compiler_link_time = bench_mark();
		compiler_print_bench();
		OUTF("Static library '%s' created.\n", output_static);
	}
	else if (output_dynamic)
	{
		if (file_path_is_relative(output_dynamic))
		{
			output_dynamic = file_append_path(compiler.build.output_dir, output_dynamic);
		}
		file_create_folders(output_dynamic);
		if (!dynamic_lib_linker(output_dynamic, obj_files, output_file_count))
		{
			error_exit("Failed to produce dynamic library '%s'.", output_dynamic);
		}
		delete_object_files(obj_files, objfile_delete_count);
		OUTF("Dynamic library '%s' created.\n", output_dynamic);
		compiler_link_time = bench_mark();
		compiler_print_bench();
	}
	else
	{
		SKIP:
		compiler_print_bench();
	}
	free(obj_files);
}
INLINE void expand_csources(const char *base_dir, const char **source_dirs, const char ***sources_ref)
{
	if (source_dirs)
	{
		static const char* c_suffix_list[3] = { ".c", ".m" };
		*sources_ref = target_expand_source_names(base_dir, source_dirs, c_suffix_list, NULL, 2, false);
	}
}

INLINE void expand_cinclude_dirs(const char *base_dir, const char **include_dirs, const char ***include_dirs_ref)
{
	const char **expanded_include_dirs = NULL;
	FOREACH(const char *, include_dir, include_dirs)
	{
		vec_add(expanded_include_dirs, file_append_path(base_dir, include_dir));
	}
	*include_dirs_ref = expanded_include_dirs;
}

void compile_target(BuildOptions *options)
{
	init_default_build_target(&compiler.build, options);
	compile();
}

void clean_obj_files(void)
{
	file_delete_all_files_in_dir_with_suffix(compiler.build.ir_file_dir, ".ll");
	file_delete_all_files_in_dir_with_suffix(compiler.build.asm_file_dir, ".s");
	file_delete_all_files_in_dir_with_suffix(compiler.build.object_file_dir, ".obj");
	file_delete_all_files_in_dir_with_suffix(compiler.build.object_file_dir, ".o");
}
void compile_clean(BuildOptions *options)
{
	init_build_target(&compiler.build, options);
	clean_obj_files();
}
void compile_file_list(BuildOptions *options)
{
	init_build_target(&compiler.build, options);
	if (compiler.build.type == TARGET_TYPE_PREPARE)
	{
		if (options->command != COMMAND_BUILD)
		{
			error_exit("The target is a 'prepare' target, and only 'build' can be used with it.");
		}
		OUTF("Running prepare target '%s'.\n", options->target_select);
		execute_scripts();
		OUTN("Completed.\n.");
		return;
	}
	if (options->command == COMMAND_CLEAN_RUN)
	{
		clean_obj_files();
	}
	compile();
}

static inline void setup_define(const char *id, Expr *expr)
{
	TokenType token_type = TOKEN_CONST_IDENT;
	id = symtab_add(id, (uint32_t) strlen(id), fnv1a(id, (uint32_t) strlen(id)), &token_type);
	void *previous = htable_set(&compiler.context.compiler_defines, (void*)id, expr);
	if (previous)
	{
		error_exit("Redefined ident %s", id);
	}
}
static void setup_int_define(const char *id, uint64_t i, Type *type)
{
	Type *flat = type_flatten(type);
	ASSERT(type_is_integer(flat));
	Expr *expr = expr_new_const_int(0, flat, i);
	expr->type = type;
	if (expr_const_will_overflow(&expr->const_expr, flat->type_kind))
	{
		error_exit("Integer define %s overflow.", id);
	}
	setup_define(id, expr);
}

static void setup_string_define(const char *id, const char *value)
{
	setup_define(id, expr_new_const_string(0, value));
}

static void setup_bool_define(const char *id, bool value)
{
	setup_define(id, expr_new_const_bool(0, type_bool, value));
}

bool use_ansi(void)
{
	switch (compiler.context.ansi)
	{
		case ANSI_DETECT:
			break;
		case ANSI_OFF:
			return false;
		case ANSI_ON:
			return true;
	}
#if PLATFORM_WINDOWS
	return false;
#else
	return isatty(fileno(stdout));
#endif
}


const char * vendor_fetch_single(const char* lib, const char* path, bool progress) 
{
	const char *resource = str_printf("/c3lang/vendor/releases/download/latest/%s.c3l", lib);
	const char *destination = file_append_path(path, str_printf("%s.c3l", lib));
	const char *error = download_file("https://github.com", resource, destination, progress);
	return error;	
}


void vendor_fetch(BuildOptions *options)
{
	if (!download_available())
	{
		error_exit("The 'vendor-fetch' command requires libcurl to download libraries.\n"
				   "Please ensure libcurl is installed on your system.");
	}
	bool ansi = use_ansi();

	if (str_eq(options->path, DEFAULT_PATH))
	{
		// check if there is a project JSON file
		if (file_exists(PROJECT_JSON5) || file_exists(PROJECT_JSON))
		{
			const char** deps_dirs =  get_project_dependency_directories();
			int num_lib = (int)vec_size(deps_dirs);
			if (num_lib > 0) options->vendor_download_path = deps_dirs[0];
		}
	}

	unsigned count = 0;
	const char** fetched_libraries = NULL;
	int total_libraries = (int)vec_size(options->libraries_to_fetch);
	
	if (total_libraries == 0)
	{
		const char *tmp_dir = dir_make_temp_dir();
		const char *tmp_file = file_append_path(tmp_dir, "vendor_list.json");
		const char *error = download_file("https://api.github.com", "/repos/c3lang/vendor/contents/libraries/", tmp_file, false);
		if (error)
		{
			error_exit("Failed to fetch library list: %s", error);
		}

		size_t size;
		char *json_str = file_read_all(tmp_file, &size);
		JsonParser parser;
		json_init_string(&parser, json_str);
		JSONObject *obj = json_parse(&parser);
		if (!obj || obj->type != J_ARRAY)
		{
			error_exit("Failed to parse library list.");
		}

		printf("Available vendor libraries:\n");
		FOREACH(JSONObject *, entry, obj->elements)
		{
			JSONObject *name = json_map_get(entry, "name");
			JSONObject *type = json_map_get(entry, "type");
			if (name && type && str_eq(type->str, "dir"))
			{
				const char *n = name->str;
				if (n[0] == '.') continue;
				if (str_has_suffix(n, ".c3l"))
				{
					printf("  %s\n", str_remove_suffix(n, ".c3l"));
				}
				else
				{
					printf("  %s\n", n);
				}
			}
		}
		file_delete_file(tmp_file);
		file_delete_dir(tmp_dir);
		return;
	}

	for(int i = 0; i < total_libraries; i++)
	{
		const char *lib = options->libraries_to_fetch[i];
		printf("Fetching library '%s'...\n", lib);
		(void)fflush(stdout);
		const char *error = vendor_fetch_single(lib, options->vendor_download_path, ansi);

		if (!error)
		{
			vec_add(fetched_libraries, lib);
			count++;
		}
		else
		{
			if (ansi)
			{
				printf("\033[2K\033[31mFailed to fetch library '%s': %s\033[0m\n", lib, error);
			}
			else
			{
				printf("Failed: '%s'\n", error);
			}
			(void)fflush(stdout);
		}
	}

	if (ansi && total_libraries > 1) printf("\033[2K");

	// add fetched library to the dependency list
	add_libraries_to_project_file(fetched_libraries, options->project_options.target_name);

	if (count == 0)	error_exit("Error: Failed to download any libraries.");
	if (count < vec_size(options->libraries_to_fetch)) error_exit("Error: Only some libraries were downloaded.");

	if (ansi) printf("\033[32mFetching complete.\033[0m\t\t\n");
}


void print_syntax(BuildOptions *options)
{
	symtab_init(64 * 1024);

	if (options->print_keywords)
	{
		for (int i = 1; i < TOKEN_LAST; i++)
		{
			const char *name = token_type_to_string((TokenType)i);
			if (name[0] == '$' || (name[0] >= 'a' && name[0] <= 'z'))
			{
				if (name[1] == '$' || name[1] == '\0') continue;
				printf("%s\n", name);
			}
		}
	}
	if (options->print_operators)
	{
		for (int i = 1; i < TOKEN_LAST; i++)
		{
			if (i == TOKEN_DOCS_START || i == TOKEN_DOCS_END) continue;
			const char *name = token_type_to_string((TokenType)i);
			char first_char = name[0];
			if (first_char == '$' || first_char == '@' || first_char == '_' || first_char == '#'
				|| (first_char >= 'a' && first_char <= 'z')
				|| (first_char >= 'A' && first_char <= 'Z'))
			{
				continue;
			}
			printf("%s\n", name);
		}
	}
	if (options->print_attributes)
	{
		for (int i = 0; i < NUMBER_OF_ATTRIBUTES; i++)
		{
			printf("%s\n", attribute_list[i]);
		}
	}
	if (options->print_builtins)
	{
		for (int i = 0; i < NUMBER_OF_BUILTINS; i++)
		{
			printf("$$%s\n", builtin_list[i]);
		}
		for (int i = 0; i < NUMBER_OF_BUILTIN_DEFINES; i++)
		{
			printf("$$%s\n", builtin_defines[i]);
		}
	}
	if (options->print_type_properties)
	{
		for (int i = 0; i < NUMBER_OF_TYPE_PROPERTIES; i++)
		{
			printf("%s\n", type_property_list[i]);
		}
	}
	if (options->print_project_properties)
	{
		puts("Project properties");
		puts("------------------");
		for (int i = 0; i < project_default_keys_count; i++)
		{
			printf("%-*s%s\n", 35, project_default_keys[i][0], project_default_keys[i][1]);
		}
		puts("");
		puts("Target properties");
		puts("-----------------");
		for (int i = 0; i < project_target_keys_count; i++)
		{
			printf("%-*s%s\n", 35, project_target_keys[i][0], project_target_keys[i][1]);
		}
		puts("");
	}
	if (options->print_manifest_properties)
	{
		puts("Manifest properties");
		puts("------------------");
		for (int i = 0; i < manifest_default_keys_count; i++)
		{
			printf("%-*s%s\n", 35, manifest_default_keys[i][0], manifest_default_keys[i][1]);
		}
		puts("");
		puts("Target properties");
		puts("-----------------");
		for (int i = 0; i < manifest_target_keys_count; i++)
		{
			printf("%-*s%s\n", 35, manifest_target_keys[i][0], manifest_target_keys[i][1]);
		}
		puts("");
	}
	if (options->print_precedence)
	{
		puts("precedence     | operators");
		puts("---------------+----------");
		puts(" 1. Macro      | @        ");
		puts(" 2. Call       | . () [] !! postfix ++/-- postfix !");
		puts(" 3. Unary      | ! - + ~ * & prefix ++/-- (cast)");
		puts(" 4. Mult       | * / %");
		puts(" 5. Shift      | << >>");
		puts(" 6. Bitwise    | ^ | &");
		puts(" 7. Additive   | + - +++");
		puts(" 8. Relational | < > <= >= == !=");
		puts(" 9. And        | && &&&");
		puts("10. Or         | || |||");
		puts("11. Ternary    | ?: ?? ???");
		puts("12. Assign     | = *= /= %= -= += |= &= ^= <<= >>= +++=");
	}

}

static int jump_buffer_size()
{
	switch (compiler.build.arch_os_target)
	{
		case ARCH_OS_TARGET_DEFAULT:
			return 512;
		case ELF_RISCV32:
		case LINUX_RISCV32:
			// Godbolt test
			return 76;
		case ELF_XTENSA:
			// Godbolt 68 => 17 with 32 bit pointers
			return 17;
		case ELF_RISCV64:
		case LINUX_RISCV64:
			// Godbolt test
			return 43;
		case MACOS_X64:
			return 19; // Actually 18.5
		case WINDOWS_X64: // 16 on x32
		case MINGW_X64:
			// Godbolt test
			return 32;
		case ELF_X64:
		case LINUX_X64:
		case ANDROID_X86_64:
			// Godbolt test
			return 25;
		case FREEBSD_X64:
		case NETBSD_AARCH64:
		case NETBSD_X64:
		case OPENBSD_X64:
			REMINDER("Guessing setjmp for platform.");
			return 32;
		case ANDROID_AARCH64:
		case LINUX_AARCH64:
		case ELF_AARCH64:
			return 39;
		case WINDOWS_AARCH64:
			// Based on Godbolt
			return 24;
		case IOS_AARCH64:
		case MACOS_AARCH64:
			// Based on macOS headers
			return 25;
		case LINUX_X86:
		case MCU_X86:
		case NETBSD_X86:
		case OPENBSD_X86:
		case ELF_X86:
		case FREEBSD_X86:
			// Early GCC
			return 39;
		case WASM32:
		case WASM64:
			REMINDER("WASM setjmp size is unknown");
			return 512;
	}
	UNREACHABLE
}

void execute_scripts(void)
{
	if (!vec_size(compiler.build.exec)) return;
	if (compiler.build.trust_level < TRUST_FULL)
	{
		error_exit("This target has 'exec' directives, to run it trust level must be set to '--trust=full'.");
	}
	char old_path[PATH_MAX + 1];
	if (compiler.build.script_dir)
	{
		if (getcwd(old_path, PATH_MAX) && !dir_change(compiler.build.script_dir))
		{
			error_exit("Failed to open script dir '%s'", compiler.build.script_dir);
		}
	}
	double start = bench_mark();
	FOREACH(const char *, exec, compiler.build.exec)
	{
		StringSlice execs = slice_from_string(exec);
		StringSlice call = slice_next_token(&execs, ' ');
		File *script;
		if (call.len < 3 || call.ptr[call.len - 3] != '.' || call.ptr[call.len - 2] != 'c' ||
		    call.ptr[call.len - 1] != '3')
		{
			char *res = execute_cmd(exec, false, NULL, 0);
			if (compiler.build.silent) continue;
			script = source_file_text_load(exec, res);
			goto PRINT_SCRIPT;
		}
		scratch_buffer_clear();
		scratch_buffer_append_len(call.ptr, call.len);
		script = compile_and_invoke(scratch_buffer_copy(), execs.len ? execs.ptr : "", NULL, 2048);
PRINT_SCRIPT:;
		size_t out_len = script->content_len;
		const char *out = script->contents;
		if (!compiler.build.silent && script->content_len > 0)
		{
			printf("%.*s\n", (int)out_len, out);
		}
	}
	dir_change(old_path);
	compiler.script_time += bench_mark() - start;
}

static void check_address_sanitizer_options(BuildTarget *target)
{
	if (target->feature.sanitize_memory || target->feature.sanitize_thread)
	{
		error_exit("Address sanitizer cannot be used together with memory or thread sanitizer.");
	}
	switch (target->arch_os_target)
	{
		case WINDOWS_X64:
		{
			WinCrtLinking crt_linking = target->win.crt_linking;
			if (crt_linking == WIN_CRT_DEFAULT)
			{
				// Default to dynamic, as static ASan is removed in LLVM 21+ for Windows
				target->win.crt_linking = WIN_CRT_DYNAMIC;
				crt_linking = WIN_CRT_DYNAMIC;
			}
			else if (crt_linking == WIN_CRT_STATIC)
			{
				error_exit("Address sanitizer on Windows no longer supports static CRT linking (`--wincrt=static`). Please use `dynamic`.");
			}

			if (crt_linking == WIN_CRT_STATIC_DEBUG || crt_linking == WIN_CRT_DYNAMIC_DEBUG)
			{
				// We currently don't have ASan runtime libraries linked against debug CRT.
				error_exit("Address sanitizer cannot be used when using `static-debug` or `dynamic-debug` for `wincrt`. Please use `static` or `dynamic` instead.");
			}

			WARNING("Using address sanitizer on Windows requires the sanitizer option `detect_odr_violation=0`, either set by returning it from `__asan_default_options`, or via an environment variable `ASAN_OPTIONS=detect_odr_violation=0`");
			break;
		}
		case LINUX_X86:
		case LINUX_X64:
		case MACOS_AARCH64:
		case MACOS_X64:
		case FREEBSD_X86:
		case FREEBSD_X64:
		case NETBSD_X86:
		case NETBSD_X64:
			break;
		default:
			error_exit("Address sanitizer is only supported on Linux, FreeBSD, NetBSD, Darwin and Windows.");
	}
}

static void check_memory_sanitizer_options(BuildTarget *target)
{
	if (target->feature.sanitize_thread)
	{
		error_exit("Memory sanitizer cannot be used together thread sanitizer.");
	}
	switch (target->arch_os_target)
	{
		case LINUX_AARCH64:
		case LINUX_X86:
		case LINUX_X64:
		case FREEBSD_X86:
		case FREEBSD_X64:
		case NETBSD_X86:
		case NETBSD_X64:
			break;
		default:
			error_exit("Memory sanitizer is only supported on Linux, FreeBSD and NetBSD.");
	}
	if (target->reloc_model != RELOC_BIG_PIE)
	{
		error_exit("Memory sanitizer requires `PIE` relocation model.");
	}
}

static void check_thread_sanitizer_options(BuildTarget *target)
{
	switch (target->arch_os_target)
	{
		case LINUX_AARCH64:
		case LINUX_X64:
		case MACOS_AARCH64:
		case MACOS_X64:
		case FREEBSD_X64:
		case NETBSD_X64:
			break;
		default:
			error_exit("Thread sanitizer is only supported on 64-bit Linux, NetBSD, FreeBSD and Darwin.");
	}
}
static void check_sanitizer_options(BuildTarget *target)
{
	if (target->feature.sanitize_address) check_address_sanitizer_options(target);
	if (target->feature.sanitize_memory) check_memory_sanitizer_options(target);
	if (target->feature.sanitize_thread) check_thread_sanitizer_options(target);

	if (target->type == TARGET_TYPE_BENCHMARK)
	{
		if (target->feature.sanitize_address)
		{
			WARNING("Running benchmarks with address sanitizer enabled!");
		}
		else if (target->feature.sanitize_thread)
		{
			WARNING("Running benchmarks with thread sanitizer enabled!");
		}
		else if (target->feature.sanitize_memory)
		{
			WARNING("Running benchmarks with memory sanitizer enabled!");
		}
	}

}

const char *compiler_date_to_iso(void)
{
	const char *comp_date = __DATE__;
	static char iso[11] = "2000-01-01";
	iso[2] = comp_date[9];
	iso[3] = comp_date[10];
	int month;
	switch (comp_date[0])
	{
		case 'O':
			month = 10;
			break;
		case 'D':
			month = 12;
			break;
		case 'N':
			month = 11;
			break;
		case 'J':
			if (comp_date[1] == 'a')
			{
				month = 1;
				break;
			}
			if (comp_date[2] == 'n')
			{
				month = 6;
				break;
			}
			month = 7;
			break;
		case 'F':
			month = 2;
			break;
		case 'A':
			month = comp_date[2] == 'p' ? 4 : 8;
			break;
		case 'M':
			month = comp_date[2] == 'r' ? 3 : 5;
			break;
		case 'S':
			month = 9;
			break;
		default:
			UNREACHABLE
	}
	iso[5] = (char)(month / 10 + '0');
	iso[6] = (char)(month % 10 + '0');
	iso[8] = (char)(comp_date[4] == ' ' ? '0' : comp_date[4]);
	iso[9] = comp_date[5];
	return iso;
}

void compile()
{
	symtab_init(compiler.build.symtab_size);
	compiler.build.sources = target_expand_source_names(NULL, compiler.build.source_dirs, c3_suffix_list, &compiler.build.object_files, 3, true);
	if (compiler.build.testing && compiler.build.test_source_dirs)
	{
		const char **test_sources = target_expand_source_names(NULL, compiler.build.test_source_dirs, c3_suffix_list, &compiler.build.object_files, 3, true);
		FOREACH(const char *, file, test_sources) vec_add(compiler.build.sources, file);
	}
	expand_csources(NULL, compiler.build.csource_dirs, &compiler.build.csources);
	execute_scripts();
	compiler.context.main = NULL;
	compiler.context.string_type = NULL;
	compiler.platform.asm_initialized = false;
	// Create the core module if needed.
	Path *core_path = MALLOCS(Path);
	core_path->module = kw_std__core;
	core_path->loc = 0;
	core_path->len = strlen(kw_std__core);
	compiler.context.core_module = compiler_find_or_create_module(core_path);
	CompilationUnit *unit = CALLOCS(CompilationUnit);
	unit->file = source_file_generate("core_internal.c3");
	unit->module = compiler.context.core_module;
	compiler.context.core_unit = unit;
	target_setup(&compiler.build);
	if (compiler.context.should_print_environment)
	{
		print_build_env();
		exit_compiler(COMPILER_SUCCESS_EXIT);
	}
	if (compiler.context.should_print_asm)
	{
		print_asm(&compiler.platform);
		exit_compiler(COMPILER_SUCCESS_EXIT);
	}
	check_sanitizer_options(&compiler.build);
	resolve_libraries(&compiler.build);
	compiler.context.sources = compiler.build.sources;
	FOREACH(LibraryTarget *, lib, compiler.build.ccompiling_libraries)
	{
		expand_csources(lib->parent->dir, lib->csource_dirs, &lib->csources);
		expand_cinclude_dirs(lib->parent->dir, lib->cinclude_dirs, &lib->cinclude_dirs);
	}
	FOREACH(const char *, feature_flag, compiler.build.feature_list)
	{
		feature_flag = symtab_preset(feature_flag, TOKEN_CONST_IDENT);
		htable_set(&compiler.context.features, (void *) feature_flag, (void *) feature_flag);
	}

	setup_int_define("C_SHORT_SIZE", compiler.platform.width_c_short, type_int);
	setup_int_define("C_INT_SIZE", compiler.platform.width_c_int, type_int);
	setup_int_define("C_LONG_SIZE", compiler.platform.width_c_long, type_int);
	setup_int_define("C_LONG_LONG_SIZE", compiler.platform.width_c_long_long, type_int);
	setup_int_define("REGISTER_SIZE", compiler.platform.width_register, type_int);
	setup_int_define("MAX_VECTOR_SIZE", compiler.build.max_vector_size, type_int);
	setup_bool_define("C_CHAR_IS_SIGNED", compiler.platform.signed_c_char);
	setup_bool_define("PLATFORM_BIG_ENDIAN", compiler.platform.big_endian);
	setup_bool_define("PLATFORM_I128_SUPPORTED", compiler.platform.int128);
	setup_bool_define("PLATFORM_F128_SUPPORTED", compiler.platform.float128);
	setup_bool_define("PLATFORM_F16_SUPPORTED", compiler.platform.float16);
	setup_int_define("ARCH_TYPE", (uint64_t)compiler.platform.arch, type_int);
	setup_int_define("MEMORY_ENVIRONMENT", (uint64_t)compiler.build.memory_environment, type_int);
	setup_bool_define("COMPILER_LIBC_AVAILABLE", link_libc());
	setup_bool_define("CUSTOM_LIBC", custom_libc());
	setup_int_define("COMPILER_OPT_LEVEL", (uint64_t)compiler.build.optlevel, type_int);
	setup_int_define("OS_TYPE", (uint64_t)compiler.platform.os, type_int);
	setup_int_define("COMPILER_SIZE_OPT_LEVEL", (uint64_t)compiler.build.optsize, type_int);
	setup_bool_define("COMPILER_SAFE_MODE", safe_mode_enabled());
	setup_bool_define("DEBUG_SYMBOLS", compiler.build.debug_info == DEBUG_INFO_FULL);
	setup_bool_define("PANIC_MSG", compiler.build.feature.panic_level != PANIC_OFF);
	setup_bool_define("BACKTRACE", compiler.build.show_backtrace != SHOW_BACKTRACE_OFF);
#if LLVM_AVAILABLE
    setup_int_define("LLVM_VERSION", llvm_version_major, type_int);
#else 
    setup_int_define("LLVM_VERSION", 0, type_int);
#endif

	setup_string_define("VERSION", COMPILER_VERSION);
	setup_bool_define("PRERELEASE", PRERELEASE);

    setup_bool_define("BENCHMARKING", compiler.build.benchmarking);
	setup_int_define("JMP_BUF_SIZE", jump_buffer_size(), type_int);
	setup_bool_define("TESTING", compiler.build.testing);
	setup_int_define("LANGUAGE_DEV_VERSION", 7, type_int);
	setup_bool_define("ADDRESS_SANITIZER", compiler.build.feature.sanitize_address);
	setup_bool_define("MEMORY_SANITIZER", compiler.build.feature.sanitize_memory);
	setup_bool_define("THREAD_SANITIZER", compiler.build.feature.sanitize_thread);
	setup_string_define("BUILD_HASH", GIT_HASH);
	setup_string_define("BUILD_DATE", compiler_date_to_iso());
	Expr *expr_names = expr_new(EXPR_CONST, 0);
	Expr *expr_emails = expr_new(EXPR_CONST, 0);
	expr_names->const_expr.const_kind = CONST_UNTYPED_LIST;
	expr_emails->const_expr.const_kind = CONST_UNTYPED_LIST;
	expr_names->type = type_untypedlist;
	expr_emails->type = type_untypedlist;
	expr_names->resolve_status = expr_emails->resolve_status = RESOLVE_DONE;
	FOREACH(AuthorEntry, entry, compiler.build.authors)
	{
		Expr *const_name = expr_new_const_string(0, entry.author); // NOLINT
		Expr *const_email = expr_new_const_string(0, entry.email ? entry.email : ""); // NOLINT
		vec_add(expr_names->const_expr.untyped_list, const_name);
		vec_add(expr_emails->const_expr.untyped_list, const_email);
	}
	setup_define("AUTHORS", expr_names);
	setup_define("AUTHOR_EMAILS", expr_emails);
	setup_string_define("PROJECT_VERSION", compiler.build.version);
	type_init_cint();
	compiler_init_time = bench_mark();

	if (!vec_size((compiler.build.object_files)) && !vec_size(compiler.build.sources) && !compiler.build.read_stdin) error_exit("No files to compile.");

	if (compiler.build.lex_only)
	{
		compiler_lex();
		compiler_parsing_time = bench_mark();
		return;
	}
	if (compiler.build.parse_only)
	{
		compiler_parse();
		compiler_parsing_time = bench_mark();
		emit_json();
		exit_compiler(COMPILER_SUCCESS_EXIT);
	}
	compiler_compile();
}

void global_context_add_decl(Decl *decl)
{
	decltable_set(&compiler.context.symbols, decl);
	pathtable_set(&compiler.context.path_symbols, decl);
}

void global_context_replace_decl(Decl *old, Decl *new_symbol)
{
	decltable_replace(&compiler.context.symbols, old, new_symbol);
	pathtable_replace(&compiler.context.path_symbols, old, new_symbol);
}

void linking_add_link(Linking *linking, const char *link)
{
	FOREACH(const char *, existing_link, linking->links)
	{
		if (str_eq(link, existing_link)) return;
	}
	vec_add(linking->links, link);
}

void global_context_clear_errors(void)
{
	compiler.context.in_panic_mode = false;
	compiler.context.errors_found = 0;
	compiler.context.warnings_found = 0;
}

void global_context_add_type(Type *type)
{
	DEBUG_LOG("Created type %s.", type->name);
	ASSERT(type_ok(type));
	vec_add(compiler.context.type, type);
}

const char *get_object_extension(void)
{
	switch (compiler.build.arch_os_target)
	{
		case ANY_WINDOWS_ARCH_OS:
			return ".obj";
		case WASM32:
		case WASM64:
			return ".wasm";
		default:
			return ".o";
	}
}

Module *global_context_find_module(const char *name)
{
	ASSERT(name);
	return htable_get(&compiler.context.modules, (void *)name);
}

Module *compiler_find_or_create_module(Path *module_name)
{
	Module *module = global_context_find_module(module_name->module);
	if (module) return module;

	DEBUG_LOG("Creating module %s.", module_name->module);
	// Set up the module.
	module = CALLOCS(Module);
	module->name = module_name;
	module->inlined_at = (InliningSpan) { 0, NULL };
	size_t first = 0;
	for (size_t i = module_name->len; i > 0; i--)
	{
		if (module_name->module[i - 1] == ':')
		{
			first = i;
			break;
		}
	}
	if (!first)
	{
		module->short_path = module_name->module;
	}
	else
	{
		const char *name = &module_name->module[first];
		size_t len = module_name->len - first;
		TokenType type = TOKEN_IDENT;
		module->short_path = symtab_add(name, len, fnv1a(name, len), &type);
	}
	module->stage = ANALYSIS_NOT_BEGUN;
	htable_init(&module->symbols, 0x1000);
	htable_set(&compiler.context.modules, (void *)module_name->module, module);
	vec_add(compiler.context.module_list, module);
	return module;
}


const char *scratch_buffer_interned(void)
{
	TokenType type = TOKEN_INVALID_TOKEN;
	return scratch_buffer_interned_as(&type);
}

const char *scratch_buffer_interned_as(TokenType* type)
{
	return symtab_add(scratch_buffer.str, scratch_buffer.len,
	                  fnv1a(scratch_buffer.str, scratch_buffer.len), type);
}

void scratch_buffer_append_native_safe_path(const char *data, int len)
{
#if PLATFORM_WINDOWS
	scratch_buffer_append("\"");
	for (int i = 0; i < len; i++)
	{
		char c = data[i];
		switch (c)
		{
			case '/':
			case '\\':
				scratch_buffer_append("\\");
				break;
			default:
				scratch_buffer_append_char(c);
				break;
		}
	}
	scratch_buffer_append("\"");
#else
	scratch_buffer_append_len(data, len);
#endif
}

File *compile_and_invoke(const char *file, const char *args, const char *stdin_data, size_t limit)
{
	char *name;
	if (!file_namesplit(compiler_exe_name, &name, NULL))
	{
		error_exit("Failed to extract file name from '%s'", compiler_exe_name);
	}
	const char *compiler_path = file_append_path(find_executable_path(), name);

	scratch_buffer_clear();
#if PLATFORM_WINDOWS
	scratch_buffer_append_char('"');
#endif
	scratch_buffer_append_native_safe_path(compiler_path, (int)strlen(compiler_path));
	const char *output = "__c3exec__";
	scratch_buffer_append(" compile -g0 --single-module=yes");
	StringSlice slice = slice_from_string(file);
	while (slice.len > 0)
	{
		StringSlice file_name = slice_next_token(&slice, ';');
		if (!file_name.len) continue;
		scratch_buffer_append(" ");
		scratch_buffer_append_native_safe_path(file_name.ptr, (int)file_name.len);
	}
	scratch_buffer_printf(" -o %s", output);
	char *out;
#if PLATFORM_WINDOWS
	scratch_buffer_append_char('"');
#endif
	if (!execute_cmd_failable(scratch_buffer_to_string(), &out, NULL, limit))
	{
		if (strlen(out))
		{
			eprintf("+-- Script compilation output ---------+\n");
			eprintf("%s\n", out);
			eprintf("+--------------------------------------+\n");
		}
		error_exit("Failed to compile script '%s'.", file);
	}
	DEBUG_LOG("EXEC OUT: %s", out);
	scratch_buffer_clear();
#if (!PLATFORM_WINDOWS)
	scratch_buffer_append("./");
#endif
	scratch_buffer_append(output);
	scratch_buffer_append(" ");
	scratch_buffer_append(args);
	if (!execute_cmd_failable(scratch_buffer_to_string(), &out, stdin_data, limit))
	{
		if (strlen(out))
		{
			eprintf("+-- Script output ---------------------+\n");
			eprintf("%s\n", out);
			eprintf("+--------------------------------------+\n");
		}
		error_exit("Error invoking script '%s' with arguments %s.", file, args);
	}
	file_delete_file(output);
	return source_file_text_load(file, out);
}

const char *default_c_compiler(void)
{
	static const char *cc = NULL;
	if (cc) return cc;
	const char *cc_env = getenv("C3C_CC");
	if (cc_env && strlen(cc_env) > 0)
	{
		INFO_LOG("Setting CC to %s from environment variable 'C3C_CC'.", cc_env);
		cc = strdup(cc_env);
		return cc;
	}
#if PLATFORM_WINDOWS
	WindowsSDK *sdk = windows_get_sdk();

	if (sdk && sdk->cl_path)
	{
		return cc = sdk->cl_path;
	}
	return cc = "cl.exe";
#else
	return cc = "cc";
#endif
}

static bool is_posix(OsType os)
{
	switch (os)
	{
		case OS_TYPE_IOS:
		case OS_TYPE_MACOSX:
		case OS_TYPE_WATCHOS:
		case OS_TYPE_TVOS:
		case OS_TYPE_NETBSD:
		case OS_TYPE_LINUX:
		case OS_TYPE_KFREEBSD:
		case OS_TYPE_FREEBSD:
		case OS_TYPE_OPENBSD:
		case OS_TYPE_SOLARIS:
		case OS_TYPE_ANDROID:
			return true;
		case OS_TYPE_WIN32:
		case OS_TYPE_WASI:
		case OS_TYPE_EMSCRIPTEN:
			return false;
		default:
			return false;
	}
}
void print_build_env(void)
{
	char path[PATH_MAX + 1];
	printf("Version           : %s\n", COMPILER_VERSION);
	printf("Stdlib            : %s\n", compiler.context.lib_dir);
	printf("Exe name          : %s\n", compiler_exe_name);
	printf("Base path         : %s\n", getcwd(path, PATH_MAX));
	if (compiler.build.name && !compiler.build.is_non_project)
	{
		printf("Target name       : %s\n", compiler.build.name);
	}
	printf("Output name       : %s\n", compiler.build.output_name);
	printf("System path       : %s\n", getenv("PATH"));
	printf("Arch/OS target    : %s\n", arch_os_target[compiler.build.arch_os_target]);
	printf("env::POSIX        : %s\n", link_libc() && is_posix(compiler.platform.os) ? "true" : "false");
	printf("env::WIN32        : %s\n", compiler.platform.os == OS_TYPE_WIN32 ? "true" : "false");
	printf("env::LIBC         : %s\n", link_libc() ? "true" : "false");
}
