diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 0000000..af4d44d --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,41 @@ +name: CodSpeed + +on: + push: + branches: + - "master" + pull_request: + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + codspeed: + name: Run benchmarks + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install GCC 14 + run: | + sudo apt-get update + sudo apt-get install -y g++-14 + + - name: Build benchmarks + run: | + cmake -B build \ + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DENABLE_TESTS=OFF \ + -DENABLE_BENCHMARKS=ON \ + -DCODSPEED_MODE=simulation + cmake --build build --config RelWithDebInfo -j + + - name: Run benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: ./build/pxkorka_benchmarks diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a18693..f05f368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,12 +11,13 @@ set(CMAKE_CXX_EXTENSIONS OFF) # --- OPTIONS --- option(ENABLE_TESTS "Build tests" ON) +option(ENABLE_BENCHMARKS "Build benchmarks" OFF) -# Enable all warnings +# Strict warning flags for project targets only (not third-party dependencies) if (MSVC) - add_compile_options(/W4 /WX /permissive-) + set(PROJECT_WARNING_FLAGS /W4 /WX /permissive-) else () - add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wshadow -Wunused) + set(PROJECT_WARNING_FLAGS -Wall -Wextra -Wpedantic -Werror -Wshadow -Wunused) endif () # Dependencies @@ -29,6 +30,19 @@ if (ENABLE_TESTS) endif () +if (ENABLE_BENCHMARKS) + set(BENCHMARK_DOWNLOAD_DEPENDENCIES ON) + + include(FetchContent) + FetchContent_Declare( + google_benchmark + GIT_REPOSITORY https://github.com/CodSpeedHQ/codspeed-cpp + SOURCE_SUBDIR google_benchmark + GIT_TAG main + ) + FetchContent_MakeAvailable(google_benchmark) +endif () + add_library(korka_lib include/korka/vm/vm_runtime.hpp src/vm/vm_runtime.cpp include/korka/vm/op_codes.hpp @@ -44,6 +58,8 @@ add_library(korka_lib include/korka/compiler/compiler.hpp ) +target_compile_options(korka_lib PRIVATE ${PROJECT_WARNING_FLAGS}) + target_include_directories(korka_lib PUBLIC $ @@ -55,6 +71,7 @@ target_include_directories(korka_lib add_executable(pxkorka main.cpp) target_link_libraries(pxkorka PRIVATE korka_lib) +target_compile_options(pxkorka PRIVATE ${PROJECT_WARNING_FLAGS}) # --- TESTS --- if (ENABLE_TESTS) @@ -72,5 +89,22 @@ if (ENABLE_TESTS) Catch2WithMain ) + target_compile_options(pxkorka_tests PRIVATE ${PROJECT_WARNING_FLAGS}) + catch_discover_tests(pxkorka_tests) -endif () \ No newline at end of file +endif () + +# --- BENCHMARKS --- +if (ENABLE_BENCHMARKS) + add_executable(pxkorka_benchmarks + bench/lexer_bench.cpp + bench/parser_bench.cpp + bench/bytecode_builder_bench.cpp + ) + + target_link_libraries(pxkorka_benchmarks + PRIVATE + korka_lib + benchmark::benchmark + ) +endif () diff --git a/README.md b/README.md index 818a987..67ba84b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A Virtual Machine where lexing and compilation happen entirely at **compile-time**. +[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge/github/PyXiion/pxkorka)](https://codspeed.io/PyXiion/pxkorka?utm_source=badge) + --- ### What is this diff --git a/bench/bytecode_builder_bench.cpp b/bench/bytecode_builder_bench.cpp new file mode 100644 index 0000000..4972d51 --- /dev/null +++ b/bench/bytecode_builder_bench.cpp @@ -0,0 +1,58 @@ +#include +#include "korka/vm/bytecode_builder.hpp" +#include "korka/vm/op_codes.hpp" +#include "korka/vm/options.hpp" + +static void BM_EmitArithmeticOps(benchmark::State &state) { + for (auto _ : state) { + korka::vm::bytecode_builder b{}; + b.emit_add(0, 1, 2); + b.emit_sub(3, 4, 5); + b.emit_mul(6, 7, 8); + b.emit_div(9, 10, 11); + auto bytes = b.build(); + benchmark::DoNotOptimize(bytes); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_EmitArithmeticOps); + +static void BM_EmitJumps(benchmark::State &state) { + for (auto _ : state) { + korka::vm::bytecode_builder b{}; + auto target = b.make_label(); + b.emit_jmp(target); + b.emit_add(0, 1, 2); + b.bind(target); + b.emit_add(3, 4, 5); + auto bytes = b.build(); + benchmark::DoNotOptimize(bytes); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_EmitJumps); + +static void BM_BuildComplexBytecode(benchmark::State &state) { + for (auto _ : state) { + korka::vm::bytecode_builder b{}; + + auto loop_start = b.make_label(); + auto loop_end = b.make_label(); + + b.emit_load_imm(0, korka::vm::stack_value_t{0}); + b.emit_load_imm(1, korka::vm::stack_value_t{100}); + b.emit_load_imm(2, korka::vm::stack_value_t{1}); + + b.bind(loop_start); + b.emit_cmp_lt(3, 0, 1); + b.emit_jmp_if(loop_end, 3); + b.emit_add(0, 0, 2); + b.emit_jmp(loop_start); + + b.bind(loop_end); + auto bytes = b.build(); + benchmark::DoNotOptimize(bytes); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_BuildComplexBytecode); diff --git a/bench/lexer_bench.cpp b/bench/lexer_bench.cpp new file mode 100644 index 0000000..eb3c258 --- /dev/null +++ b/bench/lexer_bench.cpp @@ -0,0 +1,70 @@ +#include +#include "korka/compiler/lexer.hpp" + +static const char *simple_program = + "int main() {\n" + " return 0;\n" + "}\n"; + +static const char *medium_program = + "int main() {\n" + " int i = 0;\n" + " while (i < 100) {\n" + " i = i + 1;\n" + " }\n" + " return i;\n" + "}\n"; + +static const char *complex_program = + "int fibonacci(int n) {\n" + " if (n <= 1) return n;\n" + " return fibonacci(n - 1) + fibonacci(n - 2);\n" + "}\n" + "\n" + "int sum(int a, int b) {\n" + " return a + b;\n" + "}\n" + "\n" + "int main() {\n" + " int x = 10;\n" + " int y = 20;\n" + " int z = sum(x, y);\n" + " int fib = fibonacci(z);\n" + " if (fib > 1000) {\n" + " return 1;\n" + " } else {\n" + " return 0;\n" + " }\n" + "}\n"; + +static void BM_LexSimpleProgram(benchmark::State &state) { + for (auto _ : state) { + korka::lexer lexer{simple_program}; + auto tokens = lexer.lex(); + benchmark::DoNotOptimize(tokens); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_LexSimpleProgram); + +static void BM_LexMediumProgram(benchmark::State &state) { + for (auto _ : state) { + korka::lexer lexer{medium_program}; + auto tokens = lexer.lex(); + benchmark::DoNotOptimize(tokens); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_LexMediumProgram); + +static void BM_LexComplexProgram(benchmark::State &state) { + for (auto _ : state) { + korka::lexer lexer{complex_program}; + auto tokens = lexer.lex(); + benchmark::DoNotOptimize(tokens); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_LexComplexProgram); + +BENCHMARK_MAIN(); diff --git a/bench/parser_bench.cpp b/bench/parser_bench.cpp new file mode 100644 index 0000000..d5dc584 --- /dev/null +++ b/bench/parser_bench.cpp @@ -0,0 +1,83 @@ +#include +#include "korka/compiler/lexer.hpp" +#include "korka/compiler/parser.hpp" + +static const char *simple_program = + "int main() {\n" + " return 0;\n" + "}\n"; + +static const char *medium_program = + "int main() {\n" + " int i = 0;\n" + " while (i < 100) {\n" + " i = i + 1;\n" + " }\n" + " return i;\n" + "}\n"; + +static const char *complex_program = + "int fibonacci(int n) {\n" + " if (n <= 1) return n;\n" + " return fibonacci(n - 1) + fibonacci(n - 2);\n" + "}\n" + "\n" + "int sum(int a, int b) {\n" + " return a + b;\n" + "}\n" + "\n" + "int main() {\n" + " int x = 10;\n" + " int y = 20;\n" + " int z = sum(x, y);\n" + " int fib = fibonacci(z);\n" + " if (fib > 1000) {\n" + " return 1;\n" + " } else {\n" + " return 0;\n" + " }\n" + "}\n"; + +static void BM_ParseSimpleProgram(benchmark::State &state) { + auto tokens = korka::lexer{simple_program}.lex(); + for (auto _ : state) { + korka::parser parser{tokens.value()}; + auto result = parser.parse(); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_ParseSimpleProgram); + +static void BM_ParseMediumProgram(benchmark::State &state) { + auto tokens = korka::lexer{medium_program}.lex(); + for (auto _ : state) { + korka::parser parser{tokens.value()}; + auto result = parser.parse(); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_ParseMediumProgram); + +static void BM_ParseComplexProgram(benchmark::State &state) { + auto tokens = korka::lexer{complex_program}.lex(); + for (auto _ : state) { + korka::parser parser{tokens.value()}; + auto result = parser.parse(); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_ParseComplexProgram); + +static void BM_LexAndParseComplexProgram(benchmark::State &state) { + for (auto _ : state) { + auto tokens = korka::lexer{complex_program}.lex(); + korka::parser parser{tokens.value()}; + auto result = parser.parse(); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_LexAndParseComplexProgram);