konpeito 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Justfile ADDED
@@ -0,0 +1,107 @@
1
+ # Konpeito — Ruby AOT Native Compiler
2
+ # Task runner recipes (https://github.com/casey/just)
3
+
4
+ # Default recipe: show available recipes
5
+ default:
6
+ @just --list
7
+
8
+ # --- Development ---
9
+
10
+ # Install dependencies
11
+ setup:
12
+ bundle install
13
+
14
+ # Clean build artifacts and caches
15
+ clean:
16
+ rm -rf .konpeito_cache/
17
+ find . -name "*.bundle" -not -path "./vendor/*" -delete
18
+ find . -name "*.so" -not -path "./vendor/*" -delete
19
+ find . -name "*.jar" -not -path "./vendor/*" -delete
20
+ find . -name "*.class" -not -path "./vendor/*" -delete
21
+ find . -name "*.o" -not -path "./vendor/*" -delete
22
+ find . -name "*.ll" -not -path "./vendor/*" -delete
23
+ find . -name "*.bc" -not -path "./vendor/*" -delete
24
+
25
+ # Check environment (LLVM, Java, etc.)
26
+ doctor:
27
+ bundle exec ruby -Ilib bin/konpeito doctor
28
+
29
+ # --- Testing ---
30
+
31
+ # Run all unit tests
32
+ test:
33
+ bundle exec rake test
34
+
35
+ # Run a specific test file
36
+ test-file path:
37
+ bundle exec ruby -Ilib:test {{path}}
38
+
39
+ # Run conformance tests (all backends)
40
+ conformance:
41
+ bundle exec rake conformance
42
+
43
+ # Run conformance tests (native only)
44
+ conformance-native:
45
+ bundle exec rake conformance:native
46
+
47
+ # Run conformance tests (JVM only)
48
+ conformance-jvm:
49
+ bundle exec rake conformance:jvm
50
+
51
+ # --- Linting & Formatting ---
52
+
53
+ # Run RuboCop linter
54
+ lint:
55
+ bundle exec rubocop
56
+
57
+ # Run RuboCop with auto-fix
58
+ lint-fix:
59
+ bundle exec rubocop -A
60
+
61
+ # Format source files (via RuboCop)
62
+ fmt *args:
63
+ bundle exec ruby -Ilib bin/konpeito fmt {{args}}
64
+
65
+ # Check formatting (via RuboCop)
66
+ fmt-check:
67
+ bundle exec ruby -Ilib bin/konpeito fmt --check
68
+
69
+ # --- Build & Run ---
70
+
71
+ # Compile a Ruby file to CRuby extension
72
+ build *args:
73
+ bundle exec ruby -Ilib bin/konpeito build {{args}}
74
+
75
+ # Compile and run a Ruby file
76
+ run *args:
77
+ bundle exec ruby -Ilib bin/konpeito run {{args}}
78
+
79
+ # Type-check a Ruby file
80
+ check *args:
81
+ bundle exec ruby -Ilib bin/konpeito check {{args}}
82
+
83
+ # --- Benchmarks ---
84
+
85
+ # Run a benchmark (e.g., just bench native_internal)
86
+ bench name:
87
+ bundle exec ruby benchmark/{{name}}_bench.rb
88
+
89
+ # List available benchmarks
90
+ bench-list:
91
+ @ls benchmark/*_bench.rb | sed 's|benchmark/||; s|_bench.rb||'
92
+
93
+ # --- Install ---
94
+
95
+ # Build and install gem locally
96
+ install:
97
+ gem build konpeito.gemspec && gem install konpeito-*.gem && rm -f konpeito-*.gem
98
+
99
+ # --- Tools ---
100
+
101
+ # Start LSP server
102
+ lsp:
103
+ bundle exec ruby -Ilib bin/konpeito lsp
104
+
105
+ # Generate shell completion
106
+ completion shell:
107
+ bundle exec ruby -Ilib bin/konpeito completion {{shell}}
data/README.md CHANGED
@@ -16,6 +16,81 @@ Konpeito uses a three-tier type resolution strategy:
16
16
 
17
17
  Adding an RBS signature promotes a dynamic fallback to static dispatch. The compiler tells you where the boundaries are — fix them if you want, or leave them dynamic. Your call.
18
18
 
19
+ ## What Gets Compiled
20
+
21
+ Konpeito compiles your `.rb` source files into native code. Understanding the boundary between compiled and non-compiled code is key:
22
+
23
+ **Compiled (native code):**
24
+ - All `.rb` files you pass to `konpeito build`
25
+ - Files resolved via `require` / `require_relative` at compile time (when source is available on the load path via `-I`)
26
+ - Within compiled code: type-resolved operations become native CPU instructions; unresolved operations fall back to `rb_funcallv` (CRuby's dynamic dispatch) with a compiler warning
27
+
28
+ **Not compiled (loaded at runtime by CRuby):**
29
+ - Installed gems referenced by `require` — the compiler detects these and emits `rb_require` calls so CRuby loads them at runtime
30
+ - Standard libraries (`json`, `net/http`, `fileutils`, etc.) — same treatment
31
+ - Any Ruby code running in the host CRuby process outside the compiled extension
32
+
33
+ The compiled extension and CRuby coexist in the same process. Native code calls CRuby C API functions (`rb_define_class`, `rb_funcallv`, `rb_ary_push`, etc.), so compiled code can freely create Ruby objects, call Ruby methods, and interact with any loaded gem. The speed gain comes from the parts where Konpeito resolves types and emits native instructions instead of going through Ruby's method dispatch.
34
+
35
+ This leads to two usage patterns:
36
+
37
+ ### Pattern 1: Extension Library
38
+
39
+ Compile performance-critical code into a CRuby extension (`.bundle` / `.so`). Your main application stays as regular Ruby and loads the compiled code via `require`:
40
+
41
+ ```ruby
42
+ # math.rb — compiled by Konpeito
43
+ module MyMath
44
+ def self.sum_up_to(n)
45
+ total = 0
46
+ i = 1
47
+ while i <= n
48
+ total += i
49
+ i += 1
50
+ end
51
+ total
52
+ end
53
+ end
54
+ ```
55
+
56
+ ```bash
57
+ konpeito build math.rb # → math.bundle
58
+ ```
59
+
60
+ ```ruby
61
+ # app.rb — regular Ruby, NOT compiled
62
+ require_relative "math"
63
+ puts MyMath.sum_up_to(10_000_000) # calls into compiled native code
64
+ ```
65
+
66
+ Your code can `require` installed gems freely. The compiler compiles your code and emits runtime `require` calls for the gems, so everything works together at runtime:
67
+
68
+ ```ruby
69
+ # my_app.rb — compiled by Konpeito
70
+ require "kumiki" # gem — loaded at runtime by CRuby
71
+ include Kumiki
72
+
73
+ class MyComponent < Component
74
+ # ... your code is compiled natively
75
+ # ... calls to kumiki methods go through rb_funcallv (dynamic dispatch)
76
+ end
77
+ ```
78
+
79
+ This is the pattern shown in [Hello World](#hello-world) below.
80
+
81
+ ### Pattern 2: Whole Application
82
+
83
+ Compile an entire Ruby application including framework code — the compiler traces all `require` statements on the load path specified by `-I` and compiles everything into a single extension:
84
+
85
+ ```bash
86
+ konpeito build -I /path/to/kumiki/lib counter.rb # → counter.bundle (971 KB)
87
+ ruby -r ./counter -e "" # load and run
88
+ ```
89
+
90
+ The difference from Pattern 1: framework code (kumiki) is also compiled, enabling direct dispatch, monomorphization, and inlining across the entire codebase. Without `-I`, only your code is compiled (~50 KB) and the framework is loaded at runtime — this still works, but method calls into the framework go through dynamic dispatch.
91
+
92
+ See [Tutorial](docs/tutorial.md) for step-by-step walkthroughs of both patterns with working examples.
93
+
19
94
  ## Quick Start
20
95
 
21
96
  ### Prerequisites
@@ -65,18 +140,20 @@ Write a small Ruby file:
65
140
 
66
141
  ```ruby
67
142
  # math.rb
68
- def add(a, b)
69
- a + b
70
- end
143
+ module MyMath
144
+ def self.add(a, b)
145
+ a + b
146
+ end
71
147
 
72
- def sum_up_to(n)
73
- total = 0
74
- i = 1
75
- while i <= n
76
- total = total + i
77
- i = i + 1
148
+ def self.sum_up_to(n)
149
+ total = 0
150
+ i = 1
151
+ while i <= n
152
+ total = total + i
153
+ i = i + 1
154
+ end
155
+ total
78
156
  end
79
- total
80
157
  end
81
158
  ```
82
159
 
@@ -88,19 +165,26 @@ konpeito build math.rb # produces math.bundle (macOS), math.so (Linux),
88
165
 
89
166
  ```ruby
90
167
  require_relative "math"
91
- puts add(3, 4) # => 7
92
- puts sum_up_to(100) # => 5050
168
+ puts MyMath.add(3, 4) # => 7
169
+ puts MyMath.sum_up_to(100) # => 5050
93
170
  ```
94
171
 
95
- A few more commands to get you going:
172
+ ### CLI Overview
96
173
 
97
174
  ```bash
98
- konpeito doctor # check your environment
99
- konpeito run --target jvm main.rb # compile and run in one step
100
- konpeito fmt # format source files
101
- konpeito fmt --check # check formatting without modifying
175
+ konpeito build src/main.rb # compile to CRuby extension
176
+ konpeito build --target jvm -o app.jar src/main.rb # compile to standalone JAR
177
+ konpeito run src/main.rb # build and run in one step
178
+ konpeito check src/main.rb # type check only (no codegen)
179
+ konpeito init my_project # scaffold a new project
180
+ konpeito test # run project tests
181
+ konpeito fmt # format source files
182
+ konpeito watch src/main.rb # auto-recompile on changes
183
+ konpeito doctor # check your environment
102
184
  ```
103
185
 
186
+ For detailed options and examples, see [CLI Reference](docs/cli-reference.md).
187
+
104
188
  ## Features
105
189
 
106
190
  - **HM Type Inference** — Types are inferred automatically. No annotations needed for most code.
@@ -118,9 +202,38 @@ konpeito fmt --check # check formatting without modifying
118
202
  - **Pattern Matching** — Full `case/in` support with array, hash, guard, and capture patterns.
119
203
  - **Modern Ruby Syntax** — `_1`/`_2` numbered params, `it`, endless methods, `class << self`, safe navigation (`&.`), and more.
120
204
  - **Concurrency** — Fiber, Thread, Mutex, ConditionVariable, SizedQueue, and Ractor (with `Ractor::Port`, `Ractor.select`, `Ractor[:key]` local storage, `name:`, `monitor`/`unmonitor`). JVM Ractor uses Virtual Threads for scheduling but does not enforce object isolation — objects are shared by reference, unlike CRuby's strict isolation model.
121
- - **Built-in Tooling** — Formatter (`fmt`), LSP (hover, completion, go-to-def, references, rename), debug info (`-g`), and profiling (`--profile`).
205
+ - **Built-in Tooling** — Formatter (`fmt`), debug info (`-g`), and profiling (`--profile`).
122
206
  - **Castella UI** — A reactive GUI framework for the JVM backend (see below).
123
207
 
208
+ ## Supported Ruby Syntax
209
+
210
+ Konpeito supports most Ruby 4.0 syntax:
211
+
212
+ | Category | Supported |
213
+ |----------|-----------|
214
+ | Literals | Integer, Float, String, Symbol, Array, Hash, Regexp, Range, Heredoc, nil/true/false |
215
+ | String interpolation | `"Hello #{name}"` |
216
+ | Variables | Local, `@instance`, `@@class`, `$global` |
217
+ | Control flow | `if`/`unless`, `while`/`until`, `for`, `case`/`when`, `case`/`in`, `break`, `next`, `return` |
218
+ | Methods & OOP | Classes, modules, inheritance, `super`, `attr_accessor`, method visibility (`private`/`protected`) |
219
+ | Blocks & closures | `yield`, `block_given?`, Proc, Lambda, `&blk` |
220
+ | Pattern matching | Literals, arrays, hashes, guards (`if`), captures (`=>`), pins (`^`), rest (`*`) |
221
+ | Exceptions | `begin`/`rescue`/`else`/`ensure`, custom exception classes |
222
+ | Modern syntax | `_1`/`_2` numbered params, `it`, endless methods, `class << self`, `&.` safe navigation |
223
+ | Operators | Full overloading (`+`, `-`, `*`, `==`, `<=>`, etc.), compound assignment (`+=`, `\|\|=`, `&&=`) |
224
+ | Arguments | Keyword args, rest args (`*args`), keyword rest (`**kwargs`), splat (`foo(*arr)`) |
225
+ | Concurrency | Fiber, Thread, Mutex, ConditionVariable, SizedQueue, Ractor (JVM) |
226
+ | Misc | `alias`, `defined?`, open classes, multi-assignment, `%w`/`%i` literals |
227
+
228
+ ### Not Supported (by design)
229
+
230
+ - `eval`, `instance_eval`, `class_eval`
231
+ - `define_method`, `method_missing`
232
+ - `ObjectSpace`, `Binding`
233
+ - Dynamic `require`/`load` (variable-based require)
234
+
235
+ For the complete specification, see [Language Specification](docs/language-specification.md).
236
+
124
237
  ## JVM Backend
125
238
 
126
239
  Konpeito can also compile to JVM bytecode, producing standalone JAR files:
@@ -370,45 +483,32 @@ app.run
370
483
 
371
484
  ## Performance
372
485
 
373
- Konpeito shines in compute-heavy, typed loops where unboxed arithmetic and backend optimizations kick in. All benchmarks compare against Ruby 4.0.1 with YJIT enabled on Apple M4 Max.
486
+ Konpeito targets compute-heavy, typed loops where unboxed arithmetic and backend optimizations can make a meaningful difference. Actual speedups depend heavily on the workload, input data, and hardware the numbers below are from one specific environment and should be taken as rough indicators, not guarantees.
374
487
 
375
488
  ### LLVM Backend (CRuby Extension)
376
489
 
377
- | Benchmark | vs Ruby (YJIT) |
378
- |---|---|
379
- | N-Body simulation (5M steps) | **81x** faster |
380
- | Numeric method inlining (abs, even?, odd?) | **25-29x** faster |
381
- | Range enumerable (each, reduce, select) | **40-53x** faster |
382
- | Integer#times (nested, typed) | **891-972x** faster |
383
- | Typed reduce over Array[Integer] | **7x** faster |
384
- | Loop sum (n=100) | **18x** faster |
385
- | Typed counter loop (LICM + LLVM O2) | **5,345x** faster |
386
- | StaticArray/NativeArray sum (4 elements) | **65x** faster |
387
- | StaticArray/NativeArray sum (16 elements) | **232x** faster |
490
+ Typical speedup ranges observed on our test machine (vs Ruby YJIT):
388
491
 
389
- These numbers compare native-internal performance (the loop itself runs inside compiled code). Single cross-boundary calls see smaller gains due to CRuby interop overhead.
492
+ - **Numeric loops** (arithmetic, counters, reductions): often **5–80x** faster, depending on how much of the loop body can be fully unboxed
493
+ - **Native data structures** (NativeArray, StaticArray): **~10–60x** faster for tight element-access loops
494
+ - **Iterator inlining** (each, map, reduce, times): **~7–50x** faster with typed arrays or ranges
390
495
 
391
- ### JVM Backend (Standalone JAR)
496
+ These figures reflect native-internal performance — the loop itself runs entirely inside compiled code. Cross-boundary calls (Ruby ↔ native) see smaller gains due to interop overhead.
392
497
 
393
- Benchmarks run on Java 21 (HotSpot) with JIT warmup. "Realistic" mode uses variable arguments to prevent constant folding.
498
+ **Where Konpeito is slower:** Pattern matching (`case/in`) is currently **~2–3x slower** than YJIT — Ruby's VM is highly optimized for this. String operations (NativeString) are also slower than CRuby's mature string implementation due to conversion overhead. Workloads that are I/O-bound, rely heavily on dynamic features, or hit areas where YJIT already excels may see no benefit or even regressions.
394
499
 
395
- | Benchmark (10M iterations) | Ruby (YJIT) | Konpeito JVM | Speedup |
396
- |---|---|---|---|
397
- | Multiply Add (realistic) | 196 ms | 5.0 ms | **39x** faster |
398
- | Compute Chain (realistic) | 300 ms | 5.1 ms | **59x** faster |
399
- | Arithmetic Intensive (realistic) | 272 ms | 5.0 ms | **55x** faster |
400
- | Loop Sum (realistic) | 107 ms | 2.6 ms | **41x** faster |
401
- | Fibonacci fib(30) x 10 (recursive) | 300 ms | 9.8 ms | **31x** faster |
500
+ ### JVM Backend (Standalone JAR)
402
501
 
403
- The JVM backend benefits from HotSpot's JIT compilation on top of Konpeito's static type resolution, yielding **30-60x** speedups for numeric workloads.
502
+ For numeric workloads, the JVM backend typically shows **~30–60x** speedups over Ruby YJIT, benefiting from HotSpot's JIT on top of Konpeito's static type resolution. I/O-bound or polymorphic code will see less improvement.
404
503
 
405
- > **Environment:** Apple M4 Max, Ruby 4.0.1 + YJIT, Java 21.0.10 (HotSpot), macOS 15.
504
+ > **Test environment:** Apple M4 Max, Ruby 4.0.1 + YJIT, Java 21.0.10 (HotSpot), macOS 15. Results will vary on different hardware and configurations.
406
505
 
407
506
  ## Documentation
408
507
 
409
508
  ### User Guides
410
509
 
411
510
  - **[Getting Started](docs/getting-started.md)** — Installation, Hello World, first project, Castella UI tutorial
511
+ - **[Tutorial](docs/tutorial.md)** — Extension library and whole-application compilation patterns
412
512
  - **[CLI Reference](docs/cli-reference.md)** — All commands, options, and configuration
413
513
  - **[API Reference](docs/api-reference.md)** — Castella UI widgets, native data structures, standard library
414
514
 
@@ -439,7 +539,7 @@ This project was developed collaboratively between a human director ([Yasushi It
439
539
 
440
540
  Konpeito is in an early stage. Bugs and undocumented limitations should be expected. Actively improving — bug reports and feedback are very welcome.
441
541
 
442
- The JVM backend might be more mature than the LLVM/CRuby backend at this point. If you're getting started, try the JVM backend first (`--target jvm`).
542
+ Both LLVM and JVM backends are tested against the project's conformance test suite covering core language features (control flow, classes, blocks, exceptions, pattern matching, concurrency, etc.). The LLVM backend has been successfully used to compile and run [kumiki](https://github.com/i2y/kumiki)'s `all_widgets_demo.rb` — a non-trivial reactive GUI application with 20+ widget types — as a CRuby extension.
443
543
 
444
544
  ## Contributing
445
545
 
data/konpeito.gemspec CHANGED
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.metadata["homepage_uri"] = spec.homepage
22
22
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
23
  spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
24
+ spec.metadata["source_code_uri"] = spec.homepage
25
+ spec.metadata["documentation_uri"] = "#{spec.homepage}/blob/main/docs/getting-started.md"
24
26
 
25
27
  spec.files = Dir.chdir(__dir__) do
26
28
  `git ls-files -z`.split("\x0").reject do |f|
@@ -35,9 +37,8 @@ Gem::Specification.new do |spec|
35
37
 
36
38
  spec.add_dependency "prism"
37
39
  spec.add_dependency "rbs"
38
- spec.add_dependency "language_server-protocol", "~> 3.17"
39
40
 
40
41
  # ruby-llvm is optional — only needed for native/CRuby extension compilation (--target native).
41
- # JVM backend (--target jvm), type checking, LSP, and formatting work without it.
42
+ # JVM backend (--target jvm), type checking, and formatting work without it.
42
43
  # Install manually: gem install ruby-llvm
43
44
  end
@@ -16,11 +16,14 @@ module Konpeito
16
16
  parse_options!
17
17
 
18
18
  if args.empty?
19
- error("No source file specified. Usage: konpeito build <source.rb>")
19
+ source_file = find_default_source
20
+ unless source_file
21
+ error("No source file specified. Usage: konpeito build <source.rb>")
22
+ end
23
+ else
24
+ source_file = args.first
20
25
  end
21
26
 
22
- source_file = args.first
23
-
24
27
  unless File.exist?(source_file)
25
28
  error("File not found: #{source_file}")
26
29
  end
@@ -204,6 +207,16 @@ module Konpeito
204
207
  size_str = File.exist?(output_file) ? " (#{format_size(File.size(output_file))})" : ""
205
208
  emit("Finished", "in #{duration_str} -> #{output_file}#{size_str}")
206
209
  end
210
+
211
+ # Show usage hint (skip for --run and --lib)
212
+ unless options[:run_after] || options[:library]
213
+ if options[:target] == :jvm
214
+ emit("Hint", "java -jar #{output_file}")
215
+ else
216
+ ext_name = File.basename(output_file, File.extname(output_file))
217
+ emit("Hint", "ruby -r ./#{ext_name} -e \"YourModule.method\"")
218
+ end
219
+ end
207
220
  end
208
221
  rescue Konpeito::DependencyError => e
209
222
  display_dependency_error(e)
@@ -215,6 +228,11 @@ module Konpeito
215
228
  $stderr.puts "Error: #{e.message}"
216
229
  exit 1
217
230
  end
231
+
232
+ def find_default_source
233
+ candidates = ["src/main.rb", "main.rb", "app.rb"]
234
+ candidates.find { |f| File.exist?(f) }
235
+ end
218
236
  end
219
237
  end
220
238
  end