Compare commits

...

5 Commits

Author SHA1 Message Date
Christoffer Lerno
82c3facb65 --obj, --emit-stdlib, --strip-unused 2023-06-09 09:37:07 +02:00
Christoffer Lerno
266dba466c Rename to no-emit-stdlib 2023-06-06 15:25:25 +02:00
Christoffer Lerno
379a5f670f Add no-obj and no-stdlib-codegen options. 2023-06-06 15:22:28 +02:00
Christoffer Lerno
8eaad81800 Dead strip by default. Add list to_string. Fix missing check for dynamic calls. 2023-06-05 14:54:17 +02:00
Christoffer Lerno
4baacc7d52 Formatting. 2023-06-03 12:08:11 +02:00
18 changed files with 258 additions and 44 deletions

View File

@@ -184,7 +184,7 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [16]
llvm_version: [16, 17]
steps:
- uses: actions/checkout@v3

View File

@@ -38,6 +38,26 @@ fn void List.tinit(List* list, usz initial_capacity = 16)
list.init(initial_capacity, mem::temp()) @inline;
}
fn String List.to_string(List* list, Allocator* using = mem::heap()) @dynamic
{
if (!list.size) return "[]".copy(using);
if (list.size == 1) return string::printf("[%s]", list.entries[0], .using = using);
@stack_mem(512 + 128; Allocator* mem)
{
DString str;
str.init(512, mem);
str.append("[");
foreach (i, element : list.entries[:list.size])
{
if (i != 0) str.append(", ");
str.printf("%s", element);
}
str.printf("]");
return str.copy_str(using);
};
}
fn void List.push(List* list, Type element) @inline
{
list.append(element);

View File

@@ -135,9 +135,9 @@ macro bool! Formatter.print_with_function(Formatter* this, any arg)
this.width = old_width;
this.prec = old_prec;
}
@pool()
@stack_mem(512; Allocator* mem)
{
this.out_substr(arg.to_string(mem::temp()))!;
this.out_substr(arg.to_string(mem))!;
return true;
};
}

View File

@@ -50,18 +50,18 @@ fn void! InetAddress.to_format(InetAddress* addr, Formatter* formatter) @dynamic
formatter.printf("%d.%d.%d.%d", addr.ipv4.a, addr.ipv4.b, addr.ipv4.c, addr.ipv4.d)!;
}
fn String! InetAddress.to_string(InetAddress* addr, Allocator* using = mem::heap()) @dynamic
fn String InetAddress.to_string(InetAddress* addr, Allocator* using = mem::heap()) @dynamic
{
if (addr.is_ipv6)
{
char[8 * 5 + 1] buffer;
String res = (String)io::bprintf(&buffer, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
addr.ipv6.a, addr.ipv6.b, addr.ipv6.c, addr.ipv6.d,
addr.ipv6.e, addr.ipv6.f, addr.ipv6.g, addr.ipv6.h)!;
addr.ipv6.e, addr.ipv6.f, addr.ipv6.g, addr.ipv6.h)!!;
return res.copy(using);
}
char[3 * 4 + 3 + 1] buffer;
String res = (String)io::bprintf(&buffer, "%d.%d.%d.%d", addr.ipv4.a, addr.ipv4.b, addr.ipv4.c, addr.ipv4.d)!;
String res = (String)io::bprintf(&buffer, "%d.%d.%d.%d", addr.ipv4.a, addr.ipv4.b, addr.ipv4.c, addr.ipv4.d)!!;
return res.copy(using);
}

View File

@@ -114,6 +114,10 @@ static void usage(void)
OUTPUT(" --emit-llvm - Emit LLVM IR as a .ll file per module.");
OUTPUT(" --asm-out <dir> - Override asm output directory for '--emit-asm'.");
OUTPUT(" --emit-asm - Emit asm as a .s file per module.");
OUTPUT(" --obj - Emit object files. (Enabled by default)");
OUTPUT(" --no-obj - Do not output object files, this is only valid for `compile-only`.");
OUTPUT(" --emit-stdlib - Output files for the standard library. (Enabled by default)");
OUTPUT(" --no-emit-stdlib - Do not output object files (nor asm or ir) for the standard library.");
OUTPUT(" --target <target> - Compile for a particular architecture + OS target.");
OUTPUT(" --threads <number> - Set the number of threads to use for compilation.");
OUTPUT(" --safe - Set mode to 'safe', generating runtime traps on overflows and contract violations.");
@@ -134,7 +138,8 @@ static void usage(void)
OUTPUT(" --x86vec=<option> - Set max type of vector use: none, mmx, sse, avx, avx512, native.");
OUTPUT(" --riscvfloat=<option> - Set type of RISC-V float support: none, float, double");
OUTPUT(" --memory-env=<option> - Set the memory environment: normal, small, tiny, none.");
OUTPUT(" --strip-unused - Strip unused code and globals from the output (experimental)");
OUTPUT(" --strip-unused - Strip unused code and globals from the output. (Enabled by default)");
OUTPUT(" --no-strip-unused - Do not strip unused code and globals from the output.");
OUTPUT("");
OUTPUT(" --debug-stats - Print debug statistics.");
#ifndef NDEBUG
@@ -549,9 +554,14 @@ static void parse_option(BuildOptions *options)
print_version();
exit_compiler(COMPILER_SUCCESS_EXIT);
}
if (match_longopt("no-strip-unused"))
{
options->no_strip_unused = true;
return;
}
if (match_longopt("strip-unused"))
{
options->strip_unused = true;
options->no_strip_unused = false;
return;
}
if ((argopt = match_argopt("x86vec")))
@@ -585,6 +595,26 @@ static void parse_option(BuildOptions *options)
OUTPUT("C3 is low level programming language based on C.");
exit_compiler(COMPILER_SUCCESS_EXIT);
}
if (match_longopt("no-obj"))
{
options->no_obj = true;
return;
}
if (match_longopt("obj"))
{
options->no_obj = false;
return;
}
if (match_longopt("no-emit-stdlib"))
{
options->no_emit_stdlib = true;
return;
}
if (match_longopt("emit-stdlib"))
{
options->no_emit_stdlib = false;
return;
}
if (match_longopt("debug-log"))
{
debug_log = true;
@@ -871,7 +901,6 @@ BuildOptions parse_arguments(int argc, const char *argv[])
BuildOptions build_options = {
.path = ".",
.emit_llvm = false,
.emit_bitcode = true,
.optimization_setting_override = OPT_SETTING_NOT_SET,
.debug_info_override = DEBUG_INFO_NOT_SET,
.safe_mode = -1,

View File

@@ -321,11 +321,12 @@ typedef struct BuildOptions_
int safe_mode;
bool emit_llvm;
bool emit_asm;
bool emit_bitcode;
bool test_mode;
bool no_stdlib;
bool no_entry;
bool no_libc;
bool no_obj;
bool no_emit_stdlib;
bool force_linker;
bool read_stdin;
bool print_output;
@@ -340,7 +341,7 @@ typedef struct BuildOptions_
X86CpuSet x86_cpu_set;
RiscvFloatCapability riscv_float_capability;
MemoryEnvironment memory_environment;
bool strip_unused;
bool no_strip_unused;
bool print_keywords;
bool print_attributes;
bool print_builtins;
@@ -414,7 +415,7 @@ typedef struct
bool emit_asm;
bool no_stdlib;
bool no_libc;
bool strip_unused;
bool no_strip_unused;
bool emit_object_files;
bool force_linker;
bool benchmarking;
@@ -422,6 +423,7 @@ typedef struct
bool read_stdin;
bool print_output;
bool no_entry;
bool no_emit_stdlib;
int build_threads;
OptimizationLevel optimization_level;
MemoryEnvironment memory_environment;

View File

@@ -199,7 +199,7 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
{
target->feature.safe_mode = options->safe_mode == 1;
}
if (options->strip_unused) target->strip_unused = true;
if (options->no_strip_unused || options->test_mode) target->no_strip_unused = true;
if (options->memory_environment != MEMORY_ENV_NOT_SET)
{
@@ -302,6 +302,11 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
target->emit_asm = false;
target->emit_object_files = false;
}
if (options->no_obj)
{
target->emit_object_files = false;
}
target->no_emit_stdlib = options->no_emit_stdlib;
for (int i = 0; i < options->lib_dir_count; i++)
{
vec_add(target->libdirs, options->lib_dir[i]);

View File

@@ -484,21 +484,29 @@ void compiler_compile(void)
printf("Program finished with exit code %d.\n", ret);
}
}
if (output_static)
else if (output_static)
{
if (!static_lib_linker(output_static, obj_files, output_file_count))
{
error_exit("Failed to produce static library '%s'.", output_static);
}
compiler_link_time = bench_mark();
compiler_print_bench();
printf("Static library '%s' created.", output_static);
}
if (output_dynamic)
else if (output_dynamic)
{
if (!dynamic_lib_linker(output_dynamic, obj_files, output_file_count))
{
error_exit("Failed to produce static library '%s'.", output_dynamic);
}
printf("Dynamic library '%s' created.", output_dynamic);
compiler_link_time = bench_mark();
compiler_print_bench();
}
else
{
compiler_print_bench();
}
free(obj_files);
}

View File

@@ -286,7 +286,7 @@ static void linker_setup_macos(const char ***args_ref, LinkerType linker_type)
}
add_arg("-arch");
add_arg(arch_to_linker_arch(platform_target.arch));
if (active_target.strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
if (!active_target.no_strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
{
add_arg("-no_exported_symbols");
add_arg("-dead_strip");
@@ -398,9 +398,9 @@ static void linker_setup_linux(const char ***args_ref, LinkerType linker_type)
const char *crt_begin_dir = find_linux_crt_begin();
const char *crt_dir = find_linux_crt();
if (active_target.strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
if (!active_target.no_strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
{
add_arg("-dead_strip");
add_arg("--gc-sections");
}
if (!crt_begin_dir || !crt_dir)
@@ -448,9 +448,9 @@ static void linker_setup_freebsd(const char ***args_ref, LinkerType linker_type)
{
error_exit("Failed to find the C runtime at link time.");
}
if (active_target.strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
if (!active_target.no_strip_unused && active_target.type == TARGET_TYPE_EXECUTABLE)
{
add_arg("-dead_strip");
add_arg("--gc-sections");
}
if (is_pie_pic(platform_target.reloc_model))

View File

@@ -1265,9 +1265,21 @@ LLVMMetadataRef llvm_get_debug_file(GenContext *c, FileId file_id)
return file;
}
static bool module_is_stdlib(Module *module)
{
if (module->name->len < 3) return false;
if (module->name->len == 3 && strcmp(module->name->module, "std") == 0) return true;
if (module->name->len > 5 && memcmp(module->name->module, "std::", 5) == 0) return true;
if (module->name->len == 4 && strcmp(module->name->module, "libc") == 0) return true;
if (module->name->len > 6 && memcmp(module->name->module, "libc::", 6) == 0) return true;
return false;
}
static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context)
{
if (!vec_size(module->units)) return NULL;
if (active_target.no_emit_stdlib && module_is_stdlib(module)) return NULL;
assert(intrinsics_setup);
bool has_elements = false;
@@ -1275,7 +1287,7 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context
gencontext_init(gen_context, module, shared_context);
gencontext_begin_module(gen_context);
bool only_used = active_target.strip_unused && !active_target.testing;
bool only_used = !active_target.no_strip_unused;
FOREACH_BEGIN(CompilationUnit *unit, module->units)
@@ -1296,10 +1308,12 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context
FOREACH_END();
FOREACH_BEGIN(Decl *type_decl, unit->types)
if (only_used && !type_decl->is_live) continue;
llvm_emit_type_decls(gen_context, type_decl);
FOREACH_END();
FOREACH_BEGIN(Decl *enum_decl, unit->enums)
if (only_used && !enum_decl->is_live) continue;
llvm_emit_type_decls(gen_context, enum_decl);
FOREACH_END();
@@ -1384,8 +1398,6 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context
llvm_emit_constructors_and_destructors(gen_context);
// EmitDeferred()
if (llvm_use_debug(gen_context))
{
LLVMDIBuilderFinalize(gen_context->debug.builder);

View File

@@ -2332,6 +2332,7 @@ static inline bool sema_analyse_func(SemaContext *context, Decl *decl)
return false;
}
global_context.test_func = decl;
if (active_target.testing) decl->no_strip = true;
}
bool is_test = decl->func_decl.attr_test;
Signature *sig = &decl->func_decl.signature;

View File

@@ -178,12 +178,14 @@ static void sema_trace_stmt_liveness(Ast *ast)
static void sema_trace_const_initializer_liveness(ConstInitializer *const_init)
{
RETRY:
switch (const_init->kind)
{
case CONST_INIT_ZERO:
return;
case CONST_INIT_ARRAY_VALUE:
UNREACHABLE
const_init = const_init->init_array_value.element;
goto RETRY;
case CONST_INIT_ARRAY_FULL:
{
bool was_modified = false;
@@ -204,8 +206,8 @@ static void sema_trace_const_initializer_liveness(ConstInitializer *const_init)
return;
}
case CONST_INIT_UNION:
sema_trace_const_initializer_liveness(const_init->init_union.element);
return;
const_init = const_init->init_union.element;
goto RETRY;
case CONST_INIT_STRUCT:
{
Decl *decl = const_init->type->decl;
@@ -353,8 +355,16 @@ RETRY:
sema_trace_expr_list_liveness(expr->designated_init_list);
return;
case EXPR_EXPR_BLOCK:
sema_trace_stmt_liveness(astptr(expr->expr_block.first_stmt));
{
AstId current = expr->expr_block.first_stmt;
if (!current) return;
do
{
Ast *value = ast_next(&current);
sema_trace_stmt_liveness(value);
} while (current);
return;
}
case EXPR_IDENTIFIER:
sema_trace_decl_liveness(expr->identifier_expr.decl);
return;
@@ -452,10 +462,15 @@ void sema_trace_liveness(void)
{
sema_trace_decl_liveness(global_context.main);
}
bool keep_tests = active_target.testing;
FOREACH_BEGIN(Decl *function, global_context.method_extensions)
if (function->func_decl.attr_dynamic) function->no_strip = true;
if (function->is_export || function->no_strip) sema_trace_decl_liveness(function);
FOREACH_END();
FOREACH_BEGIN(Module *module, global_context.module_list)
FOREACH_BEGIN(CompilationUnit *unit, module->units)
FOREACH_BEGIN(Decl *function, unit->functions)
if (function->is_export || function->no_strip) sema_trace_decl_liveness(function);
if (function->is_export || function->no_strip || (function->func_decl.attr_test && keep_tests)) sema_trace_decl_liveness(function);
FOREACH_END();
FOREACH_BEGIN(Decl *method, unit->methods)
if (method->is_export || method->no_strip) sema_trace_decl_liveness(method);
@@ -463,26 +478,67 @@ void sema_trace_liveness(void)
FOREACH_BEGIN(Decl *var, unit->vars)
if (var->is_export || var->no_strip) sema_trace_decl_liveness(var);
FOREACH_END();
FOREACH_BEGIN(Decl *method, unit->local_method_extensions)
if (method->is_export || method->no_strip) sema_trace_decl_liveness(method);
FOREACH_END();
FOREACH_BEGIN(Decl *xxlizer, unit->xxlizers)
sema_trace_decl_liveness(xxlizer);
FOREACH_END();
FOREACH_END();
FOREACH_END();
}
INLINE void sema_trace_type_liveness(Type *type)
{
if (!type || !type_is_user_defined(type)) return;
sema_trace_decl_liveness(type->decl);
}
INLINE void sema_trace_decl_dynamic_methods(Decl *decl)
{
Decl **methods = decl->methods;
unsigned method_count = vec_size(methods);
if (!method_count) return;
for (unsigned i = 0; i < method_count; i++)
{
Decl *method = methods[i];
if (!method->func_decl.attr_dynamic) continue;
sema_trace_decl_liveness(method);
}
}
static void sema_trace_decl_liveness(Decl *decl)
{
RETRY:
if (!decl || decl->is_live) return;
decl->is_live = true;
switch (decl->decl_kind)
{
case DECL_POISONED:
case DECL_ATTRIBUTE:
case DECL_BITSTRUCT:
case DECL_TYPEDEF:
if (!decl->typedef_decl.is_func)
{
sema_trace_type_liveness(decl->typedef_decl.type_info->type);
return;
}
FOREACH_BEGIN(Decl *param, decl->typedef_decl.function_signature.params)
sema_trace_decl_liveness(param);
FOREACH_END();
sema_trace_type_liveness(typeinfotype(decl->typedef_decl.function_signature.rtype));
return;
case DECL_DEFINE:
decl = decl->define_decl.alias;
goto RETRY;
case DECL_DISTINCT:
case DECL_ENUM:
case DECL_ENUM_CONSTANT:
case DECL_BITSTRUCT:
case DECL_FAULT:
case DECL_STRUCT:
case DECL_UNION:
sema_trace_decl_dynamic_methods(decl);
return;
case DECL_POISONED:
case DECL_ATTRIBUTE:
case DECL_ENUM_CONSTANT:
case DECL_FAULTVALUE:
return;
case DECL_CT_CASE:
@@ -508,20 +564,27 @@ static void sema_trace_decl_liveness(Decl *decl)
{
case VARDECL_REWRAPPED:
case VARDECL_UNWRAPPED:
return;
break;
case VARDECL_PARAM_EXPR:
case VARDECL_PARAM_CT:
case VARDECL_PARAM_REF:
case VARDECL_PARAM:
sema_trace_type_liveness(decl->type);
if (decl->var.init_expr && decl->var.init_expr->resolve_status == RESOLVE_DONE)
{
sema_trace_expr_liveness(decl->var.init_expr);
}
break;
default:
sema_trace_type_liveness(decl->type);
sema_trace_expr_liveness(decl->var.init_expr);
break;
}
sema_trace_expr_liveness(decl->var.init_expr);
return;
case DECL_INITIALIZE:
case DECL_FINALIZE:
sema_trace_stmt_liveness(astptrzero(decl->xxlizer.init));
return;
case DECL_STRUCT:
case DECL_TYPEDEF:
case DECL_UNION:
return;
case DECL_DECLARRAY:
UNREACHABLE
}

View File

@@ -2927,6 +2927,49 @@ bool sema_analyse_function_body(SemaContext *context, Decl *func)
func->name);
return false;
}
Signature any_sig = any->func_decl.signature;
Signature this_sig = func->func_decl.signature;
Type *any_rtype = typeinfotype(any_sig.rtype);
Type *this_rtype = typeinfotype(this_sig.rtype);
if (any_rtype->canonical != this_rtype->canonical)
{
SEMA_ERROR(type_infoptr(this_sig.rtype), "The prototype method has a return type %s, but this function returns %s, they need to match.",
type_quoted_error_string(any_rtype), type_quoted_error_string(this_rtype));
SEMA_NOTE(type_infoptr(any_sig.rtype), "The interface definition is here.");
return false;
}
Decl **any_params = any_sig.params;
Decl **this_params = this_sig.params;
unsigned any_param_count = vec_size(any_params);
unsigned this_param_count = vec_size(this_params);
if (any_param_count != this_param_count)
{
if (any_param_count > this_param_count)
{
SEMA_ERROR(func, "This function is missing parameters, %d parameters were expected.", any_param_count);
SEMA_NOTE(any_params[this_param_count], "Compare with the interface definition.");
return false;
}
else
{
SEMA_ERROR(this_params[any_param_count], "This function has too many parameters (%d).", this_param_count);
SEMA_NOTE(any, "Compare with the interface, which has only %d parameter%s.",
any_param_count, any_param_count == 1 ? "" : "s");
}
return false;
}
FOREACH_BEGIN_IDX(i, Decl *param, this_params)
if (i == 0) continue;
if (param->type->canonical != any_params[i]->type->canonical)
{
SEMA_ERROR(param->var.type_info, "The prototype argument has type %s, but in this function it has type %s. Please make them match.",
type_quoted_error_string(any_params[i]->type), type_quoted_error_string(param->type));
SEMA_NOTE(any_params[i]->var.type_info, "The interface definition is here.");
return false;
}
FOREACH_END();
func->func_decl.any_prototype = declid(any);
}
Signature *signature = &func->func_decl.signature;

View File

@@ -373,12 +373,13 @@ RESOLVE_LAMBDA:;
if (found_lambda) goto RESOLVE_LAMBDA;
halt_on_error();
if (active_target.strip_unused && !active_target.testing)
assign_panicfn();
if (!active_target.no_strip_unused)
{
sema_trace_liveness();
}
assign_panicfn();
compiler_sema_time = bench_mark();

View File

@@ -25,6 +25,15 @@ static void cleanup()
int main_real(int argc, const char *argv[])
{
printf("------------------------------------------------------------\n"
" PLEASE NOTE, this version of the compiler has enabled dead\n"
" code stripping by default. This functionality has not been\n"
" completely audited, so if you run into any linking error, \n"
" please use --no-strip-unused to disable the feature. \n"
" If possible, file an error here: \n"
" https://github.com/c3lang/c3c/issues\n"
" Thank you!\n"
"------------------------------------------------------------\n");
bench_begin();
// Setjmp will allow us to add things like fuzzing with

View File

@@ -1 +1 @@
#define COMPILER_VERSION "0.4.522"
#define COMPILER_VERSION "0.4.525"

View File

@@ -0,0 +1,21 @@
fn int any.test(void* a, int ag) @interface;
struct Foo { int a; }
fn int! Foo.test(Foo* f) @dynamic { return 1; } // #error: The prototype method has a return type 'int'
struct Foo1 { int a; }
fn int Foo1.test(Foo1* f, int a) @dynamic { return 1; }
struct Foo2 { int a; }
fn int Foo2.test(Foo2* f) @dynamic { return 1; } // #error: This function is missing parameters, 2
struct Foo3 { int a; }
fn int Foo3.test(Foo3* f, double a) @dynamic { return 1; } // #error: The prototype argument has type 'int'
struct Foo4 { int a; }
fn int Foo4.test(Foo4* f, double a, int x) @dynamic { return 1; } // #error: This function has too many parameters

View File

@@ -12,14 +12,14 @@ fn void test_ipv4()
fn void! test_ipv4_to_string()
{
InetAddress a = net::ipv4_from_str("127.0.0.1")!;
assert(a.to_string()! == "127.0.0.1");
assert(a.to_string() == "127.0.0.1");
}
fn void! test_ipv6_to_string()
{
InetAddress a = net::ipv6_from_str("2001:db8::2:1")!;
a.to_string()!;
assert(a.to_string()! == "2001:0db8:0000:0000:0000:0000:0002:0001");
a.to_string();
assert(a.to_string() == "2001:0db8:0000:0000:0000:0000:0002:0001");
assert(net::ipv6_from_str("2001:db8::1").to_string()! == "2001:0db8:0000:0000:0000:0000:0000:0001");
assert(net::ipv6_from_str("::1").to_string()! == "0000:0000:0000:0000:0000:0000:0000:0001");
assert(net::ipv6_from_str("2001::1").to_string()! == "2001:0000:0000:0000:0000:0000:0000:0001");