From ae72b3c5bd80fad917a7e2d78d9945b9f19adb52 Mon Sep 17 00:00:00 2001 From: Ginger Bill Date: Sun, 4 Sep 2016 22:50:17 +0100 Subject: [PATCH] Tagged unions memory layout change; begin demo 002 --- src/checker/expr.cpp | 57 ++++++++++++++---------- src/checker/stmt.cpp | 29 +++++------- src/checker/type.cpp | 6 +-- src/codegen/codegen.cpp | 7 ++- src/codegen/print_llvm.cpp | 2 +- src/codegen/ssa.cpp | 91 +++++++++++++++++++++++--------------- src/main.cpp | 33 +++++++++++++- 7 files changed, 142 insertions(+), 83 deletions(-) diff --git a/src/checker/expr.cpp b/src/checker/expr.cpp index a3ffd6ccc..eff3852ac 100644 --- a/src/checker/expr.cpp +++ b/src/checker/expr.cpp @@ -1934,9 +1934,11 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) switch (id) { case BuiltinProc_new: { // new :: proc(Type) -> ^Type - Type *type = check_type(c, ce->arg_list); - if (type == NULL || type == t_invalid) { - error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `size_of`"); + Operand op = {}; + check_expr_or_type(c, &op, ce->arg_list); + Type *type = op.type; + if (op.mode != Addressing_Type && type == NULL || type == t_invalid) { + error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `new`"); return false; } operand->mode = Addressing_Value; @@ -1944,16 +1946,17 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) } break; case BuiltinProc_new_slice: { // new_slice :: proc(Type, len: int[, cap: int]) -> []Type - Type *type = check_type(c, ce->arg_list); - if (type == NULL || type == t_invalid) { - error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `size_of`"); + Operand op = {}; + check_expr_or_type(c, &op, ce->arg_list); + Type *type = op.type; + if (op.mode != Addressing_Type && type == NULL || type == t_invalid) { + error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `new_slice`"); return false; } AstNode *len = ce->arg_list->next; AstNode *cap = len->next; - Operand op = {}; check_expr(c, &op, len); if (op.mode == Addressing_Invalid) return false; @@ -1967,7 +1970,7 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) } if (cap != NULL) { - check_expr(c, &op, len); + check_expr(c, &op, cap); if (op.mode == Addressing_Invalid) return false; if (!is_type_integer(op.type)) { @@ -2006,7 +2009,9 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) case BuiltinProc_size_of: { // size_of :: proc(Type) -> int - Type *type = check_type(c, ce->arg_list); + Operand op = {}; + check_expr_or_type(c, &op, ce->arg_list); + Type *type = op.type; if (!type) { error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `size_of`"); return false; @@ -2031,7 +2036,9 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) case BuiltinProc_align_of: { // align_of :: proc(Type) -> int - Type *type = check_type(c, ce->arg_list); + Operand op = {}; + check_expr_or_type(c, &op, ce->arg_list); + Type *type = op.type; if (!type) { error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `align_of`"); return false; @@ -2054,18 +2061,22 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) case BuiltinProc_offset_of: { // offset_val :: proc(Type, field) -> int - Type *type = get_base_type(check_type(c, ce->arg_list)); + Operand op = {}; + check_expr_or_type(c, &op, ce->arg_list); + Type *type = get_base_type(op.type); AstNode *field_arg = unparen_expr(ce->arg_list->next); - if (type) { - if (!is_type_struct(type)) { - error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a structure type for `offset_of`"); - return false; - } - if (field_arg == NULL || - field_arg->kind != AstNode_Ident) { - error(&c->error_collector, ast_node_token(field_arg), "Expected an identifier for field argument"); - return false; - } + if (type != NULL) { + error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a type for `offset_of`"); + return false; + } + if (!is_type_struct(type)) { + error(&c->error_collector, ast_node_token(ce->arg_list), "Expected a structure type for `offset_of`"); + return false; + } + if (field_arg == NULL || + field_arg->kind != AstNode_Ident) { + error(&c->error_collector, ast_node_token(field_arg), "Expected an identifier for field argument"); + return false; } @@ -2145,7 +2156,7 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) gbString str = expr_to_string(ce->arg_list); defer (gb_string_free(str)); error(&c->error_collector, ast_node_token(call), - "Static assertion: `%s`", str); + "Compile time assertion: `%s`", str); return true; } if (operand->mode != Addressing_Constant) { @@ -2468,7 +2479,7 @@ b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) } if (cap != NULL) { - check_expr(c, &op, len); + check_expr(c, &op, cap); if (op.mode == Addressing_Invalid) return false; if (!is_type_integer(op.type)) { diff --git a/src/checker/stmt.cpp b/src/checker/stmt.cpp index 78a98a648..75c6ad11d 100644 --- a/src/checker/stmt.cpp +++ b/src/checker/stmt.cpp @@ -1238,22 +1238,10 @@ void check_stmt(Checker *c, AstNode *node, u32 flags) { switch (e->kind) { case Entity_TypeName: { Type *t = get_base_type(e->type); - if (is_type_enum(t)) { + if (is_type_struct(t) || is_type_enum(t)) { for (isize i = 0; i < t->Record.other_field_count; i++) { Entity *f = t->Record.other_fields[i]; Entity *found = scope_insert_entity(c->context.scope, f); - if (found != NULL) { - error(&c->error_collector, us->token, "Namespace collision while `using` `%s` of the constant: %.*s", expr_str, LIT(found->token.string)); - return; - } - f->using_parent = e; - } - } else if (is_type_struct(t)) { - Scope **found = map_get(&c->info.scopes, hash_pointer(t->Record.node)); - GB_ASSERT(found != NULL); - gb_for_array(i, (*found)->elements.entries) { - Entity *f = (*found)->elements.entries[i].value; - Entity *found = scope_insert_entity(c->context.scope, f); if (found != NULL) { error(&c->error_collector, us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string)); return; @@ -1261,10 +1249,17 @@ void check_stmt(Checker *c, AstNode *node, u32 flags) { f->using_parent = e; } } else if (is_type_union(t)) { - Scope **found = map_get(&c->info.scopes, hash_pointer(t->Record.node)); - GB_ASSERT(found != NULL); - gb_for_array(i, (*found)->elements.entries) { - Entity *f = (*found)->elements.entries[i].value; + for (isize i = 0; i < t->Record.field_count; i++) { + Entity *f = t->Record.fields[i]; + Entity *found = scope_insert_entity(c->context.scope, f); + if (found != NULL) { + error(&c->error_collector, us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string)); + return; + } + f->using_parent = e; + } + for (isize i = 0; i < t->Record.other_field_count; i++) { + Entity *f = t->Record.other_fields[i]; Entity *found = scope_insert_entity(c->context.scope, f); if (found != NULL) { error(&c->error_collector, us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string)); diff --git a/src/checker/type.cpp b/src/checker/type.cpp index ed8b16ef1..e2971ed9f 100644 --- a/src/checker/type.cpp +++ b/src/checker/type.cpp @@ -117,8 +117,8 @@ struct Type { // All record types // Theses are arrays - Entity **fields; // Entity_Variable - isize field_count; // == offset_count + Entity **fields; // Entity_Variable (otherwise Entity_TypeName if union) + isize field_count; // == offset_count is struct AstNode *node; // enum only @@ -909,7 +909,7 @@ i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t) { if (max < size) max = size; } - return type_size_of(s, allocator, t_int) + max; + return align_formula(max, s.max_align) + type_size_of(s, allocator, t_int); } break; case TypeRecord_RawUnion: { diff --git a/src/codegen/codegen.cpp b/src/codegen/codegen.cpp index 06b6952d0..a583af2c8 100644 --- a/src/codegen/codegen.cpp +++ b/src/codegen/codegen.cpp @@ -65,7 +65,7 @@ struct ssaGlobalVariable { DeclInfo *decl; }; -void ssa_gen_code(ssaGen *s) { +void ssa_gen_tree(ssaGen *s) { if (v_zero == NULL) { v_zero = ssa_make_value_constant(gb_heap_allocator(), t_int, make_exact_value_integer(0)); v_one = ssa_make_value_constant(gb_heap_allocator(), t_int, make_exact_value_integer(1)); @@ -203,7 +203,10 @@ void ssa_gen_code(ssaGen *s) { // m->layout = make_string("e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"); - ssa_print_llvm_ir(&s->output_file, &s->module); + } +void ssa_gen_ir(ssaGen *s) { + ssa_print_llvm_ir(&s->output_file, &s->module); +} diff --git a/src/codegen/print_llvm.cpp b/src/codegen/print_llvm.cpp index e0ab7edc4..e4e804aa1 100644 --- a/src/codegen/print_llvm.cpp +++ b/src/codegen/print_llvm.cpp @@ -160,7 +160,7 @@ void ssa_print_type(gbFile *f, BaseTypeSizes s, Type *t) { break; case TypeRecord_Union: { i64 size_of_union = type_size_of(s, gb_heap_allocator(), t) - s.word_size; - ssa_fprintf(f, "{i%lld, [%lld x i8]}", word_bits, size_of_union); + ssa_fprintf(f, "{[%lld x i8], i%lld}", size_of_union, word_bits); } break; case TypeRecord_RawUnion: ssa_fprintf(f, "[%lld x i8]", type_size_of(s, gb_heap_allocator(), t)); diff --git a/src/codegen/ssa.cpp b/src/codegen/ssa.cpp index 5d1a05f7a..50d33c366 100644 --- a/src/codegen/ssa.cpp +++ b/src/codegen/ssa.cpp @@ -313,8 +313,8 @@ void ssa_module_init(ssaModule *m, Checker *c) { m->info = &c->info; m->sizes = c->sizes; - map_init(&m->values, m->allocator); - map_init(&m->members, m->allocator); + map_init(&m->values, gb_heap_allocator()); + map_init(&m->members, gb_heap_allocator()); } void ssa_module_destroy(ssaModule *m) { @@ -1394,20 +1394,13 @@ ssaValue *ssa_emit_conv(ssaProcedure *proc, ssaValue *value, Type *t, b32 is_arg gbAllocator allocator = proc->module->allocator; ssaValue *parent = ssa_add_local_generated(proc, t); ssaValue *tag = ssa_make_value_constant(allocator, t_int, make_exact_value_integer(i)); - ssa_emit_store(proc, ssa_emit_struct_gep(proc, parent, v_zero32, t_int), tag); + ssa_emit_store(proc, ssa_emit_struct_gep(proc, parent, v_one32, t_int), tag); - i64 tag_size = proc->module->sizes.word_size; - i64 underlying_array_size = type_size_of(proc->module->sizes, allocator, t); - underlying_array_size -= tag_size; - Type *array_type = make_type_array(allocator, t_u8, underlying_array_size); - Type *array_type_ptr = make_type_pointer(allocator, array_type); - ssaValue *data = ssa_emit_struct_gep(proc, parent, v_one32, array_type_ptr); - data = ssa_array_elem(proc, data); + ssaValue *data = ssa_emit_conv(proc, parent, t_rawptr); Type *tag_type = src_type; - Type *t_u8_ptr = make_type_pointer(allocator, t_u8); Type *tag_type_ptr = make_type_pointer(allocator, tag_type); - ssaValue *underlying = ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_bitcast, data, t_u8_ptr, tag_type_ptr)); + ssaValue *underlying = ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_bitcast, data, t_rawptr, tag_type_ptr)); ssa_emit_store(proc, underlying, value); return ssa_emit_load(proc, parent); @@ -1534,14 +1527,13 @@ ssaValue *ssa_emit_down_cast(ssaProcedure *proc, ssaValue *value, Type *t) { GB_ASSERT(is_type_pointer(ssa_type(value))); gbAllocator allocator = proc->module->allocator; - // String field_name = check_down_cast_name(t, ssa_type(value)); String field_name = check_down_cast_name(t, type_deref(ssa_type(value))); GB_ASSERT(field_name.len > 0); Selection sel = lookup_field(t, field_name, false); Type *t_u8_ptr = make_type_pointer(allocator, t_u8); ssaValue *bytes = ssa_emit_conv(proc, value, t_u8_ptr); - // IMPORTANT TODO(bill): THIS ONLY DOES ONE LAY DEEP!!! FUCKING HELL THIS IS NOT WHAT I SIGNED UP FOR! + // IMPORTANT TODO(bill): THIS ONLY DOES ONE LAYER DEEP!!! FUCKING HELL THIS IS NOT WHAT I SIGNED UP FOR! i64 offset_ = type_offset_of_from_selection(proc->module->sizes, allocator, type_deref(t), sel); ssaValue *offset = ssa_make_value_constant(allocator, t_int, make_exact_value_integer(-offset_)); @@ -1549,6 +1541,32 @@ ssaValue *ssa_emit_down_cast(ssaProcedure *proc, ssaValue *value, Type *t) { return ssa_emit_conv(proc, head, t); } +void ssa_build_cond(ssaProcedure *proc, AstNode *cond, ssaBlock *true_block, ssaBlock *false_block); + +ssaValue *ssa_emit_logical_binary_expr(ssaProcedure *proc, AstNode *expr) { + ast_node(be, BinaryExpr, expr); + ssaBlock *true_ = ssa_add_block(proc, NULL, make_string("logical.cmp.true")); + ssaBlock *false_ = ssa_add_block(proc, NULL, make_string("logical.cmp.false")); + ssaBlock *done = ssa__make_block(proc, NULL, make_string("logical.cmp.done")); + + ssaValue *result = ssa_add_local_generated(proc, t_bool); + ssa_build_cond(proc, expr, true_, false_); + + proc->curr_block = true_; + ssa_emit_store(proc, result, v_true); + ssa_emit_jump(proc, done); + + proc->curr_block = false_; + ssa_emit_store(proc, result, v_false); + ssa_emit_jump(proc, done); + + gb_array_append(proc->blocks, done); + proc->curr_block = done; + + return ssa_emit_load(proc, result); +} + + ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue *tv) { switch (expr->kind) { @@ -1644,6 +1662,10 @@ ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue return ssa_emit_conv(proc, cmp, default_type(tv->type)); } break; + case Token_CmpAnd: + case Token_CmpOr: + return ssa_emit_logical_binary_expr(proc, expr); + case Token_as: ssa_emit_comment(proc, make_string("cast - as")); return ssa_emit_conv(proc, ssa_build_expr(proc, be->left), tv->type); @@ -1917,13 +1939,15 @@ ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue err_len += 2; // HACK(bill): memory leaks u8 *err_str = gb_alloc_array(gb_heap_allocator(), u8, err_len); - isize len = gb_snprintf(cast(char *)err_str, err_len, - "%.*s(%td:%td) Runtime assertion: %s\n", - LIT(pos.file), pos.line, pos.column, expr); + err_len = gb_snprintf(cast(char *)err_str, err_len, + "%.*s(%td:%td) Runtime assertion: %s\n", + LIT(pos.file), pos.line, pos.column, expr); + err_len--; - ssaValue *array = ssa_add_global_string_array(proc, make_exact_value_string(make_string(err_str, len-1))); + ssaValue *array = ssa_add_global_string_array(proc, make_exact_value_string(make_string(err_str, err_len))); ssaValue *elem = ssa_array_elem(proc, array); - ssaValue *string = ssa_emit_load(proc, ssa_emit_string(proc, elem, ssa_array_len(proc, array))); + ssaValue *len = ssa_make_value_constant(proc->module->allocator, t_int, make_exact_value_integer(err_len)); + ssaValue *string = ssa_emit_string(proc, elem, len); ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 1); args[0] = string; @@ -2224,8 +2248,9 @@ ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue ssa_emit_store(proc, addr, args[i]); } - ssaValue *base_elem = ssa_emit_struct_gep(proc, base_array, v_zero32, elem_type); - ssa_emit_store(proc, ssa_emit_struct_gep(proc, slice, v_zero32, elem_ptr_type), base_elem); + ssaValue *base_elem = ssa_emit_struct_gep(proc, base_array, v_zero32, elem_ptr_type); + ssaValue *slice_elem = ssa_emit_struct_gep(proc, slice, v_zero32, elem_ptr_type); + ssa_emit_store(proc, slice_elem, base_elem); ssaValue *len = ssa_make_value_constant(allocator, t_int, make_exact_value_integer(slice_len)); ssa_emit_store(proc, ssa_emit_struct_gep(proc, slice, v_one32, t_int), len); ssa_emit_store(proc, ssa_emit_struct_gep(proc, slice, v_two32, t_int), len); @@ -2425,7 +2450,7 @@ ssaAddr ssa_build_addr(ssaProcedure *proc, AstNode *expr) { } } break; case Type_Pointer: { - ssaValue *array = ssa_emit_load(proc, ssa_build_expr(proc, ie->expr)); + ssaValue *array = ssa_build_expr(proc, ie->expr); elem = ssa_array_elem(proc, array); } break; } @@ -2545,13 +2570,13 @@ void ssa_build_cond(ssaProcedure *proc, AstNode *cond, ssaBlock *true_block, ssa case_ast_node(be, BinaryExpr, cond); if (be->op.kind == Token_CmpAnd) { - ssaBlock *block = ssa_add_block(proc, NULL, make_string("cmp-and")); + ssaBlock *block = ssa_add_block(proc, NULL, make_string("cmp.and")); ssa_build_cond(proc, be->left, block, false_block); proc->curr_block = block; ssa_build_cond(proc, be->right, true_block, false_block); return; } else if (be->op.kind == Token_CmpOr) { - ssaBlock *block = ssa_add_block(proc, NULL, make_string("cmp-or")); + ssaBlock *block = ssa_add_block(proc, NULL, make_string("cmp.or")); ssa_build_cond(proc, be->left, true_block, block); proc->curr_block = block; ssa_build_cond(proc, be->right, true_block, false_block); @@ -3123,18 +3148,12 @@ void ssa_build_stmt(ssaProcedure *proc, AstNode *node) { Type *union_type = type_deref(ssa_type(parent)); GB_ASSERT(is_type_union(union_type)); - ssaValue *tag_index = ssa_emit_struct_gep(proc, parent, v_zero32, make_type_pointer(allocator, t_int)); + ssaValue *tag_index = ssa_emit_struct_gep(proc, parent, v_one32, make_type_pointer(allocator, t_int)); tag_index = ssa_emit_load(proc, tag_index); - i64 tag_size = proc->module->sizes.word_size; - i64 underlying_array_size = type_size_of(proc->module->sizes, allocator, union_type); - underlying_array_size -= tag_size; - Type *array_type = make_type_array(allocator, t_u8, underlying_array_size); - Type *array_type_ptr = make_type_pointer(allocator, array_type); - ssaValue *data = ssa_emit_struct_gep(proc, parent, v_one32, array_type_ptr); - data = ssa_array_elem(proc, data); + ssaValue *data = ssa_emit_conv(proc, parent, t_rawptr); - ssaBlock *done = ssa__make_block(proc, node, make_string("type.match.done")); // NOTE(bill): Append later + ssaBlock *done = ssa__make_block(proc, node, make_string("type-match.done")); // NOTE(bill): Append later ast_node(body, BlockStmt, ms->body); @@ -3157,9 +3176,9 @@ void ssa_build_stmt(ssaProcedure *proc, AstNode *node) { if (body == NULL) { append_body = true; if (cc->list == NULL) { - body = ssa__make_block(proc, clause, make_string("type.match.dflt.body")); + body = ssa__make_block(proc, clause, make_string("type-match.dflt.body")); } else { - body = ssa__make_block(proc, clause, make_string("type.match.case.body")); + body = ssa__make_block(proc, clause, make_string("type-match.case.body")); } } @@ -3189,7 +3208,7 @@ void ssa_build_stmt(ssaProcedure *proc, AstNode *node) { } GB_ASSERT(index != NULL); - ssaBlock *next_cond = ssa__make_block(proc, clause, make_string("type.match.case.next")); + ssaBlock *next_cond = ssa__make_block(proc, clause, make_string("type-match.case.next")); Token eq = {Token_CmpEq}; ssaValue *cond = ssa_emit_comp(proc, eq, tag_index, index); ssa_emit_if(proc, cond, body, next_cond); diff --git a/src/main.cpp b/src/main.cpp index c1caafc53..4dc85bb15 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,9 @@ int main(int argc, char **argv) { return 1; } + u64 start_time, end_time; + start_time = gb_utc_time_now(); + init_universal_scope(); char *init_filename = argv[1]; @@ -64,6 +67,10 @@ int main(int argc, char **argv) { if (parse_files(&parser, init_filename) != ParseFile_None) return 1; + end_time = gb_utc_time_now(); + gb_printf_err("Parser: %lld ms\n", (end_time - start_time)/1000); + start_time = gb_utc_time_now(); + // print_ast(parser.files[0].decls, 0); Checker checker = {}; @@ -73,13 +80,30 @@ int main(int argc, char **argv) { check_parsed_files(&checker); + + // end_time = gb_utc_time_now(); + // gb_printf_err("Checker: %lld ms\n", (end_time - start_time)/1000); + // start_time = gb_utc_time_now(); + #if 1 ssaGen ssa = {}; if (!ssa_gen_init(&ssa, &checker)) return 1; defer (ssa_gen_destroy(&ssa)); - ssa_gen_code(&ssa); + ssa_gen_tree(&ssa); + + // end_time = gb_utc_time_now(); + // gb_printf_err("ssa tree: %lld ms\n", (end_time - start_time)/1000); + // start_time = gb_utc_time_now(); + + // TODO(bill): Speedup writing to file for IR code + ssa_gen_ir(&ssa); + + // end_time = gb_utc_time_now(); + // gb_printf_err("ssa ir: %lld ms\n", (end_time - start_time)/1000); + // start_time = gb_utc_time_now(); + char const *output_name = ssa.output_file.filename; isize base_name_len = gb_path_extension(output_name)-1 - output_name; @@ -93,6 +117,10 @@ int main(int argc, char **argv) { if (exit_code != 0) return exit_code; + // end_time = gb_utc_time_now(); + // gb_printf_err("llvm-opt: %lld ms\n", (end_time - start_time)/1000); + // start_time = gb_utc_time_now(); + gbString lib_str = gb_string_make(gb_heap_allocator(), "-lKernel32.lib"); char lib_str_buf[1024] = {}; gb_for_array(i, parser.system_libraries) { @@ -116,6 +144,9 @@ int main(int argc, char **argv) { if (exit_code != 0) return exit_code; + // end_time = gb_utc_time_now(); + // gb_printf_err("clang: %lld ms\n\n\n", (end_time - start_time)/1000); + if (run_output) { win32_exec_command_line_app("%.*s.exe", cast(int)base_name_len, output_name); }