Browse Source

Add `-show-timings`; Clean up polymorphic procedure code a bit

Ginger Bill 8 years ago
parent
commit
773cf5ca08
7 changed files with 370 additions and 272 deletions
  1. 2 3
      src/array.cpp
  2. 1 0
      src/build_settings.cpp
  3. 236 166
      src/check_expr.cpp
  4. 4 4
      src/ir.cpp
  5. 1 1
      src/ir_print.cpp
  6. 116 94
      src/main.cpp
  7. 10 4
      src/timings.cpp

+ 2 - 3
src/array.cpp

@@ -10,12 +10,12 @@ struct Array {
 	isize       capacity;
 
 	T &operator[](isize index) {
-		GB_ASSERT_MSG(0 <= index && index < count, "Index out of bounds");
+		GB_ASSERT_MSG(0 <= index && index < count, "Index %td is out of bounds ranges 0..<%td", index, count);
 		return data[index];
 	}
 
 	T const &operator[](isize index) const {
-		GB_ASSERT_MSG(0 <= index && index < count, "Index out of bounds");
+		GB_ASSERT_MSG(0 <= index && index < count, "Index %td is out of bounds ranges 0..<%td", index, count);
 		return data[index];
 	}
 };
@@ -31,7 +31,6 @@ template <typename T> void     array_reserve     (Array<T> *array, isize capacit
 template <typename T> void     array_resize      (Array<T> *array, isize count);
 template <typename T> void     array_set_capacity(Array<T> *array, isize capacity);
 
-
 template <typename T>
 void array_init(Array<T> *array, gbAllocator a, isize init_capacity) {
 	array->allocator = a;

+ 1 - 0
src/build_settings.cpp

@@ -18,6 +18,7 @@ struct BuildContext {
 	bool   is_dll;
 	bool   generate_docs;
 	i32    optimization_level;
+	bool   show_timings;
 };
 
 

+ 236 - 166
src/check_expr.cpp

@@ -25,6 +25,12 @@ struct CallArgumentData {
 	Type *  result_type;
 };
 
+struct PolyProcData {
+	Entity *      gen_entity;
+	ProcedureInfo proc_info;
+};
+
+
 
 #define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(Checker *c, AstNode *call, Type *proc_type, Entity *entity, Array<Operand> operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data)
 typedef CALL_ARGUMENT_CHECKER(CallArgumentCheckerType);
@@ -52,9 +58,9 @@ void     check_stmt                     (Checker *c, AstNode *node, u32 flags);
 void     check_stmt_list                (Checker *c, Array<AstNode *> stmts, u32 flags);
 void     check_init_constant            (Checker *c, Entity *e, Operand *operand);
 bool     check_representable_as_constant(Checker *c, ExactValue in_value, Type *type, ExactValue *out_value);
+bool     check_procedure_type           (Checker *c, Type *type, AstNode *proc_type_node, Array<Operand> *operands = nullptr);
 CallArgumentData check_call_arguments   (Checker *c, Operand *operand, Type *proc_type, AstNode *call);
 
-
 void error_operand_not_expression(Operand *o) {
 	if (o->mode == Addressing_Type) {
 		gbString err = expr_to_string(o->expr);
@@ -140,11 +146,219 @@ bool check_is_assignable_to_using_subtype(Type *src, Type *dst) {
 	return false;
 }
 
+bool find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Type *type,
+                                            Array<Operand> *param_operands, PolyProcData *poly_proc_data,
+                                            bool check_later) {
+	///////////////////////////////////////////////////////////////////////////////
+	//                                                                           //
+	// TODO CLEANUP(bill): This procedure is very messy and hacky. Clean this!!! //
+	//                                                                           //
+	///////////////////////////////////////////////////////////////////////////////
+
+	if (base_entity == nullptr) {
+		return false;
+	}
+
+	if (!is_type_proc(base_entity->type)) {
+		return false;
+	}
+
+	Type *src = base_type(base_entity->type);
+	Type *dst = nullptr;
+	if (type != nullptr) dst = base_type(type);
+
+	if (param_operands == nullptr) {
+		GB_ASSERT(dst != nullptr);
+	}
+	if (param_operands != nullptr) {
+		GB_ASSERT(dst == nullptr);
+	}
+
+
+	if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) {
+		return false;
+	}
+
+	if (dst != nullptr) {
+		if (dst->Proc.is_polymorphic) {
+			return false;
+		}
+
+		if (dst->Proc.param_count  != src->Proc.param_count ||
+		    dst->Proc.result_count != src->Proc.result_count) {
+		    return false;
+		}
+	}
+
+
+	DeclInfo *old_decl = decl_info_of_entity(&c->info, base_entity);
+	GB_ASSERT(old_decl != nullptr);
+
+	gbAllocator a = heap_allocator();
+
+	Array<Operand> operands = {};
+	if (param_operands) {
+		operands = *param_operands;
+	} else {
+		array_init(&operands, a, dst->Proc.param_count);
+		for (isize i = 0; i < dst->Proc.param_count; i++) {
+			Entity *param = dst->Proc.params->Tuple.variables[i];
+			Operand o = {Addressing_Value};
+			o.type = param->type;
+			array_add(&operands, o);
+		}
+	}
+
+	defer (if (param_operands == nullptr) {
+		array_free(&operands);
+	});
+
+
+	CheckerContext prev_context = c->context;
+	defer (c->context = prev_context);
+
+	Scope *scope = make_scope(base_entity->scope, a);
+	scope->is_proc = true;
+	c->context.scope = scope;
+	c->context.allow_polymorphic_types = true;
+	if (param_operands == nullptr) {
+		c->context.no_polymorphic_errors = false;
+	}
+
+	bool generate_type_again = c->context.no_polymorphic_errors;
+
+	auto *pt = &src->Proc;
+
+	// NOTE(bill): This is slightly memory leaking if the type already exists
+	// Maybe it's better to check with the previous types first?
+	Type *final_proc_type = make_type_proc(c->allocator, scope, nullptr, 0, nullptr, 0, false, pt->calling_convention);
+	bool success = check_procedure_type(c, final_proc_type, pt->node, &operands);
+	if (!success) {
+		return false;
+	}
+
+	auto *found_gen_procs = map_get(&c->info.gen_procs, hash_pointer(base_entity->identifier));
+	if (found_gen_procs) {
+		auto procs = *found_gen_procs;
+		for_array(i, procs) {
+			Entity *other = procs[i];
+			Type *pt = base_type(other->type);
+			if (are_types_identical(pt, final_proc_type)) {
+				if (poly_proc_data) {
+					poly_proc_data->gen_entity = other;
+				}
+				return true;
+			}
+		}
+	}
+
+	if (generate_type_again) {
+		// LEAK TODO(bill): This is technically a memory leak as it has to generate the type twice
+
+		bool prev_no_polymorphic_errors = c->context.no_polymorphic_errors;
+		defer (c->context.no_polymorphic_errors = prev_no_polymorphic_errors);
+		c->context.no_polymorphic_errors = false;
+
+		// NOTE(bill): Reset scope from the failed procedure type
+		scope_reset(scope);
+
+		success = check_procedure_type(c, final_proc_type, pt->node, &operands);
+
+		if (!success) {
+			return false;
+		}
+
+		if (found_gen_procs) {
+			auto procs = *found_gen_procs;
+			for_array(i, procs) {
+				Entity *other = procs[i];
+				Type *pt = base_type(other->type);
+				if (are_types_identical(pt, final_proc_type)) {
+					if (poly_proc_data) {
+						poly_proc_data->gen_entity = other;
+					}
+					return true;
+				}
+			}
+		}
+	}
+
+	AstNode *proc_lit = clone_ast_node(a, old_decl->proc_lit);
+	ast_node(pl, ProcLit, proc_lit);
+	// NOTE(bill): Associate the scope declared above with this procedure declaration's type
+	add_scope(c, pl->type, final_proc_type->Proc.scope);
+	final_proc_type->Proc.is_poly_specialized = true;
+	final_proc_type->Proc.is_polymorphic = true;
+
+	u64 tags = base_entity->Procedure.tags;
+	AstNode *ident = clone_ast_node(a, base_entity->identifier);
+	Token token = ident->Ident.token;
+	DeclInfo *d = make_declaration_info(c->allocator, scope, old_decl->parent);
+	d->gen_proc_type = final_proc_type;
+	d->type_expr = pl->type;
+	d->proc_lit = proc_lit;
+
+
+	Entity *entity = make_entity_procedure(c->allocator, nullptr, token, final_proc_type, tags);
+	entity->identifier = ident;
+
+	add_entity_and_decl_info(c, ident, entity, d);
+	// NOTE(bill): Set the scope afterwards as this is not real overloading
+	entity->scope = scope->parent;
+
+	AstFile *file = nullptr;
+	{
+		Scope *s = entity->scope;
+		while (s != nullptr && s->file == nullptr) {
+			s = s->parent;
+		}
+		file = s->file;
+	}
+
+	ProcedureInfo proc_info = {};
+	proc_info.file  = file;
+	proc_info.token = token;
+	proc_info.decl  = d;
+	proc_info.type  = final_proc_type;
+	proc_info.body  = pl->body;
+	proc_info.tags  = tags;
+	proc_info.generated_from_polymorphic = true;
+
+	if (found_gen_procs) {
+		array_add(found_gen_procs, entity);
+	} else {
+		Array<Entity *> array = {};
+		array_init(&array, heap_allocator());
+		array_add(&array, entity);
+		map_set(&c->info.gen_procs, hash_pointer(base_entity->identifier), array);
+	}
+
+	GB_ASSERT(entity != nullptr);
+
+
+	if (poly_proc_data) {
+		poly_proc_data->gen_entity = entity;
+		poly_proc_data->proc_info  = proc_info;
+	}
+
+	if (check_later) {
+		// NOTE(bill): Check the newly generated procedure body
+		check_procedure_later(c, proc_info);
+	}
+
+	return true;
+}
+
+bool check_polymorphic_procedure_assignment(Checker *c, Operand *operand, Type *type, PolyProcData *poly_proc_data) {
+	Entity *base_entity = entity_of_ident(&c->info, operand->expr);
+	if (base_entity == nullptr) return false;
+	return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_proc_data, true);
+}
+
+bool find_or_generate_polymorphic_procedure_from_parameters(Checker *c, Entity *base_entity, Array<Operand> *operands, PolyProcData *poly_proc_data) {
+	return find_or_generate_polymorphic_procedure(c, base_entity, nullptr, operands, poly_proc_data, false);
+}
 
-// IMPORTANT TODO(bill): figure out the exact distance rules
-// -1 is not convertable
-// 0 is exact
-// >0 is convertable
 
 i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 	if (operand->mode == Addressing_Invalid ||
@@ -290,6 +504,11 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 		if (are_types_identical(src, dst)) {
 			return 3;
 		}
+		PolyProcData poly_proc_data = {};
+		if (check_polymorphic_procedure_assignment(c, operand, type, &poly_proc_data)) {
+			add_entity_use(c, operand->expr, poly_proc_data.gen_entity);
+			return 4;
+		}
 	}
 
 	if (is_type_vector(dst)) {
@@ -1737,7 +1956,7 @@ bool abi_compat_return_by_value(gbAllocator a, ProcCallingConvention cc, Type *a
 }
 
 // NOTE(bill): `operands` is for generating non generic procedure type
-bool check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node, Array<Operand> *operands = nullptr) {
+bool check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node, Array<Operand> *operands) {
 	ast_node(pt, ProcType, proc_type_node);
 
 	bool variadic = false;
@@ -5151,155 +5370,8 @@ bool check_unpack_arguments(Checker *c, isize lhs_count, Array<Operand> *operand
 	return optional_ok;
 }
 
-// NOTE(bill): Returns `nullptr` on failure
-Entity *find_or_generate_polymorphic_procedure(Checker *c, AstNode *call, Entity *base_entity, CallArgumentCheckerType *call_checker,
-                                               Array<Operand> *operands, ProcedureInfo *proc_info_) {
-	///////////////////////////////////////////////////////////////////////////////
-	//                                                                           //
-	// TODO CLEANUP(bill): This procedure is very messy and hacky. Clean this!!! //
-	//                                                                           //
-	///////////////////////////////////////////////////////////////////////////////
-	if (base_entity == nullptr) {
-		return nullptr;
-	}
-
-	if (!is_type_proc(base_entity->type)) {
-		return nullptr;
-	}
-
-	TypeProc *pt = &base_type(base_entity->type)->Proc;
-	if (!pt->is_polymorphic || pt->is_poly_specialized) {
-		return nullptr;
-	}
-
-	DeclInfo *old_decl = decl_info_of_entity(&c->info, base_entity);
-	GB_ASSERT(old_decl != nullptr);
-
-	gbAllocator a = heap_allocator();
-
-	CheckerContext prev_context = c->context;
-	defer (c->context = prev_context);
-
-	Scope *scope = make_scope(base_entity->scope, a);
-	scope->is_proc = true;
-	c->context.scope = scope;
-	c->context.allow_polymorphic_types = true;
-
-	bool generate_type_again = c->context.no_polymorphic_errors;
-
-	// NOTE(bill): This is slightly memory leaking if the type already exists
-	// Maybe it's better to check with the previous types first?
-	Type *final_proc_type = make_type_proc(c->allocator, scope, nullptr, 0, nullptr, 0, false, pt->calling_convention);
-	bool success = check_procedure_type(c, final_proc_type, pt->node, operands);
-	if (!success) {
-		ProcedureInfo proc_info = {};
-		if (proc_info_) *proc_info_ = proc_info;
-		return nullptr;
-	}
-
-	auto *found_gen_procs = map_get(&c->info.gen_procs, hash_pointer(base_entity->identifier));
-	if (found_gen_procs) {
-		auto procs = *found_gen_procs;
-		for_array(i, procs) {
-			Entity *other = procs[i];
-			Type *pt = base_type(other->type);
-			if (are_types_identical(pt, final_proc_type)) {
-				// NOTE(bill): This scope is not needed any more, destroy it
-				// destroy_scope(scope);
-				return other;
-			}
-		}
-	}
-
-	if (generate_type_again) {
-		// LEAK TODO(bill): This is technically a memory leak as it has to generate the type twice
-
-		bool prev_no_polymorphic_errors = c->context.no_polymorphic_errors;
-		defer (c->context.no_polymorphic_errors = prev_no_polymorphic_errors);
-		c->context.no_polymorphic_errors = false;
-
-		// NOTE(bill): Reset scope from the failed procedure type
-		scope_reset(scope);
-
-		success = check_procedure_type(c, final_proc_type, pt->node, operands);
-
-		if (!success) {
-			ProcedureInfo proc_info = {};
-			if (proc_info_) *proc_info_ = proc_info;
-			return nullptr;
-		}
-
-		if (found_gen_procs) {
-			auto procs = *found_gen_procs;
-			for_array(i, procs) {
-				Entity *other = procs[i];
-				Type *pt = base_type(other->type);
-				if (are_types_identical(pt, final_proc_type)) {
-					// NOTE(bill): This scope is not needed any more, destroy it
-					// destroy_scope(scope);
-					return other;
-				}
-			}
-		}
-	}
-
-	AstNode *proc_lit = clone_ast_node(a, old_decl->proc_lit);
-	ast_node(pl, ProcLit, proc_lit);
-	// NOTE(bill): Associate the scope declared above with this procedure declaration's type
-	add_scope(c, pl->type, final_proc_type->Proc.scope);
-	final_proc_type->Proc.is_poly_specialized = true;
-	final_proc_type->Proc.is_polymorphic = true;
-
-	u64 tags = base_entity->Procedure.tags;
-	AstNode *ident = clone_ast_node(a, base_entity->identifier);
-	Token token = ident->Ident.token;
-	DeclInfo *d = make_declaration_info(c->allocator, scope, old_decl->parent);
-	d->gen_proc_type = final_proc_type;
-	d->type_expr = pl->type;
-	d->proc_lit = proc_lit;
 
 
-	Entity *entity = make_entity_procedure(c->allocator, nullptr, token, final_proc_type, tags);
-	entity->identifier = ident;
-
-	add_entity_and_decl_info(c, ident, entity, d);
-	// NOTE(bill): Set the scope afterwards as this is not real overloading
-	entity->scope = scope->parent;
-
-	AstFile *file = nullptr;
-	{
-		Scope *s = entity->scope;
-		while (s != nullptr && s->file == nullptr) {
-			s = s->parent;
-		}
-		file = s->file;
-	}
-
-	ProcedureInfo proc_info = {};
-	proc_info.file  = file;
-	proc_info.token = token;
-	proc_info.decl  = d;
-	proc_info.type  = final_proc_type;
-	proc_info.body  = pl->body;
-	proc_info.tags  = tags;
-	proc_info.generated_from_polymorphic = true;
-
-	if (found_gen_procs) {
-		array_add(found_gen_procs, entity);
-	} else {
-		Array<Entity *> array = {};
-		array_init(&array, heap_allocator());
-		array_add(&array, entity);
-		map_set(&c->info.gen_procs, hash_pointer(base_entity->identifier), array);
-	}
-
-	GB_ASSERT(entity != nullptr);
-
-
-	if (proc_info_) *proc_info_ = proc_info;
-	return entity;
-}
-
 CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 	ast_node(ce, CallExpr, call);
 	GB_ASSERT(is_type_proc(proc_type));
@@ -5386,11 +5458,11 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 			}
 		} else {
 			// NOTE(bill): Generate the procedure type for this generic instance
-			ProcedureInfo proc_info = {};
+			PolyProcData poly_proc_data = {};
 
 			if (pt->is_polymorphic && !pt->is_poly_specialized) {
-				gen_entity = find_or_generate_polymorphic_procedure(c, call, entity, check_call_arguments_internal, &operands, &proc_info);
-				if (gen_entity != nullptr) {
+				if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &operands, &poly_proc_data)) {
+					gen_entity = poly_proc_data.gen_entity;
 					GB_ASSERT(is_type_proc(gen_entity->type));
 					final_proc_type = gen_entity->type;
 				}
@@ -5477,11 +5549,9 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 				gb_printf_err("append %s with score %lld %d\n", type_to_string(final_proc_type), score, err);
 			}
 
-			if (gen_entity != nullptr && err == CallArgumentError_None) {
-				if (proc_info.decl != nullptr) {
-					// NOTE(bill): Check the newly generated procedure body
-					check_procedure_later(c, proc_info);
-				}
+			if (err == CallArgumentError_None && poly_proc_data.proc_info.decl != nullptr) {
+				// NOTE(bill): Check the newly generated procedure body
+				check_procedure_later(c, poly_proc_data.proc_info);
 			}
 		}
 	}
@@ -5622,11 +5692,11 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) {
 
 	Entity *gen_entity = nullptr;
 	if (pt->is_polymorphic && !pt->is_poly_specialized && err == CallArgumentError_None) {
-		ProcedureInfo proc_info = {};
-		gen_entity = find_or_generate_polymorphic_procedure(c, call, entity, check_named_call_arguments, &ordered_operands, &proc_info);
-		if (gen_entity != nullptr) {
-			if (proc_info.decl != nullptr) {
-				check_procedure_later(c, proc_info);
+		PolyProcData poly_proc_data = {};
+		if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, &poly_proc_data)) {
+			gen_entity = poly_proc_data.gen_entity;
+			if (poly_proc_data.proc_info.decl != nullptr) {
+				check_procedure_later(c, poly_proc_data.proc_info);
 			}
 			Type *gept = base_type(gen_entity->type);
 			GB_ASSERT(is_type_proc(gept));

+ 4 - 4
src/ir.cpp

@@ -1249,8 +1249,8 @@ irValue *ir_add_module_constant(irModule *m, Type *type, ExactValue value) {
 irValue *ir_add_global_string_array(irModule *m, String string) {
 	// TODO(bill): Should this use the arena allocator or the heap allocator?
 	// Strings could be huge!
-	gbAllocator a = m->allocator;
-	// gbAllocator a = gb_heap_allocator();
+	// gbAllocator a = m->allocator;
+	gbAllocator a = gb_heap_allocator();
 
 	isize max_len = 6+8+1;
 	u8 *str = cast(u8 *)gb_alloc_array(a, u8, max_len);
@@ -1260,12 +1260,12 @@ irValue *ir_add_global_string_array(irModule *m, String string) {
 	String name = make_string(str, len-1);
 	Token token = {Token_String};
 	token.string = name;
-	Type *type = make_type_array(a, t_u8, string.len);
+	Type *type = make_type_array(a, t_u8, string.len+1);
 	ExactValue ev = exact_value_string(string);
 	Entity *entity = make_entity_constant(a, nullptr, token, type, ev);
 	irValue *g = ir_value_global(a, entity, ir_add_module_constant(m, type, ev));
 	g->Global.is_private      = true;
-	// g->Global.is_unnamed_addr = true;
+	g->Global.is_unnamed_addr = true;
 	// g->Global.is_constant = true;
 
 	ir_module_add_value(m, entity, g);

+ 1 - 1
src/ir_print.cpp

@@ -415,7 +415,7 @@ void ir_print_exact_value(irFileBuffer *f, irModule *m, ExactValue value, Type *
 			GB_ASSERT(is_type_array(type));
 			ir_fprintf(f, "c\"");
 			ir_print_escape_string(f, str, false, false);
-			ir_fprintf(f, "\"");
+			ir_fprintf(f, "\\00\"");
 		} else {
 			// HACK NOTE(bill): This is a hack but it works because strings are created at the very end
 			// of the .ll file

+ 116 - 94
src/main.cpp

@@ -1,5 +1,4 @@
 #define USE_CUSTOM_BACKEND 0
-// #define PRINT_TIMINGS
 
 #include "common.cpp"
 #include "timings.cpp"
@@ -171,6 +170,7 @@ enum BuildFlagKind {
 	BuildFlag_Invalid,
 
 	BuildFlag_OptimizationLevel,
+	BuildFlag_ShowTimings,
 
 	BuildFlag_COUNT,
 };
@@ -202,6 +202,9 @@ bool parse_build_flags(Array<String> args) {
 	Array<BuildFlag> build_flags = {};
 	array_init(&build_flags, heap_allocator(), BuildFlag_COUNT);
 	add_flag(&build_flags, BuildFlag_OptimizationLevel, str_lit("opt"), BuildFlagParam_Integer);
+	add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None);
+
+
 
 	Array<String> flag_args = args;
 	flag_args.data  += 3;
@@ -214,105 +217,111 @@ bool parse_build_flags(Array<String> args) {
 		String flag = flag_args[i];
 		if (flag[0] != '-') {
 			gb_printf_err("Invalid flag: %.*s\n", LIT(flag));
-		} else {
-			String name = substring(flag, 1, flag.len);
-			isize end = 0;
-			for (; end < name.len; end++) {
-				if (name[end] == '=') {
-					break;
-				}
-			}
-			name.len = end;
-			String param = substring(flag, 2+end, flag.len);
-
-			bool found = false;
-			for_array(build_flag_index, build_flags) {
-				BuildFlag bf = build_flags[build_flag_index];
-				if (bf.name == name) {
-					found = true;
-					if (set_flags[bf.kind]) {
-						gb_printf_err("Previous flag set: `%.*s`\n", LIT(name));
+			continue;
+		}
+		String name = substring(flag, 1, flag.len);
+		isize end = 0;
+		for (; end < name.len; end++) {
+			if (name[end] == '=') break;
+		}
+		name = substring(name, 0, end);
+		String param = {};
+		if (end < flag.len-1) param = substring(flag, 2+end, flag.len);
+
+		bool found = false;
+		for_array(build_flag_index, build_flags) {
+			BuildFlag bf = build_flags[build_flag_index];
+			if (bf.name == name) {
+				found = true;
+				if (set_flags[bf.kind]) {
+					gb_printf_err("Previous flag set: `%.*s`\n", LIT(name));
+					bad_flags = true;
+				} else {
+					ExactValue value = {};
+					bool ok = false;
+					if (bf.param_kind == BuildFlagParam_None) {
+						if (param.len == 0) {
+							ok = true;
+						} else {
+							gb_printf_err("Flag `%.*s` was not expecting a parameter `%.*s`\n", LIT(name), LIT(param));
+							bad_flags = true;
+						}
+					} else if (param.len == 0) {
+						gb_printf_err("Flag missing for `%.*s`\n", LIT(name));
 						bad_flags = true;
 					} else {
-						ExactValue value = {};
-						bool ok = false;
-						if (bf.param_kind == BuildFlagParam_None) {
-							if (param.len == 0) {
-								ok = true;
+						ok = true;
+						switch (bf.param_kind) {
+						default: ok = false; break;
+						case BuildFlagParam_Boolean: {
+							if (param == "t") {
+								value = exact_value_bool(true);
+							} else if (param == "T") {
+								value = exact_value_bool(true);
+							} else if (param == "true") {
+								value = exact_value_bool(true);
+							} else if (param == "TRUE") {
+								value = exact_value_bool(true);
+							} else if (param == "1") {
+								value = exact_value_bool(true);
+							} else if (param == "f") {
+								value = exact_value_bool(false);
+							} else if (param == "F") {
+								value = exact_value_bool(false);
+							} else if (param == "false") {
+								value = exact_value_bool(false);
+							} else if (param == "FALSE") {
+								value = exact_value_bool(false);
+							} else if (param == "0") {
+								value = exact_value_bool(false);
 							} else {
-								gb_printf_err("Flag `%.*s` was not expecting a parameter `%.*s`\n", LIT(name), LIT(param));
-								bad_flags = true;
+								gb_printf_err("Invalid flag parameter for `%.*s` = `%.*s`\n", LIT(name), LIT(param));
 							}
-						} else {
-							if (param.len == 0) {
-								gb_printf_err("Flag missing for `%.*s`\n", LIT(name));
-								bad_flags = true;
+						} break;
+						case BuildFlagParam_Integer:
+							value = exact_value_integer_from_string(param);
+							break;
+						case BuildFlagParam_Float:
+							value = exact_value_float_from_string(param);
+							break;
+						case BuildFlagParam_String:
+							value = exact_value_string(param);
+							break;
+						}
+					}
+					if (ok) {
+						switch (bf.kind) {
+						case BuildFlag_OptimizationLevel:
+							if (value.kind == ExactValue_Integer) {
+								build_context.optimization_level = cast(i32)i128_to_i64(value.value_integer);
 							} else {
-								ok = true;
-								switch (bf.param_kind) {
-								default: ok = false; break;
-								case BuildFlagParam_Boolean: {
-									if (param == "t") {
-										value = exact_value_bool(true);
-									} else if (param == "T") {
-										value = exact_value_bool(true);
-									} else if (param == "true") {
-										value = exact_value_bool(true);
-									} else if (param == "TRUE") {
-										value = exact_value_bool(true);
-									} else if (param == "1") {
-										value = exact_value_bool(true);
-									} else if (param == "f") {
-										value = exact_value_bool(false);
-									} else if (param == "F") {
-										value = exact_value_bool(false);
-									} else if (param == "false") {
-										value = exact_value_bool(false);
-									} else if (param == "FALSE") {
-										value = exact_value_bool(false);
-									} else if (param == "0") {
-										value = exact_value_bool(false);
-									} else {
-										gb_printf_err("Invalid flag parameter for `%.*s` = `%.*s`\n", LIT(name), LIT(param));
-									}
-								} break;
-								case BuildFlagParam_Integer:
-									value = exact_value_integer_from_string(param);
-									break;
-								case BuildFlagParam_Float:
-									value = exact_value_float_from_string(param);
-									break;
-								case BuildFlagParam_String:
-									value = exact_value_string(param);
-									break;
-								}
+								gb_printf_err("%.*s expected an integer, got %.*s", LIT(name), LIT(param));
+								bad_flags = true;
+								ok = false;
 							}
-
-						}
-						if (ok) {
-							switch (bf.kind) {
-							case BuildFlag_OptimizationLevel:
-								if (value.kind == ExactValue_Integer) {
-									build_context.optimization_level = cast(i32)i128_to_i64(value.value_integer);
-								} else {
-									gb_printf_err("%.*s expected an integer, got %.*s", LIT(name), LIT(param));
-									bad_flags = true;
-									ok = false;
-								}
-								break;
+							break;
+						case BuildFlag_ShowTimings:
+							if (value.kind == ExactValue_Invalid) {
+								build_context.show_timings = true;
+							} else {
+								gb_printf_err("%.*s expected no value, got %.*s", LIT(name), LIT(param));
+								bad_flags = true;
+								ok = false;
 							}
+							break;
 						}
 
-
-						set_flags[bf.kind] = ok;
 					}
-					break;
+
+
+					set_flags[bf.kind] = ok;
 				}
+				break;
 			}
-			if (!found) {
-				gb_printf_err("Unknown flag: `%.*s`\n", LIT(name));
-				bad_flags = true;
-			}
+		}
+		if (!found) {
+			gb_printf_err("Unknown flag: `%.*s`\n", LIT(name));
+			bad_flags = true;
 		}
 	}
 
@@ -320,7 +329,20 @@ bool parse_build_flags(Array<String> args) {
 }
 
 
+void show_timings(Checker *c, Timings *t) {
+	Parser *p = c->parser;
+	timings_print_all(t);
+	gb_printf("\n");
+	gb_printf("Total lines:  %td\n", p->total_line_count);
+	gb_printf("Lines/s:      %.3f\n", cast(f64)p->total_line_count/t->total_time_seconds);
+	gb_printf("us/Line:      %.3f\n", 1.0e6*t->total_time_seconds/cast(f64)p->total_line_count);
+	gb_printf("\n");
+	gb_printf("Total tokens: %td\n", p->total_token_count);
+	gb_printf("Tokens/s:     %.3f\n", cast(f64)p->total_token_count/t->total_time_seconds);
+	gb_printf("us/Token:     %.3f\n\n", 1.0e6*t->total_time_seconds/cast(f64)p->total_token_count);
+	gb_printf("\n");
 
+}
 
 int main(int arg_count, char **arg_ptr) {
 	if (arg_count < 2) {
@@ -556,9 +578,9 @@ int main(int arg_count, char **arg_ptr) {
 			return exit_code;
 		}
 
-	#if defined(PRINT_TIMINGS)
-		timings_print_all(&timings);
-	#endif
+		if (build_context.show_timings) {
+			show_timings(&checker, &timings);
+		}
 
 
 		if (run_output) {
@@ -662,9 +684,9 @@ int main(int arg_count, char **arg_ptr) {
 			return exit_code;
 		}
 
-	#if defined(PRINT_TIMINGS)
-		timings_print_all(&timings);
-	#endif
+		if (build_context.show_timings) {
+			show_timings(&checker, &timings);
+		}
 
 		if (run_output) {
 			system_exec_command_line_app("odin run", false, "%.*s", LIT(output_base));

+ 10 - 4
src/timings.cpp

@@ -8,6 +8,7 @@ struct Timings {
 	TimeStamp        total;
 	Array<TimeStamp> sections;
 	u64              freq;
+	f64              total_time_seconds;
 };
 
 
@@ -123,16 +124,21 @@ void timings_print_all(Timings *t) {
 
 	GB_ASSERT(max_len <= gb_size_of(SPACES)-1);
 
-	gb_printf("%.*s%.*s - %.3f ms\n",
+	t->total_time_seconds = cast(f64)(t->total.finish - t->total.start) / cast(f64)t->freq;
+
+	f64 total_ms = time_stamp_as_ms(t->total, t->freq);
+
+	gb_printf("%.*s%.*s - % 9.3f ms\n",
 	          LIT(t->total.label),
 	          cast(int)(max_len-t->total.label.len), SPACES,
-	          time_stamp_as_ms(t->total, t->freq));
+	          total_ms);
 
 	for_array(i, t->sections) {
 		TimeStamp ts = t->sections[i];
-		gb_printf("%.*s%.*s - %.3f ms\n",
+		f64 section_ms = time_stamp_as_ms(ts, t->freq);
+		gb_printf("%.*s%.*s - % 9.3f ms - %5.2f%%\n",
 		          LIT(ts.label),
 	              cast(int)(max_len-ts.label.len), SPACES,
-		          time_stamp_as_ms(ts, t->freq));
+		          section_ms, 100*section_ms/total_ms);
 	}
 }