Skip to content

Commit 7a434be

Browse files
committed
first commit
0 parents  commit 7a434be

22 files changed

Lines changed: 2726 additions & 0 deletions

.clang-format

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
BasedOnStyle: LLVM
2+
IndentWidth: 4

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Benchmark
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
benchmark:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install dependencies
16+
run: |
17+
sudo apt-get update
18+
sudo apt-get install -y build-essential clang cmake llvm-dev
19+
wget https://github.com/sharkdp/hyperfine/releases/download/v1.20.0/hyperfine_1.20.0_amd64.deb
20+
sudo dpkg -i hyperfine_1.20.0_amd64.deb
21+
22+
- name: Build
23+
run: |
24+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
25+
cmake --build build
26+
clang ./benchmark/bf.c -o build/simple_c -O3
27+
28+
- name: Run benchmarks
29+
run: |
30+
set -euo pipefail
31+
32+
result_file="$(mktemp)"
33+
block_file="$(mktemp)"
34+
tmp_readme="$(mktemp)"
35+
trap 'rm -f "$result_file" "$block_file" "$tmp_readme"' EXIT
36+
37+
hyperfine -w 1 -r 10 -N \
38+
"./build/bfjit ./examples/hanoi.b.txt" \
39+
"./build/simple_c ./examples/hanoi.b.txt" \
40+
"bun ./benchmark/bf.js ./examples/hanoi.b.txt" \
41+
| tee "$result_file"
42+
43+
{
44+
printf '```text\n'
45+
cat "$result_file"
46+
printf '```\n'
47+
} > "$block_file"
48+
49+
sed '/<!-- benchmark:start -->/,/<!-- benchmark:end -->/{//!d;}' README.md \
50+
| sed "/<!-- benchmark:start -->/r $block_file" \
51+
> "$tmp_readme"
52+
53+
mv "$tmp_readme" README.md
54+
55+
echo "Updated README benchmark block:"
56+
sed -n '/<!-- benchmark:start -->/,/<!-- benchmark:end -->/p' README.md

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.cache/
2+
build/
3+
.DS_Store
4+
Thumbs.db

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"C_Cpp.default.compileCommands": [
3+
"${workspaceFolder}/build/compile_commands.json"
4+
]
5+
}

CMakeLists.txt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
project(bfjit LANGUAGES C)
4+
5+
set(CMAKE_C_STANDARD 17)
6+
set(CMAKE_C_STANDARD_REQUIRED ON)
7+
set(CMAKE_C_EXTENSIONS OFF)
8+
9+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
10+
11+
find_program(LLVM_CONFIG_EXECUTABLE NAMES llvm-config-22 llvm-config REQUIRED)
12+
13+
function(bfjit_query_llvm_config output_variable option)
14+
execute_process(
15+
COMMAND "${LLVM_CONFIG_EXECUTABLE}" "${option}"
16+
OUTPUT_VARIABLE query_output
17+
OUTPUT_STRIP_TRAILING_WHITESPACE
18+
RESULT_VARIABLE query_status
19+
)
20+
21+
if(NOT query_status EQUAL 0)
22+
message(FATAL_ERROR "Failed to query LLVM using ${LLVM_CONFIG_EXECUTABLE} ${option}")
23+
endif()
24+
25+
set(${output_variable} "${query_output}" PARENT_SCOPE)
26+
endfunction()
27+
28+
bfjit_query_llvm_config(LLVM_VERSION --version)
29+
30+
if(NOT LLVM_VERSION MATCHES "^22\\.")
31+
message(FATAL_ERROR "LLVM 22 is required, but ${LLVM_VERSION} was found")
32+
endif()
33+
34+
bfjit_query_llvm_config(LLVM_INCLUDE_DIR --includedir)
35+
bfjit_query_llvm_config(LLVM_SHARED_MODE --shared-mode)
36+
bfjit_query_llvm_config(LLVM_LIBFILES_RAW --libfiles)
37+
38+
if(NOT LLVM_SHARED_MODE STREQUAL "shared")
39+
message(FATAL_ERROR "LLVM must provide shared libraries, but llvm-config --shared-mode returned ${LLVM_SHARED_MODE}")
40+
endif()
41+
42+
string(REPLACE "\n" ";" LLVM_SHARED_LIBRARIES "${LLVM_LIBFILES_RAW}")
43+
list(FILTER LLVM_SHARED_LIBRARIES EXCLUDE REGEX "^$")
44+
45+
if(NOT LLVM_SHARED_LIBRARIES)
46+
message(FATAL_ERROR "No LLVM shared libraries were reported by llvm-config --libfiles")
47+
endif()
48+
49+
add_library(llvm-shared INTERFACE)
50+
target_include_directories(llvm-shared INTERFACE "${LLVM_INCLUDE_DIR}")
51+
target_link_libraries(llvm-shared INTERFACE ${LLVM_SHARED_LIBRARIES})
52+
53+
add_library(libbfjit STATIC
54+
include/bfjit.h
55+
include/bflexer.h
56+
include/bfopt.h
57+
include/bfparser.h
58+
src/bfjit.c
59+
src/bflexer.c
60+
src/bfopt.c
61+
src/bfparser.c
62+
)
63+
64+
target_include_directories(libbfjit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
65+
target_link_libraries(libbfjit PUBLIC llvm-shared)
66+
set_target_properties(libbfjit PROPERTIES OUTPUT_NAME bfjit)
67+
68+
add_executable(bfjit src/main.c)
69+
target_link_libraries(bfjit PRIVATE libbfjit)

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Brainf_ck JIT Compiler
2+
3+
This is a JIT compiler for the Brainf\_ck programming language, implemented in C using LLVM. It takes a Brainf\_ck program as input and compiles it to machine code at runtime, allowing for fast execution of Brainf\_ck programs.
4+
5+
## Benchmarks
6+
7+
The benchmark results comparing it to unoptimized Brainf\_ck implementations written in JavaScript and C are as follows:
8+
9+
<!-- benchmark:start -->
10+
```text
11+
Benchmark 1: ./build/bfjit ./examples/hanoi.b.txt
12+
Time (mean ± σ): 147.6 ms ± 5.2 ms [User: 140.1 ms, System: 5.5 ms]
13+
Range (min … max): 144.3 ms … 161.8 ms 10 runs
14+
15+
Benchmark 2: ./build/simple_c ./examples/hanoi.b.txt
16+
Time (mean ± σ): 7.497 s ± 0.108 s [User: 7.447 s, System: 0.033 s]
17+
Range (min … max): 7.382 s … 7.660 s 10 runs
18+
19+
Benchmark 3: bun ./benchmark/bf.js ./examples/hanoi.b.txt
20+
Time (mean ± σ): 12.688 s ± 0.304 s [User: 12.608 s, System: 0.065 s]
21+
Range (min … max): 12.110 s … 13.021 s 10 runs
22+
23+
Summary
24+
./build/bfjit ./examples/hanoi.b.txt ran
25+
50.80 ± 1.94 times faster than ./build/simple_c ./examples/hanoi.b.txt
26+
85.99 ± 3.68 times faster than bun ./benchmark/bf.js ./examples/hanoi.b.txt
27+
```
28+
<!-- benchmark:end -->
29+
30+
## License
31+
32+
This project is licensed under the MIT License.

benchmark/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JS と C で書いたベンチマーク (最適化なし)

benchmark/bf.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include <stdint.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char **argv) {
6+
if (argc < 2) {
7+
fprintf(stderr, "Usage: %s <source.b>\n", argv[0]);
8+
return 1;
9+
}
10+
11+
FILE *source_file = fopen(argv[1], "rb");
12+
if (source_file == NULL) {
13+
perror("fopen");
14+
return 1;
15+
}
16+
17+
if (fseek(source_file, 0, SEEK_END) != 0) {
18+
perror("fseek");
19+
fclose(source_file);
20+
return 1;
21+
}
22+
23+
long source_size = ftell(source_file);
24+
if (source_size < 0) {
25+
perror("ftell");
26+
fclose(source_file);
27+
return 1;
28+
}
29+
30+
if (fseek(source_file, 0, SEEK_SET) != 0) {
31+
perror("fseek");
32+
fclose(source_file);
33+
return 1;
34+
}
35+
36+
char *src = malloc((size_t)source_size + 1);
37+
if (src == NULL) {
38+
perror("malloc");
39+
fclose(source_file);
40+
return 1;
41+
}
42+
43+
size_t bytes_read = fread(src, 1, (size_t)source_size, source_file);
44+
if (bytes_read != (size_t)source_size) {
45+
perror("fread");
46+
free(src);
47+
fclose(source_file);
48+
return 1;
49+
}
50+
src[source_size] = '\0';
51+
52+
fclose(source_file);
53+
54+
size_t program_size = (size_t)source_size;
55+
size_t *brackets = calloc(program_size, sizeof(size_t));
56+
size_t *stack = malloc(program_size * sizeof(size_t));
57+
if (brackets == NULL || stack == NULL) {
58+
perror("malloc");
59+
free(brackets);
60+
free(stack);
61+
free(src);
62+
return 1;
63+
}
64+
65+
size_t stack_size = 0;
66+
for (size_t i = 0; i < program_size; ++i) {
67+
if (src[i] == '[') {
68+
stack[stack_size++] = i;
69+
} else if (src[i] == ']') {
70+
size_t j = stack[--stack_size];
71+
brackets[j] = i;
72+
brackets[i] = j;
73+
}
74+
}
75+
76+
uint8_t mem[65536] = {0};
77+
size_t dp = 0;
78+
size_t ip = 0;
79+
80+
while (ip < program_size) {
81+
switch (src[ip]) {
82+
case '>':
83+
++dp;
84+
break;
85+
case '<':
86+
--dp;
87+
break;
88+
case '+':
89+
++mem[dp];
90+
break;
91+
case '-':
92+
--mem[dp];
93+
break;
94+
case '.':
95+
fputc(mem[dp], stdout);
96+
break;
97+
case ',': {
98+
int ch = fgetc(stdin);
99+
mem[dp] = (uint8_t)ch;
100+
break;
101+
}
102+
case '[':
103+
if (mem[dp] == 0) {
104+
ip = brackets[ip];
105+
}
106+
break;
107+
case ']':
108+
if (mem[dp] != 0) {
109+
ip = brackets[ip];
110+
}
111+
break;
112+
}
113+
++ip;
114+
}
115+
116+
free(stack);
117+
free(brackets);
118+
free(src);
119+
return 0;
120+
}

benchmark/bf.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import fs from "fs";
2+
3+
const file = process.argv[2];
4+
if (!file) {
5+
console.error("Usage: bun bf.js <source.b>");
6+
process.exit(1);
7+
}
8+
9+
const src = fs.readFileSync(file, "utf-8");
10+
11+
const brackets = {};
12+
const stack = [];
13+
for (let i = 0; i < src.length; i++) {
14+
if (src[i] === "[") {
15+
stack.push(i);
16+
} else if (src[i] === "]") {
17+
const j = stack.pop();
18+
brackets[j] = i;
19+
brackets[i] = j;
20+
}
21+
}
22+
23+
const mem = new Uint8Array(65536);
24+
let dp = 0;
25+
let ip = 0;
26+
27+
while (ip < src.length) {
28+
switch (src[ip]) {
29+
case ">":
30+
dp++;
31+
break;
32+
case "<":
33+
dp--;
34+
break;
35+
case "+":
36+
mem[dp]++;
37+
break;
38+
case "-":
39+
mem[dp]--;
40+
break;
41+
case ".":
42+
process.stdout.write(String.fromCharCode(mem[dp]));
43+
break;
44+
case ",": {
45+
const buf = Buffer.alloc(1);
46+
fs.readSync(0, buf, 0, 1);
47+
mem[dp] = buf[0];
48+
break;
49+
}
50+
case "[":
51+
if (mem[dp] === 0) {
52+
ip = brackets[ip];
53+
}
54+
break;
55+
case "]":
56+
if (mem[dp] !== 0) {
57+
ip = brackets[ip];
58+
}
59+
break;
60+
}
61+
ip++;
62+
}

0 commit comments

Comments
 (0)