diff --git a/lib/std/core/runtime.c3 b/lib/std/core/runtime.c3 index 57f43475c..11cfaced1 100644 --- a/lib/std/core/runtime.c3 +++ b/lib/std/core/runtime.c3 @@ -24,6 +24,7 @@ struct SubArrayStruct usz len; } +def BenchmarkFn = fn void!(); def TestFn = fn void!(); struct TestUnit @@ -134,6 +135,16 @@ fn bool __run_default_test_runner() }; } +fn bool __run_default_benchmark_runner() +{ + BenchmarkFn[] fns = $$BENCHMARK_FNS; + String[] names = $$BENCHMARK_NAMES; + foreach (i, benchmark : fns) + { + io::printfn("Benchmark: %s %x.", names[i], fns[i]); + } + return true; +} module std::core::runtime @if(WASM_NOLIBC); diff --git a/src/build/build_options.c b/src/build/build_options.c index 31d662ac2..72b7099c1 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -60,23 +60,25 @@ static void usage(void) OUTPUT(""); OUTPUT("Commands:"); OUTPUT(""); - OUTPUT(" compile [ ...] Compile files without a project into an executable."); - OUTPUT(" init Initialize a new project structure."); - OUTPUT(" build [] Build the target in the current project."); - OUTPUT(" test Run the unit tests in the current project."); - OUTPUT(" clean Clean all build files."); - OUTPUT(" run [] Run (and build if needed) the target in the current project."); - OUTPUT(" dist [] Clean and build a target for distribution."); - OUTPUT(" directives [] Generate documentation for the target."); - OUTPUT(" bench [] Benchmark a target."); - OUTPUT(" clean-run [] Clean, then run the target."); - OUTPUT(" compile-run [ ...] Compile files then immediately run the result."); - OUTPUT(" compile-only [ ...] Compile files but do not perform linking."); - OUTPUT(" compile-test [ ...] Compile files into an executable and run unit tests."); - OUTPUT(" static-lib [ ...] Compile files without a project into a static library."); - OUTPUT(" dynamic-lib [ ...] Compile files without a project into a dynamic library."); - OUTPUT(" headers [ ...] Analyse files and generate C headers for public methods."); - OUTPUT(" vendor-fetch ... Fetches one or more libraries from the vendor collection."); + OUTPUT(" compile [ ...] Compile files without a project into an executable."); + OUTPUT(" init Initialize a new project structure."); + OUTPUT(" build [] Build the target in the current project."); + OUTPUT(" benchmark Run the benchmarks in the current project."); + OUTPUT(" test Run the unit tests in the current project."); + OUTPUT(" clean Clean all build files."); + OUTPUT(" run [] Run (and build if needed) the target in the current project."); + OUTPUT(" dist [] Clean and build a target for distribution."); + OUTPUT(" directives [] Generate documentation for the target."); + OUTPUT(" bench [] Benchmark a target."); + OUTPUT(" clean-run [] Clean, then run the target."); + OUTPUT(" compile-run [ ...] Compile files then immediately run the result."); + OUTPUT(" compile-only [ ...] Compile files but do not perform linking."); + OUTPUT(" compile-benchmark [ ...] Compile files into an executable and run benchmarks."); + OUTPUT(" compile-test [ ...] Compile files into an executable and run unit tests."); + OUTPUT(" static-lib [ ...] Compile files without a project into a static library."); + OUTPUT(" dynamic-lib [ ...] Compile files without a project into a dynamic library."); + OUTPUT(" headers [ ...] Analyse files and generate C headers for public methods."); + OUTPUT(" vendor-fetch ... Fetches one or more libraries from the vendor collection."); OUTPUT(""); OUTPUT("Options:"); OUTPUT(" --tb - Use Tilde Backend for compilation."); @@ -268,6 +270,12 @@ static void parse_command(BuildOptions *options) options->command = COMMAND_UNIT_TEST; return; } + if (arg_match("compile-benchmark")) + { + options->command = COMMAND_COMPILE_BENCHMARK; + options->benchmarking = true; + return; + } if (arg_match("compile-test")) { options->command = COMMAND_COMPILE_TEST; @@ -316,6 +324,12 @@ static void parse_command(BuildOptions *options) parse_optional_target(options); return; } + if (arg_match("benchmark")) + { + options->command = COMMAND_BENCHMARK; + options->benchmarking = true; + return; + } if (arg_match("test")) { options->command = COMMAND_TEST; @@ -913,6 +927,11 @@ static void parse_option(BuildOptions *options) options->lib_dir[options->lib_dir_count++] = check_dir(next_arg()); return; } + if (match_longopt("benchmark")) + { + options->benchmark_mode = true; + return; + } if (match_longopt("test")) { options->test_mode = true; diff --git a/src/build/build_options.h b/src/build/build_options.h index 4c7c5d5b1..235569383 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -25,6 +25,7 @@ typedef enum COMMAND_MISSING = 0, COMMAND_COMPILE, COMMAND_COMPILE_ONLY, + COMMAND_COMPILE_BENCHMARK, COMMAND_COMPILE_TEST, COMMAND_GENERATE_HEADERS, COMMAND_INIT, @@ -39,6 +40,7 @@ typedef enum COMMAND_DIST, COMMAND_DOCS, COMMAND_BENCH, + COMMAND_BENCHMARK, COMMAND_TEST, COMMAND_UNIT_TEST, COMMAND_PRINT_SYNTAX, @@ -332,6 +334,7 @@ typedef struct BuildOptions_ int safe_mode; bool emit_llvm; bool emit_asm; + bool benchmark_mode; bool test_mode; bool no_stdlib; bool no_entry; @@ -374,6 +377,7 @@ typedef enum TARGET_TYPE_STATIC_LIB, TARGET_TYPE_DYNAMIC_LIB, TARGET_TYPE_OBJECT_FILES, + TARGET_TYPE_BENCHMARK, TARGET_TYPE_TEST, } TargetType; @@ -417,7 +421,9 @@ typedef struct const char *ir_file_dir; const char *asm_file_dir; bool run_after_compile; + bool generate_benchmark_runner; bool generate_test_runner; + bool benchmark_output; bool test_output; bool output_headers; bool output_ast; diff --git a/src/build/builder.c b/src/build/builder.c index 4c64b2f71..2f4b3c9fb 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -69,6 +69,7 @@ bool command_is_projectless(CompilerCommand command) case COMMAND_COMPILE_RUN: case COMMAND_DYNAMIC_LIB: case COMMAND_STATIC_LIB: + case COMMAND_COMPILE_BENCHMARK: case COMMAND_COMPILE_TEST: case COMMAND_UNIT_TEST: return true; @@ -83,6 +84,7 @@ bool command_is_projectless(CompilerCommand command) case COMMAND_DOCS: case COMMAND_BENCH: case COMMAND_PRINT_SYNTAX: + case COMMAND_BENCHMARK: case COMMAND_TEST: case COMMAND_VENDOR_FETCH: return false; @@ -152,6 +154,11 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions * { switch (options->command) { + case COMMAND_COMPILE_BENCHMARK: + case COMMAND_BENCHMARK: + target->run_after_compile = true; + target->type = TARGET_TYPE_BENCHMARK; + break; case COMMAND_COMPILE_TEST: case COMMAND_TEST: target->run_after_compile = true; @@ -317,6 +324,13 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions * target->output_ast = true; break; } + if (options->benchmark_mode) + { + target->benchmark_output = true; + target->emit_llvm = false; + target->emit_asm = false; + target->emit_object_files = false; + } if (options->test_mode) { target->test_output = true; diff --git a/src/build/project.c b/src/build/project.c index 6ad1d4ad0..9c25eed2b 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -436,15 +436,17 @@ static void project_add_target(Project *project, BuildTarget *default_target, J static void project_add_targets(Project *project, JSONObject *project_data) { assert(project_data->type == J_OBJECT); - static const char* targets[5] = { [TARGET_TYPE_EXECUTABLE] = "executable", + static const char* targets[6] = { [TARGET_TYPE_EXECUTABLE] = "executable", [TARGET_TYPE_STATIC_LIB] = "static-lib", [TARGET_TYPE_DYNAMIC_LIB] = "dynamic-lib", + [TARGET_TYPE_BENCHMARK] = "benchmark", [TARGET_TYPE_TEST] = "test", [TARGET_TYPE_OBJECT_FILES] = "object-files"}; - static const char *target_desc[5] = { + static const char *target_desc[6] = { [TARGET_TYPE_EXECUTABLE] = "Executable", [TARGET_TYPE_STATIC_LIB] = "Static library", [TARGET_TYPE_DYNAMIC_LIB] = "Dynamic library", + [TARGET_TYPE_BENCHMARK] = "benchmark suite", [TARGET_TYPE_TEST] = "test suite", [TARGET_TYPE_OBJECT_FILES] = "object files"}; diff --git a/src/compiler/codegen_general.c b/src/compiler/codegen_general.c index 40d3b430f..e2a178eb8 100644 --- a/src/compiler/codegen_general.c +++ b/src/compiler/codegen_general.c @@ -1,6 +1,8 @@ #include "codegen_internal.h" +const char *benchmark_fns_var_name = "__$C3_BENCHMARK_FN_LIST"; +const char *benchmark_names_var_name = "__$C3_BENCHMARK_NAMES_LIST"; const char *test_fns_var_name = "__$C3_TEST_FN_LIST"; const char *test_names_var_name = "__$C3_TEST_NAMES_LIST"; /** diff --git a/src/compiler/codegen_internal.h b/src/compiler/codegen_internal.h index 1e06f0681..08bd665e1 100644 --- a/src/compiler/codegen_internal.h +++ b/src/compiler/codegen_internal.h @@ -117,5 +117,7 @@ static inline bool expr_is_vector_index(Expr *expr) const char *codegen_create_asm(Ast *ast); +extern const char *benchmark_fns_var_name; +extern const char *benchmark_names_var_name; extern const char *test_fns_var_name; -extern const char *test_names_var_name; \ No newline at end of file +extern const char *test_names_var_name; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 61ddc8cff..f0e1ee0b5 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -378,10 +378,14 @@ void compiler_compile(void) const char *output_exe = NULL; const char *output_static = NULL; const char *output_dynamic = NULL; - if (!active_target.test_output) + if (!active_target.test_output && !active_target.benchmark_output) { switch (active_target.type) { + case TARGET_TYPE_BENCHMARK: + active_target.name = "benchmarkrun"; + output_exe = exe_name(); + break; case TARGET_TYPE_TEST: active_target.name = "testrun"; output_exe = exe_name(); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 4b58ef9e2..2431d857b 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -546,6 +546,7 @@ typedef struct bool attr_noinline : 1; bool attr_extname : 1; bool attr_naked : 1; + bool attr_benchmark : 1; bool attr_test : 1; bool attr_winmain : 1; bool attr_dynamic : 1; @@ -1180,6 +1181,7 @@ struct Expr_ ExprSubscript subscript_expr; // 12 ExprSwizzle swizzle_expr; ExprTernary ternary_expr; // 16 + BuiltinDefine benchmark_hook_expr; BuiltinDefine test_hook_expr; Expr** try_unwrap_chain_expr; // 8 ExprTryUnwrap try_unwrap_expr; // 24 @@ -1510,6 +1512,7 @@ typedef struct Module_ Module *parent_module; Module *top_module; Module **sub_modules; + Decl **benchmarks; Decl **tests; Decl **lambdas_to_evaluate; const char *generic_suffix; @@ -1600,6 +1603,7 @@ struct CompilationUnit_ Attr *if_attr; bool export_by_default; bool is_interface_file; + bool benchmark_by_default; bool test_by_default; Decl **generic_defines; Decl **ct_asserts; @@ -1712,7 +1716,6 @@ typedef struct const char **sources; File **loaded_sources; bool in_panic_mode : 1; - bool in_test_mode : 1; unsigned errors_found; unsigned warnings_found; unsigned includes_used; @@ -1730,6 +1733,7 @@ typedef struct Decl *io_error_file_not_found; Decl *main; Decl *test_func; + Decl *benchmark_func; Decl *decl_stack[MAX_GLOBAL_DECL_STACK]; Decl **decl_stack_bottom; Decl **decl_stack_top; @@ -1884,6 +1888,7 @@ extern const char *type_property_list[NUMBER_OF_TYPE_PROPERTIES]; extern const char *kw_std__core; extern const char *kw_std__core__types; extern const char *kw_std__io; +extern const char *kw___run_default_benchmark_runner; extern const char *kw___run_default_test_runner; extern const char *kw_typekind; extern const char *kw_FILE_NOT_FOUND; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 2ee13118d..ddeafbf41 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -386,6 +386,7 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) case EXPR_HASH_IDENT: assert(expr->resolve_status != RESOLVE_DONE); return expr; + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: case EXPR_COMPILER_CONST: return expr; diff --git a/src/compiler/diagnostics.c b/src/compiler/diagnostics.c index 21f9d7949..59e49a8ea 100644 --- a/src/compiler/diagnostics.c +++ b/src/compiler/diagnostics.c @@ -20,7 +20,7 @@ typedef enum static void print_error(SourceSpan location, const char *message, PrintType print_type) { File *file = source_file_by_id(location.file_id); - if (active_target.test_output) + if (active_target.test_output || active_target.benchmark_output) { switch (print_type) { diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 79865b586..e84b28a96 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -257,6 +257,7 @@ typedef enum EXPR_SUBSCRIPT_ADDR, EXPR_SUBSCRIPT_ASSIGN, EXPR_TERNARY, + EXPR_BENCHMARK_HOOK, EXPR_TEST_HOOK, EXPR_TRY_UNWRAP, EXPR_TRY_UNWRAP_CHAIN, @@ -769,6 +770,7 @@ typedef enum typedef enum { ATTRIBUTE_ALIGN, + ATTRIBUTE_BENCHMARK, ATTRIBUTE_BIGENDIAN, ATTRIBUTE_BUILTIN, ATTRIBUTE_CALLCONV, @@ -940,6 +942,8 @@ typedef enum BUILTIN_DEF_LINE, BUILTIN_DEF_LINE_RAW, BUILTIN_DEF_MODULE, + BUILTIN_DEF_BENCHMARK_NAMES, + BUILTIN_DEF_BENCHMARK_FNS, BUILTIN_DEF_TEST_NAMES, BUILTIN_DEF_TEST_FNS, BUILTIN_DEF_TIME, diff --git a/src/compiler/expr.c b/src/compiler/expr.c index a038054f2..3ec557349 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -80,6 +80,7 @@ bool expr_may_addr(Expr *expr) case EXPR_SUBSCRIPT: case EXPR_SLICE: return true; + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: return false; case NON_RUNTIME_EXPR: @@ -157,6 +158,7 @@ bool expr_is_constant_eval(Expr *expr, ConstantEvalKind eval_kind) case EXPR_BUILTIN: case EXPR_CT_EVAL: case EXPR_VASPLAT: + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: return false; case EXPR_BITACCESS: @@ -633,6 +635,7 @@ bool expr_is_pure(Expr *expr) switch (expr->expr_kind) { case EXPR_BUILTIN: + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: return false; case EXPR_SWIZZLE: diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 0fe9324c5..da3960cd9 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -74,7 +74,7 @@ static void gencontext_init(GenContext *context, Module *module, LLVMContextRef { LLVMContextSetDiagnosticHandler(context->context, &diagnostics_handler, context); } - if (!active_target.emit_llvm && !active_target.test_output) LLVMContextSetDiscardValueNames(context->context, true); + if (!active_target.emit_llvm && !active_target.test_output && !active_target.benchmark_output ) LLVMContextSetDiscardValueNames(context->context, true); context->code_module = module; } @@ -1129,6 +1129,7 @@ static void llvm_gen_test_main(GenContext *c) LLVMBuildRet(builder, val); LLVMDisposeBuilder(builder); } + INLINE GenContext *llvm_gen_tests(Module** modules, unsigned module_count, LLVMContextRef shared_context) { Path *test_path = path_create_from_string("$test", 5, INVALID_SPAN); @@ -1202,6 +1203,105 @@ INLINE GenContext *llvm_gen_tests(Module** modules, unsigned module_count, LLVMC } return c; } + +static void llvm_gen_benchmark_main(GenContext *c) +{ + Decl *benchmark_runner = global_context.benchmark_func; + if (!benchmark_runner) + { + error_exit("No benchmark runner found."); + } + assert(!global_context.main && "Main should not be set if a benchmark main is generated."); + global_context.main = benchmark_runner; + LLVMTypeRef cint = llvm_get_type(c, type_cint); + LLVMTypeRef main_type = LLVMFunctionType(cint, NULL, 0, true); + LLVMTypeRef runner_type = LLVMFunctionType(c->byte_type, NULL, 0, true); + LLVMValueRef func = LLVMAddFunction(c->module, kw_main, main_type); + LLVMValueRef other_func = LLVMAddFunction(c->module, benchmark_runner->extname, runner_type); + LLVMBasicBlockRef entry = LLVMAppendBasicBlockInContext(c->context, func, "entry"); + LLVMBuilderRef builder = LLVMCreateBuilderInContext(c->context); + LLVMPositionBuilderAtEnd(builder, entry); + LLVMValueRef val = LLVMBuildCall2(builder, runner_type, other_func, NULL, 0, ""); + val = LLVMBuildSelect(builder, LLVMBuildTrunc(builder, val, c->bool_type, ""), + LLVMConstNull(cint), LLVMConstInt(cint, 1, false), ""); + LLVMBuildRet(builder, val); + LLVMDisposeBuilder(builder); +} + +INLINE GenContext *llvm_gen_benchmarks(Module** modules, unsigned module_count, LLVMContextRef shared_context) +{ + Path *benchmark_path = path_create_from_string("$benchmark", 10, INVALID_SPAN); + Module *benchmark_module = compiler_find_or_create_module(benchmark_path, NULL); + + GenContext *c = cmalloc(sizeof(GenContext)); + active_target.debug_info = DEBUG_INFO_NONE; + gencontext_init(c, benchmark_module, shared_context); + gencontext_begin_module(c); + + LLVMValueRef *names = NULL; + LLVMValueRef *decls = NULL; + + LLVMTypeRef opt_benchmark = LLVMFunctionType(llvm_get_type(c, type_anyfault), NULL, 0, false); + for (unsigned i = 0; i < module_count; i++) + { + Module *module = modules[i]; + FOREACH_BEGIN(Decl *benchmark, module->benchmarks) + LLVMValueRef ref; + LLVMTypeRef type = opt_benchmark; + ref = LLVMAddFunction(c->module, benchmark->extname, type); + scratch_buffer_clear(); + scratch_buffer_printf("%s::%s", module->name->module, benchmark->name); + LLVMValueRef name = llvm_emit_string_const(c, scratch_buffer_to_string(), ".benchmark.name"); + vec_add(names, name); + vec_add(decls, ref); + FOREACH_END(); + } + unsigned benchmark_count = vec_size(decls); + LLVMValueRef name_ref; + LLVMValueRef decl_ref; + + if (benchmark_count) + { + LLVMValueRef array_of_names = LLVMConstArray(c->chars_type, names, benchmark_count); + LLVMValueRef array_of_decls = LLVMConstArray(c->ptr_type, decls, benchmark_count); + LLVMTypeRef arr_type = LLVMTypeOf(array_of_names); + name_ref = llvm_add_global_raw(c, ".benchmark_names", arr_type, 0); + decl_ref = llvm_add_global_raw(c, ".benchmark_decls", LLVMTypeOf(array_of_decls), 0); + llvm_set_internal_linkage(name_ref); + llvm_set_internal_linkage(decl_ref); + LLVMSetGlobalConstant(name_ref, 1); + LLVMSetGlobalConstant(decl_ref, 1); + LLVMSetInitializer(name_ref, array_of_names); + LLVMSetInitializer(decl_ref, array_of_decls); + } + else + { + name_ref = LLVMConstNull(c->ptr_type); + decl_ref = LLVMConstNull(c->ptr_type); + } + LLVMValueRef count = llvm_const_int(c, type_usz, benchmark_count); + Type *chars_array = type_get_subarray(type_chars); + LLVMValueRef name_list = llvm_add_global(c, benchmark_names_var_name, chars_array, type_alloca_alignment(chars_array)); + LLVMSetGlobalConstant(name_list, 1); + LLVMSetInitializer(name_list, llvm_emit_aggregate_two(c, chars_array, name_ref, count)); + Type *decls_array_type = type_get_subarray(type_voidptr); + LLVMValueRef decl_list = llvm_add_global(c, benchmark_fns_var_name, decls_array_type, type_alloca_alignment(decls_array_type)); + LLVMSetGlobalConstant(decl_list, 1); + LLVMSetInitializer(decl_list, llvm_emit_aggregate_two(c, decls_array_type, decl_ref, count)); + + if (active_target.type == TARGET_TYPE_BENCHMARK) + { + llvm_gen_benchmark_main(c); + } + + if (llvm_use_debug(c)) + { + LLVMDIBuilderFinalize(c->debug.builder); + LLVMDisposeDIBuilder(c->debug.builder); + } + return c; +} + void **llvm_gen(Module** modules, unsigned module_count) { if (!module_count) return NULL; @@ -1220,6 +1320,10 @@ void **llvm_gen(Module** modules, unsigned module_count) } if (!gen_contexts) return NULL; GenContext *first = gen_contexts[0]; + if (active_target.benchmarking) + { + vec_add(gen_contexts, llvm_gen_benchmarks(modules, module_count, context)); + } if (active_target.testing) { vec_add(gen_contexts, llvm_gen_tests(modules, module_count, context)); @@ -1240,6 +1344,10 @@ void **llvm_gen(Module** modules, unsigned module_count) if (!result) continue; vec_add(gen_contexts, result); } + if (active_target.benchmarking) + { + vec_add(gen_contexts, llvm_gen_benchmarks(modules, module_count, NULL)); + } if (active_target.testing) { vec_add(gen_contexts, llvm_gen_tests(modules, module_count, NULL)); @@ -1324,6 +1432,11 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context if (!active_target.testing) continue; vec_add(module->tests, func); } + if (func->func_decl.attr_benchmark) + { + if (!active_target.benchmarking) continue; + vec_add(module->benchmarks, func); + } llvm_emit_function_decl(gen_context, func); FOREACH_END(); @@ -1334,7 +1447,7 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context llvm_emit_function_decl(gen_context, func); FOREACH_END(); - if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic) + if (active_target.type != TARGET_TYPE_TEST && active_target.type != TARGET_TYPE_BENCHMARK && unit->main_function && unit->main_function->is_synthetic) { has_elements = true; llvm_emit_function_decl(gen_context, unit->main_function); @@ -1363,11 +1476,12 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context FOREACH_BEGIN(Decl *decl, unit->functions) if (decl->func_decl.attr_test && !active_target.testing) continue; + if (decl->func_decl.attr_benchmark && !active_target.benchmarking) continue; if (only_used && !decl->is_live) continue; if (decl->func_decl.body) { has_elements = true; - llvm_emit_function_body(gen_context, decl, decl->func_decl.attr_test ? ST_TEST : ST_FUNCTION); + llvm_emit_function_body(gen_context, decl, decl->func_decl.attr_test ? ST_TEST : decl->func_decl.attr_benchmark ? ST_BENCHMARK : ST_FUNCTION); } FOREACH_END(); @@ -1377,7 +1491,7 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context llvm_emit_function_body(gen_context, func, ST_LAMBDA); FOREACH_END(); - if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic) + if (active_target.type != TARGET_TYPE_TEST && active_target.type != TARGET_TYPE_BENCHMARK && unit->main_function && unit->main_function->is_synthetic) { has_elements = true; llvm_emit_function_body(gen_context, unit->main_function, ST_FUNCTION); @@ -1404,8 +1518,8 @@ static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context LLVMDisposeDIBuilder(gen_context->debug.builder); } - // If it's in test, then we want to serialize the IR before it is optimized. - if (active_target.test_output) + // If it's in test or benchmark, then we want to serialize the IR before it is optimized. + if (active_target.test_output || active_target.benchmark_output) { gencontext_print_llvm_ir(gen_context); gencontext_verify_ir(gen_context); diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 8a7a5bf06..90e6bbf39 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -6605,6 +6605,34 @@ static inline void llvm_emit_builtin_access(GenContext *c, BEValue *be_value, Ex UNREACHABLE } +static LLVMValueRef llvm_get_benchmark_hook_global(GenContext *c, Expr *expr) +{ + const char *name; + switch (expr->benchmark_hook_expr) + { + case BUILTIN_DEF_BENCHMARK_FNS: + name = benchmark_fns_var_name; + break; + case BUILTIN_DEF_BENCHMARK_NAMES: + name = benchmark_names_var_name; + break; + default: + UNREACHABLE + } + LLVMValueRef global = LLVMGetNamedGlobal(c->module, name); + if (global) return global; + global = LLVMAddGlobal(c->module, llvm_get_type(c, expr->type), name); + LLVMSetExternallyInitialized(global, true); + LLVMSetGlobalConstant(global, true); + return global; +} + +static void llmv_emit_benchmark_hook(GenContext *c, BEValue *value, Expr *expr) +{ + LLVMValueRef get_global = llvm_get_benchmark_hook_global(c, expr); + llvm_value_set_address_abi_aligned(value, get_global, expr->type); +} + static LLVMValueRef llvm_get_test_hook_global(GenContext *c, Expr *expr) { const char *name; @@ -6678,6 +6706,9 @@ void llvm_emit_expr(GenContext *c, BEValue *value, Expr *expr) case EXPR_SWIZZLE: llvm_emit_swizzle(c, value, expr); return; + case EXPR_BENCHMARK_HOOK: + llmv_emit_benchmark_hook(c, value, expr); + return; case EXPR_TEST_HOOK: llmv_emit_test_hook(c, value, expr); return; diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index 18b94a7bd..d5c0395db 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -535,6 +535,7 @@ void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *pro case ST_UNKNOWN: UNREACHABLE case ST_FUNCTION: + case ST_BENCHMARK: case ST_TEST: assert(decl->name); function_name = decl->name; diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index f9a7af746..ddb226b5f 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -56,6 +56,7 @@ typedef enum ST_METHOD, ST_MACRO, ST_LAMBDA, + ST_BENCHMARK, ST_TEST, ST_INITIALIZER, ST_FINALIZER diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index e0461760b..a57de1e86 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -273,6 +273,9 @@ bool parse_module(ParseContext *c, AstId contracts) if (c->unit->if_attr) RETURN_SEMA_ERROR(attr, "'@if' appeared more than once."); c->unit->if_attr = attr; continue; + case ATTRIBUTE_BENCHMARK: + c->unit->benchmark_by_default = true; + continue; case ATTRIBUTE_TEST: c->unit->test_by_default = true; continue; @@ -961,6 +964,7 @@ static bool parse_attributes_for_global(ParseContext *c, Decl *decl) { Visibility visibility = c->unit->default_visibility; if (decl->decl_kind == DECL_FUNC) decl->func_decl.attr_test = c->unit->test_by_default; + if (decl->decl_kind == DECL_FUNC) decl->func_decl.attr_benchmark = c->unit->benchmark_by_default; decl->is_export = c->unit->export_by_default; bool is_cond; if (!parse_attributes(c, &decl->attributes, &visibility, &is_cond)) return false; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 7001329c0..3d885c10c 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1682,6 +1682,7 @@ static bool sema_analyse_attribute(SemaContext *context, Decl *decl, Attr *attr, assert(type >= 0 && type < NUMBER_OF_ATTRIBUTES); static AttributeDomain attribute_domain[NUMBER_OF_ATTRIBUTES] = { [ATTRIBUTE_ALIGN] = ATTR_FUNC | ATTR_CONST | ATTR_LOCAL | ATTR_GLOBAL | ATTR_BITSTRUCT | ATTR_STRUCT | ATTR_UNION | ATTR_MEMBER, + [ATTRIBUTE_BENCHMARK] = ATTR_FUNC, [ATTRIBUTE_BIGENDIAN] = ATTR_BITSTRUCT, [ATTRIBUTE_BUILTIN] = ATTR_MACRO | ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST, [ATTRIBUTE_CALLCONV] = ATTR_FUNC, @@ -1764,6 +1765,9 @@ static bool sema_analyse_attribute(SemaContext *context, Decl *decl, Attr *attr, if (!expr_is_const_string(expr)) RETURN_SEMA_ERROR(expr, "Expected a constant string value as argument."); if (!update_call_abi_from_string(decl, expr)) return false; break; + case ATTRIBUTE_BENCHMARK: + decl->func_decl.attr_benchmark = true; + break; case ATTRIBUTE_TEST: decl->func_decl.attr_test = true; break; @@ -2412,7 +2416,7 @@ static inline bool sema_analyse_main_function(SemaContext *context, Decl *decl) // At this point the style is either MAIN_INT_VOID, MAIN_VOID_VOID or MAIN_ERR_VOID MainType type = sema_find_main_type(context, signature, is_winmain); if (type == MAIN_TYPE_ERROR) return false; - if (active_target.type == TARGET_TYPE_TEST) return true; + if (active_target.type == TARGET_TYPE_TEST || active_target.type == TARGET_TYPE_BENCHMARK) return true; Decl *function; if (active_target.no_entry) { @@ -2521,6 +2525,16 @@ static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *era if (*erase_decl) return true; + if (decl->name == kw___run_default_benchmark_runner) + { + if (global_context.benchmark_func) + { + SEMA_ERROR(decl, "Multiple benchmark runners defined."); + return false; + } + global_context.benchmark_func = decl; + if (active_target.benchmarking) decl->no_strip = true; + } if (decl->name == kw___run_default_test_runner) { if (global_context.test_func) @@ -2532,19 +2546,20 @@ static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *era if (active_target.testing) decl->no_strip = true; } bool is_test = decl->func_decl.attr_test; + bool is_benchmark = decl->func_decl.attr_benchmark; Signature *sig = &decl->func_decl.signature; - if (is_test) + if (is_test || is_benchmark) { if (vec_size(sig->params)) { - SEMA_ERROR(sig->params[0], "'@test' functions may not take any parameters."); + SEMA_ERROR(sig->params[0], "'@test' and '@benchmark' functions may not take any parameters."); return false; } TypeInfo *rtype_info = type_infoptr(sig->rtype); if (!sema_resolve_type_info(context, rtype_info)) return false; if (type_no_optional(rtype_info->type) != type_void) { - SEMA_ERROR(rtype_info, "'@test' functions may only return 'void' or 'void!'."); + SEMA_ERROR(rtype_info, "'@test' and '@benchmark' functions may only return 'void' or 'void!'."); return false; } if (rtype_info->type == type_void) @@ -2576,7 +2591,7 @@ static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *era } if (decl->func_decl.type_parent) { - if (is_test) SEMA_ERROR(decl, "Methods may not be annotated @test."); + if (is_test || is_benchmark) SEMA_ERROR(decl, "Methods may not be annotated @test or @benchmark."); if (!sema_analyse_method(context, decl)) return decl_poison(decl); } else @@ -2593,7 +2608,7 @@ static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *era } if (decl->name == kw_main) { - if (is_test) SEMA_ERROR(decl, "Main functions may not be annotated @test."); + if (is_test || is_benchmark) SEMA_ERROR(decl, "Main functions may not be annotated @test or @benchmark."); if (!sema_analyse_main_function(context, decl)) return decl_poison(decl); } decl_set_external_name(decl); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 61c1a405b..f6e2f9357 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -513,6 +513,7 @@ static bool sema_binary_is_expr_lvalue(Expr *top_expr, Expr *expr) case EXPR_ANY: case EXPR_ANYSWITCH: case EXPR_VASPLAT: + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: case EXPR_GENERIC_IDENT: case EXPR_MACRO_BODY: @@ -626,6 +627,7 @@ static bool expr_may_ref(Expr *expr) case EXPR_ANY: case EXPR_ANYSWITCH: case EXPR_VASPLAT: + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: case EXPR_GENERIC_IDENT: case EXPR_MACRO_BODY: @@ -1768,6 +1770,13 @@ static inline bool sema_expr_analyse_func_call(SemaContext *context, Expr *expr, SEMA_ERROR(expr, "@test functions may not be directly called."); return false; } + + if (decl->func_decl.attr_benchmark) + { + SEMA_ERROR(expr, "@benchmark functions may not be directly called."); + return false; + } + sema_display_deprecated_warning_on_use(context, decl, expr->span); // Tag dynamic dispatch. @@ -5793,6 +5802,10 @@ static inline const char *sema_addr_may_take_of_ident(Expr *inner) { return "You may not take the address of a '@test' function."; } + if (decl->func_decl.attr_benchmark) + { + return "You may not take the address of a '@benchmark' function."; + } return NULL; case DECL_VAR: return sema_addr_may_take_of_var(inner, decl); @@ -6631,6 +6644,35 @@ static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr * expr_replace(expr, value); return true; } + + case BUILTIN_DEF_BENCHMARK_NAMES: + if (!active_target.benchmarking) + { + expr->const_expr.const_kind = CONST_INITIALIZER; + expr->expr_kind = EXPR_CONST; + ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); + init->kind = CONST_INIT_ZERO; + init->type = expr->type = type_get_subarray(type_string); + return true; + } + expr->type = type_get_subarray(type_string); + expr->benchmark_hook_expr = BUILTIN_DEF_BENCHMARK_NAMES; + expr->expr_kind = EXPR_BENCHMARK_HOOK; + return true; + case BUILTIN_DEF_BENCHMARK_FNS: + if (!active_target.benchmarking) + { + expr->const_expr.const_kind = CONST_INITIALIZER; + expr->expr_kind = EXPR_CONST; + ConstInitializer *init = expr->const_expr.initializer = CALLOCS(ConstInitializer); + init->kind = CONST_INIT_ZERO; + init->type = expr->type = type_get_subarray(type_voidptr); + return true; + } + expr->type = type_get_subarray(type_voidptr); + expr->benchmark_hook_expr = BUILTIN_DEF_BENCHMARK_FNS; + expr->expr_kind = EXPR_BENCHMARK_HOOK; + return true; case BUILTIN_DEF_TEST_NAMES: if (!active_target.testing) { @@ -7784,6 +7826,7 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) case EXPR_TYPEID_INFO: case EXPR_ASM: case EXPR_OPERATOR_CHARS: + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: case EXPR_SWIZZLE: case EXPR_MACRO_BODY: diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index d52c18160..b33265439 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -466,6 +466,7 @@ RETRY: expr = exprptr(expr->ternary_expr.else_expr); goto RETRY; } + case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: return; case EXPR_TYPEID_INFO: @@ -502,6 +503,7 @@ void sema_trace_liveness(void) sema_trace_decl_liveness(global_context.main); } bool keep_tests = active_target.testing; + bool keep_benchmarks = active_target.benchmarking; 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); @@ -509,7 +511,9 @@ void sema_trace_liveness(void) 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 || (function->func_decl.attr_test && keep_tests)) sema_trace_decl_liveness(function); + if (function->is_export || function->no_strip || + (function->func_decl.attr_test && keep_tests) || + (function->func_decl.attr_benchmark && keep_benchmarks)) 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); diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index b0e37d9e0..99c71d8fc 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -465,6 +465,9 @@ static inline bool analyse_func_body(SemaContext *context, Decl *decl) // Don't analyse functions that are tests. if (decl->func_decl.attr_test && !active_target.testing) return true; + // Don't analyse functions that are benchmarks. + if (decl->func_decl.attr_benchmark && !active_target.benchmarking) return true; + if (!sema_analyse_function_body(context, decl)) return decl_poison(decl); return true; } diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index 8ea154bc3..3ecd57118 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -73,6 +73,7 @@ const char *kw_std; const char *kw_std__core; const char *kw_std__core__types; const char *kw_std__io; +const char *kw___run_default_benchmark_runner; const char *kw___run_default_test_runner; const char *kw_type; const char *kw_typekind; @@ -131,6 +132,8 @@ void symtab_init(uint32_t capacity) builtin_defines[BUILTIN_DEF_LINE_RAW] = KW_DEF("LINE_RAW"); builtin_defines[BUILTIN_DEF_MODULE] = KW_DEF("MODULE"); builtin_defines[BUILTIN_DEF_TIME] = KW_DEF("TIME"); + builtin_defines[BUILTIN_DEF_BENCHMARK_NAMES] = KW_DEF("BENCHMARK_NAMES"); + builtin_defines[BUILTIN_DEF_BENCHMARK_FNS] = KW_DEF("BENCHMARK_FNS"); builtin_defines[BUILTIN_DEF_TEST_NAMES] = KW_DEF("TEST_NAMES"); builtin_defines[BUILTIN_DEF_TEST_FNS] = KW_DEF("TEST_FNS"); kw_FILE_NOT_FOUND = KW_DEF("FILE_NOT_FOUND"); @@ -164,6 +167,7 @@ void symtab_init(uint32_t capacity) kw_std__core = KW_DEF("std::core"); kw_std__core__types = KW_DEF("std::core::types"); kw_std__io = KW_DEF("std::io"); + kw___run_default_benchmark_runner = KW_DEF("__run_default_benchmark_runner"); kw___run_default_test_runner = KW_DEF("__run_default_test_runner"); kw_type = KW_DEF("type"); kw_winmain = KW_DEF("wWinMain"); @@ -299,6 +303,7 @@ void symtab_init(uint32_t capacity) kw_at_return = KW_DEF("@return"); attribute_list[ATTRIBUTE_ALIGN] = KW_DEF("@align"); + attribute_list[ATTRIBUTE_BENCHMARK] = KW_DEF("@benchmark"); attribute_list[ATTRIBUTE_BIGENDIAN] = KW_DEF("@bigendian"); attribute_list[ATTRIBUTE_BUILTIN] = KW_DEF("@builtin"); attribute_list[ATTRIBUTE_CALLCONV] = KW_DEF("@callconv"); diff --git a/src/compiler/tilde_codegen.c b/src/compiler/tilde_codegen.c index d768969ad..25d1d5be5 100644 --- a/src/compiler/tilde_codegen.c +++ b/src/compiler/tilde_codegen.c @@ -427,10 +427,15 @@ static TildeContext *tilde_gen_module(Module *module, TB_FeatureSet *feature_set if (!active_target.testing) continue; vec_add(module->tests, func); } + if (func->func_decl.attr_benchmark) + { + if (!active_target.benchmarking) continue; + vec_add(module->benchmarks, func); + } tilde_emit_function_decl(context, func); FOREACH_END(); - if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic) + if (active_target.type != TARGET_TYPE_TEST && active_target.type != TARGET_TYPE_BENCHMARK && unit->main_function && unit->main_function->is_synthetic) { tilde_emit_function_decl(context, unit->main_function); } @@ -456,7 +461,7 @@ static TildeContext *tilde_gen_module(Module *module, TB_FeatureSet *feature_set if (decl->func_decl.body) tilde_emit_function_body(context, decl); FOREACH_END(); - if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic) + if (active_target.type != TARGET_TYPE_TEST && active_target.type != TARGET_TYPE_BENCHMARK && unit->main_function && unit->main_function->is_synthetic) { tilde_emit_function_body(context, unit->main_function); } diff --git a/src/main.c b/src/main.c index 0dba24b8e..dc464e0d7 100644 --- a/src/main.c +++ b/src/main.c @@ -65,6 +65,7 @@ int main_real(int argc, const char *argv[]) case COMMAND_COMPILE_RUN: case COMMAND_DYNAMIC_LIB: case COMMAND_STATIC_LIB: + case COMMAND_COMPILE_BENCHMARK: case COMMAND_COMPILE_TEST: compile_target(&build_options); break; @@ -80,6 +81,7 @@ int main_real(int argc, const char *argv[]) case COMMAND_DIST: case COMMAND_DOCS: case COMMAND_BENCH: + case COMMAND_BENCHMARK: case COMMAND_TEST: compile_file_list(&build_options); break;