Files
c3c/src/compiler/compiler.c
Manuel Barrio Linares de8a733c77 Implement lazy-loaded libcurl
- Uses `dlopen` to load libcurl at runtime for better portability.
- Replaces the FETCH_AVAILABLE macro with download_available() check.
- Provides descriptive error messages when libcurl is missing.
2026-02-10 13:05:43 +01:00

1825 lines
50 KiB
C

// 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 <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;
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();
// 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);
}
switch (compiler.build.arch_os_target)
{
case WINDOWS_AARCH64:
case WINDOWS_X64:
case MINGW_X64:
return str_cat(name, ".dll");
case MACOS_X64:
case MACOS_AARCH64:
return str_cat(name, ".dylib");
default:
return str_cat(name, ".so");
}
}
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(" * Ast memory use: %llukb (%u elements)\n",
(unsigned long long)ast_arena.allocated / 1024,
(unsigned)(ast_arena.allocated / sizeof(Ast)));
printf(" * Decl memory use: %llukb (%u elements)\n",
(unsigned long long)decl_arena.allocated / 1024,
(unsigned)(decl_arena.allocated / sizeof(Decl)));
printf(" * Expr memory use: %llukb (%u elements)\n",
(unsigned long long)expr_arena.allocated / 1024,
(unsigned)(expr_arena.allocated / sizeof(Expr)));
printf(" * TypeInfo memory use: %llukb (%u elements)\n",
(unsigned long long)type_info_arena.allocated / 1024,
(unsigned)(type_info_arena.allocated / sizeof(TypeInfo)));
}
ast_arena_free();
decl_arena_free();
expr_arena_free();
type_info_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;
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)
{
eprintf("System linker is not supported, defaulting to built-in linker\n");
break;
}
use_system_linker = true;
break;
case LINKER_TYPE_BUILTIN:
use_system_linker = false;
break;
default:
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(INVALID_SPAN, 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(INVALID_SPAN, value));
}
static void setup_bool_define(const char *id, bool value)
{
setup_define(id, expr_new_const_bool(INVALID_SPAN, 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)
{
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);
return error;
}
#define PROGRESS_BAR_LENGTH 35
void update_progress_bar(const char* lib, int current_step, int total_steps)
{
float progress = (float)current_step / (float)total_steps;
int filled_length = (int)(progress * PROGRESS_BAR_LENGTH);
printf("\033[2K%-10s ", lib);
printf("[");
for (int i = 0; i < PROGRESS_BAR_LENGTH; i++)
{
printf(i < filled_length ? "=" : " ");
}
printf("] %d%%\r", (int)(progress * 100));
(void)fflush(stdout);
}
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);
for(int i = 0; i < total_libraries; i++)
{
const char *lib = options->libraries_to_fetch[i];
if (!ansi || total_libraries == 1)
{
printf("Fetching library '%s'...", lib);
(void)fflush(stdout);
}
else
{
update_progress_bar(lib, i, total_libraries);
}
const char *error = vendor_fetch_single(lib, options->vendor_download_path);
if (!error)
{
if (!ansi || total_libraries == 1)
{
puts("finished.");
}
else
{
update_progress_bar(lib, i + 1, total_libraries);
}
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->span = INVALID_SPAN;
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, INVALID_SPAN);
Expr *expr_emails = expr_new(EXPR_CONST, INVALID_SPAN);
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(INVALID_SPAN, entry.author); // NOLINT
Expr *const_email = expr_new_const_string(INVALID_SPAN, 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 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) { INVALID_SPAN, 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:
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");
}