From 4ed338bdb9b0a893343d29e780dcf557d0ea2945 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Fri, 27 Feb 2026 09:18:17 +0900 Subject: [PATCH] doc: add getting-started guide, C API reference, and architecture overview Three new documents: - doc/guides/getting-started.md: building, running, and embedding mruby - doc/guides/capi.md: C API reference for values, classes, methods, etc. - doc/internal/architecture.md: internal architecture for developers Co-authored-by: Claude --- README.md | 6 +- doc/guides/capi.md | 529 ++++++++++++++++++++++++++++++++++ doc/guides/getting-started.md | 281 ++++++++++++++++++ doc/internal/architecture.md | 293 +++++++++++++++++++ 4 files changed, 1108 insertions(+), 1 deletion(-) create mode 100644 doc/guides/capi.md create mode 100644 doc/guides/getting-started.md create mode 100644 doc/internal/architecture.md diff --git a/README.md b/README.md index 825378026..10b1bac15 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,19 @@ extensions in C and/or Ruby. For a guide on how to use mrbgems, consult the - [About the Limitations of mruby](doc/limitations.md) - [About Amalgamation (Single-File Build)](doc/guides/amalgamation.md) +- [C API Reference](doc/guides/capi.md) - [About the Compile](doc/guides/compile.md) - [About the Debugger with the `mrdb` Command](doc/guides/debugger.md) - [About GC Arena](doc/guides/gc-arena-howto.md) +- [Getting Started with mruby](doc/guides/getting-started.md) - [About the mruby directory structure](doc/guides/hier.md) - [About Linking with `libmruby`](doc/guides/link.md) -- [About Memory Allocator Customization](doc/guides/memory.md) +- [About Memory Allocator Customization and Heap Regions](doc/guides/memory.md) - [About Build-time Configurations](doc/guides/mrbconf.md) - [About the Build-time Library Manager](doc/guides/mrbgems.md) +- [ROM Method Tables for Memory-Efficient Method Registration](doc/guides/rom-method-table.md) - [About the Symbols](doc/guides/symbol.md) +- [Internal Implementation / About mruby Architecture](doc/internal/architecture.md) - [Internal Implementation / About Value Boxing](doc/internal/boxing.md) - [Internal Implementation / About mruby Virtual Machine Instructions](doc/internal/opcode.md) diff --git a/doc/guides/capi.md b/doc/guides/capi.md new file mode 100644 index 000000000..83f55b869 --- /dev/null +++ b/doc/guides/capi.md @@ -0,0 +1,529 @@ + + +# C API Reference + +This document covers the mruby C API for embedding and extending mruby. + +## Headers + +```c +#include /* core types, state, class/method definition */ +#include /* mrb_load_string, mrb_load_file */ +#include /* string operations */ +#include /* array operations */ +#include /* hash operations */ +#include /* wrapping C structs */ +#include /* class inspection */ +#include /* value type macros */ +#include /* loading precompiled bytecode */ +#include /* error handling (mrb_protect etc.) */ +#include /* instance/class/global variables */ +``` + +## State Management + +```c +mrb_state *mrb = mrb_open(); /* create state with all gems */ +mrb_state *mrb = mrb_open_core(); /* create state without gems */ +mrb_close(mrb); /* close and free state */ +``` + +`mrb_open()` returns `NULL` on allocation failure. Always check the +return value. + +## Values + +All Ruby values are represented as `mrb_value` in C. + +### Creating Values + +```c +mrb_nil_value() /* nil */ +mrb_true_value() /* true */ +mrb_false_value() /* false */ +mrb_bool_value(mrb_bool b) /* true or false */ +mrb_fixnum_value(mrb_int i) /* Integer */ +mrb_float_value(mrb_state *mrb, mrb_float f) /* Float */ +mrb_symbol_value(mrb_sym sym) /* Symbol */ +mrb_obj_value(void *p) /* object pointer to value */ +mrb_cptr_value(mrb_state *mrb, void *p) /* C pointer */ +``` + +### Type Checking + +```c +mrb_type(v) /* returns enum mrb_vtype */ +mrb_nil_p(v) /* true if nil */ +mrb_integer_p(v) /* true if Integer */ +mrb_float_p(v) /* true if Float */ +mrb_symbol_p(v) /* true if Symbol */ +mrb_string_p(v) /* true if String */ +mrb_array_p(v) /* true if Array */ +mrb_hash_p(v) /* true if Hash */ +mrb_true_p(v) /* true if true */ +mrb_false_p(v) /* true if false */ +mrb_undef_p(v) /* true if undefined */ +mrb_immediate_p(v) /* true if not a heap object */ +``` + +### Extracting C Values + +```c +mrb_integer(v) /* mrb_int from Integer value */ +mrb_float(v) /* mrb_float from Float value */ +mrb_symbol(v) /* mrb_sym from Symbol value */ +mrb_ptr(v) /* void* from object value */ +mrb_str_to_cstr(mrb, v) /* const char* from String value */ +``` + +### Value Types + +| `mrb_vtype` | Ruby Class | Notes | +| ----------- | ---------- | ----- | +| `MRB_TT_FALSE` | FalseClass/NilClass | `nil` has `MRB_TT_FALSE` | +| `MRB_TT_TRUE` | TrueClass | | +| `MRB_TT_INTEGER` | Integer | Immediate value | +| `MRB_TT_FLOAT` | Float | May be immediate | +| `MRB_TT_SYMBOL` | Symbol | Immediate value | +| `MRB_TT_STRING` | String | Heap object | +| `MRB_TT_ARRAY` | Array | Heap object | +| `MRB_TT_HASH` | Hash | Heap object | +| `MRB_TT_OBJECT` | Object | User-defined classes | +| `MRB_TT_CLASS` | Class | | +| `MRB_TT_MODULE` | Module | | +| `MRB_TT_PROC` | Proc | | +| `MRB_TT_CDATA` | (C data) | Wrapped C structs | +| `MRB_TT_EXCEPTION` | Exception | | +| `MRB_TT_FIBER` | Fiber | | + +## Defining Classes and Modules + +```c +/* Define a class under Object */ +struct RClass *my_class = mrb_define_class(mrb, "MyClass", mrb->object_class); + +/* Define a class under another class/module */ +struct RClass *inner = mrb_define_class_under(mrb, outer, "Inner", mrb->object_class); + +/* Define a module */ +struct RClass *my_mod = mrb_define_module(mrb, "MyModule"); +struct RClass *inner_mod = mrb_define_module_under(mrb, outer, "InnerMod"); + +/* Include/prepend a module */ +mrb_include_module(mrb, my_class, my_mod); +mrb_prepend_module(mrb, my_class, my_mod); + +/* Look up existing class/module */ +struct RClass *c = mrb_class_get(mrb, "String"); +struct RClass *m = mrb_module_get(mrb, "Kernel"); + +/* Define a constant */ +mrb_define_const(mrb, my_class, "VERSION", mrb_str_new_lit(mrb, "1.0")); +``` + +## Defining Methods + +All C methods have the same signature: + +```c +static mrb_value +my_method(mrb_state *mrb, mrb_value self) +{ + /* self is the receiver */ + return mrb_nil_value(); +} +``` + +Register with: + +```c +mrb_define_method(mrb, klass, "name", my_method, MRB_ARGS_NONE()); +mrb_define_class_method(mrb, klass, "name", my_method, MRB_ARGS_REQ(1)); +mrb_define_module_function(mrb, mod, "name", my_method, MRB_ARGS_ANY()); +``` + +### Argument Specifiers + +| Macro | Meaning | +| ----- | ------- | +| `MRB_ARGS_NONE()` | No arguments | +| `MRB_ARGS_REQ(n)` | `n` required arguments | +| `MRB_ARGS_OPT(n)` | `n` optional arguments | +| `MRB_ARGS_ARG(r,o)` | `r` required + `o` optional | +| `MRB_ARGS_REST()` | Splat (`*args`) | +| `MRB_ARGS_BLOCK()` | Block (`&block`) | +| `MRB_ARGS_ANY()` | Any number (same as REST) | +| `MRB_ARGS_KEY(n,rest)` | `n` keyword args, `rest`=1 for `**kw` | + +These can be combined with `|`: + +```c +MRB_ARGS_REQ(1) | MRB_ARGS_OPT(2) | MRB_ARGS_BLOCK() +``` + +## Parsing Arguments + +`mrb_get_args()` extracts arguments from the Ruby call stack: + +```c +mrb_int mrb_get_args(mrb_state *mrb, const char *format, ...); +``` + +### Format Specifiers + +| Spec | Ruby Type | C Type(s) | Notes | +| ---- | --------- | --------- | ----- | +| `o` | any | `mrb_value` | No type check | +| `i` | Numeric | `mrb_int` | Coerces to integer | +| `f` | Numeric | `mrb_float` | Coerces to float | +| `b` | any | `mrb_bool` | Truthiness | +| `n` | String/Symbol | `mrb_sym` | Converts to symbol | +| `s` | String | `const char*, mrb_int` | Pointer + length | +| `z` | String | `const char*` | Null-terminated | +| `S` | String | `mrb_value` | String value | +| `A` | Array | `mrb_value` | Array value | +| `H` | Hash | `mrb_value` | Hash value | +| `C` | Class | `mrb_value` | Class/Module value | +| `c` | Class | `struct RClass*` | Class pointer | +| `a` | Array | `const mrb_value*, mrb_int` | Array pointer + length | +| `d` | C Data | `void*` | Requires `mrb_data_type*` | +| `&` | Block | `mrb_value` | Block argument | +| `*` | rest | `const mrb_value*, mrb_int` | Rest arguments | +| `\|` | — | — | Following args are optional | +| `?` | — | `mrb_bool` | Was previous optional arg given? | +| `:` | keywords | `mrb_kwargs` | Keyword arguments | + +Adding `!` to `S`, `A`, `H`, `C`, `c`, `s`, `z`, `a`, `d` allows `nil` +(returns NULL/zero for nil). + +### Examples + +```c +/* def method(name, count) */ +const char *name; mrb_int len, count; +mrb_get_args(mrb, "si", &name, &len, &count); + +/* def method(required, optional=nil) */ +mrb_value req, opt = mrb_nil_value(); +mrb_get_args(mrb, "o|o", &req, &opt); + +/* def method(*args) */ +const mrb_value *args; mrb_int argc; +mrb_get_args(mrb, "*", &args, &argc); + +/* def method(&block) */ +mrb_value block; +mrb_get_args(mrb, "&", &block); + +/* def method(name:, age: 0) */ +mrb_sym kw_names[] = { mrb_intern_lit(mrb, "name"), mrb_intern_lit(mrb, "age") }; +mrb_value kw_values[2]; +mrb_kwargs kw = { 2, 1, kw_names, kw_values, NULL }; +mrb_get_args(mrb, ":", &kw); +/* kw_values[0] = name (required), kw_values[1] = age (optional, undef if not given) */ +``` + +## Calling Ruby Methods from C + +```c +/* Call obj.method(arg1, arg2) */ +mrb_funcall(mrb, obj, "method", 2, arg1, arg2); + +/* Call with symbol (faster, no string lookup) */ +mrb_funcall_id(mrb, obj, mrb_intern_lit(mrb, "method"), 2, arg1, arg2); + +/* Call with argv array */ +mrb_value argv[] = { arg1, arg2 }; +mrb_funcall_argv(mrb, obj, mrb_intern_lit(mrb, "method"), 2, argv); + +/* Call with block */ +mrb_funcall_with_block(mrb, obj, mid, argc, argv, block); + +/* Yield to block */ +mrb_yield(mrb, block, arg); +mrb_yield_argv(mrb, block, argc, argv); +``` + +## String Operations + +```c +/* Creation */ +mrb_str_new_lit(mrb, "hello") /* from string literal */ +mrb_str_new(mrb, ptr, len) /* from pointer + length */ +mrb_str_new_cstr(mrb, cstr) /* from null-terminated C string */ +mrb_str_new_static(mrb, ptr, len) /* from static data (no copy) */ + +/* Access */ +RSTRING_PTR(str) /* char* pointer */ +RSTRING_LEN(str) /* length */ +mrb_str_to_cstr(mrb, str) /* null-terminated (may copy) */ + +/* Modification */ +mrb_str_cat(mrb, str, ptr, len) /* append bytes */ +mrb_str_cat_cstr(mrb, str, cstr) /* append C string */ +mrb_str_cat_str(mrb, str, str2) /* append String */ + +/* Comparison */ +mrb_str_equal(mrb, str1, str2) /* equality */ +mrb_str_cmp(mrb, str1, str2) /* comparison (-1, 0, 1) */ +``` + +## Array Operations + +```c +/* Creation */ +mrb_ary_new(mrb) /* empty array */ +mrb_ary_new_capa(mrb, capa) /* preallocated */ +mrb_ary_new_from_values(mrb, n, vals) /* from C array */ + +/* Access */ +RARRAY_PTR(ary) /* mrb_value* pointer */ +RARRAY_LEN(ary) /* length */ +mrb_ary_entry(ary, idx) /* get element (no mrb needed) */ + +/* Modification */ +mrb_ary_push(mrb, ary, val) /* append */ +mrb_ary_pop(mrb, ary) /* remove last */ +mrb_ary_shift(mrb, ary) /* remove first */ +mrb_ary_unshift(mrb, ary, val) /* prepend */ +mrb_ary_set(mrb, ary, idx, val) /* set element */ +mrb_ary_splice(mrb, ary, pos, len, rpl) /* splice */ +mrb_ary_concat(mrb, ary, other) /* extend */ +``` + +## Hash Operations + +```c +/* Creation */ +mrb_hash_new(mrb) /* empty hash */ + +/* Access */ +mrb_hash_get(mrb, hash, key) /* get value */ +mrb_hash_fetch(mrb, hash, key, def) /* get with default */ +mrb_hash_key_p(mrb, hash, key) /* key exists? */ +mrb_hash_empty_p(mrb, hash) /* empty? */ +mrb_hash_size(mrb, hash) /* number of entries */ + +/* Modification */ +mrb_hash_set(mrb, hash, key, val) /* set key-value */ +mrb_hash_delete_key(mrb, hash, key) /* delete key */ +mrb_hash_merge(mrb, hash1, hash2) /* merge hash2 into hash1 */ + +/* Iteration */ +mrb_hash_keys(mrb, hash) /* Array of keys */ +mrb_hash_values(mrb, hash) /* Array of values */ +``` + +## Wrapping C Structures + +To expose a C struct to Ruby, use `mrb_data_type` and `Data_Wrap_Struct`: + +```c +/* 1. Define the data type with a name and destructor */ +static void point_free(mrb_state *mrb, void *p) { + mrb_free(mrb, p); +} + +static const mrb_data_type point_type = { + "Point", point_free +}; + +/* 2. Allocate and initialize */ +static mrb_value +point_init(mrb_state *mrb, mrb_value self) +{ + mrb_float x, y; + mrb_get_args(mrb, "ff", &x, &y); + + double *data = (double*)mrb_malloc(mrb, sizeof(double) * 2); + data[0] = x; + data[1] = y; + + DATA_PTR(self) = data; + DATA_TYPE(self) = &point_type; + + return self; +} + +/* 3. Access the wrapped data */ +static mrb_value +point_x(mrb_state *mrb, mrb_value self) +{ + double *data = (double*)mrb_data_get_ptr(mrb, self, &point_type); + return mrb_float_value(mrb, data[0]); +} + +/* 4. Register the class */ +struct RClass *point = mrb_define_class(mrb, "Point", mrb->object_class); +MRB_SET_INSTANCE_TT(point, MRB_TT_CDATA); +mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2)); +mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE()); +``` + +## Exception Handling + +### Raising Exceptions + +```c +mrb_raise(mrb, E_RUNTIME_ERROR, "something went wrong"); +mrb_raisef(mrb, E_ARGUMENT_ERROR, "expected %d, got %d", expected, actual); +mrb_raise(mrb, E_TYPE_ERROR, "wrong type"); +``` + +Common exception classes: `E_RUNTIME_ERROR`, `E_TYPE_ERROR`, +`E_ARGUMENT_ERROR`, `E_RANGE_ERROR`, `E_NAME_ERROR`, +`E_NOMETHOD_ERROR`, `E_NOTIMP_ERROR`, `E_KEY_ERROR`. + +### Catching Exceptions + +```c +/* Check after mrb_load_string or mrb_funcall */ +mrb_value result = mrb_load_string(mrb, code); +if (mrb->exc) { + mrb_print_error(mrb); + mrb->exc = NULL; /* clear exception */ +} + +/* Protected call */ +mrb_bool error; +mrb_value result = mrb_protect(mrb, my_func, data, &error); +if (error) { + /* result contains the exception */ +} +``` + +## Symbols + +```c +/* Create symbol from string */ +mrb_sym sym = mrb_intern_lit(mrb, "name"); /* from literal */ +mrb_sym sym = mrb_intern_cstr(mrb, cstr); /* from C string */ +mrb_sym sym = mrb_intern(mrb, ptr, len); /* from pointer + length */ + +/* Symbol to string */ +const char *name = mrb_sym_name(mrb, sym); +mrb_int len; +const char *name = mrb_sym_name_len(mrb, sym, &len); +``` + +## Instance Variables + +```c +/* Get/set instance variables on an object */ +mrb_iv_get(mrb, obj, mrb_intern_lit(mrb, "@x")); +mrb_iv_set(mrb, obj, mrb_intern_lit(mrb, "@x"), val); +mrb_iv_defined(mrb, obj, mrb_intern_lit(mrb, "@x")); +mrb_iv_remove(mrb, obj, mrb_intern_lit(mrb, "@x")); +``` + +## Global Variables + +```c +mrb_gv_get(mrb, mrb_intern_lit(mrb, "$verbose")); +mrb_gv_set(mrb, mrb_intern_lit(mrb, "$verbose"), mrb_true_value()); +``` + +## Class Variables + +```c +mrb_cv_get(mrb, klass, mrb_intern_lit(mrb, "@@count")); +mrb_cv_set(mrb, klass, mrb_intern_lit(mrb, "@@count"), mrb_fixnum_value(0)); +``` + +## Loading and Executing Code + +```c +/* Load and execute a string (requires mruby-compiler gem) */ +mrb_value result = mrb_load_string(mrb, "1 + 2"); + +/* Load and execute a file */ +FILE *f = fopen("script.rb", "r"); +mrb_value result = mrb_load_file(mrb, f); +fclose(f); + +/* Load precompiled bytecode (no compiler needed) */ +mrb_value result = mrb_load_irep(mrb, bytecode_array); +``` + +## GC Arena + +When creating many temporary Ruby objects in C, use the GC arena to +prevent them from being collected prematurely: + +```c +int ai = mrb_gc_arena_save(mrb); +/* create temporary objects here */ +mrb_gc_arena_restore(mrb, ai); +``` + +See [gc-arena-howto.md](gc-arena-howto.md) for details. + +## Memory Allocation + +```c +void *p = mrb_malloc(mrb, size); /* raises on failure */ +void *p = mrb_calloc(mrb, nmemb, size); /* zero-initialized */ +void *p = mrb_realloc(mrb, ptr, size); /* resize */ +mrb_free(mrb, p); /* free */ + +/* NULL-returning variants (for custom error handling) */ +void *p = mrb_malloc_simple(mrb, size); +void *p = mrb_realloc_simple(mrb, ptr, size); +``` + +## Type Conversion + +```c +mrb_obj_as_string(mrb, val) /* to_s */ +mrb_inspect(mrb, val) /* inspect */ +mrb_any_to_s(mrb, val) /* default to_s */ +mrb_str_to_integer(mrb, str, base, badcheck) /* String to Integer */ +mrb_str_to_dbl(mrb, str, badcheck) /* String to Float */ +mrb_ensure_float_type(mrb, val) /* ensure Float */ +``` + +## Object Comparison + +```c +mrb_equal(mrb, a, b) /* Ruby == */ +mrb_eql(mrb, a, b) /* Ruby eql? */ +mrb_obj_eq(mrb, a, b) /* Ruby equal? (identity) */ +mrb_cmp(mrb, a, b) /* Ruby <=> (returns mrb_int) */ +``` + +## Object Inspection + +```c +mrb_obj_classname(mrb, obj) /* class name as C string */ +mrb_obj_class(mrb, obj) /* class as RClass* */ +mrb_obj_is_kind_of(mrb, obj, klass) /* is_a? / kind_of? */ +mrb_obj_respond_to(mrb, klass, mid) /* respond_to? */ +mrb_obj_id(obj) /* object_id */ +mrb_obj_freeze(mrb, obj) /* freeze */ +mrb_obj_dup(mrb, obj) /* dup */ +``` + +## Compile-Time Flags + +When compiling C code that uses mruby, you must use the same flags as +the library was built with. Use `mruby-config` to get them: + +```console +$ build/host/bin/mruby-config --cflags # compiler flags +$ build/host/bin/mruby-config --ldflags # linker flags +$ build/host/bin/mruby-config --libs # libraries +``` + +Key macros that affect ABI: + +| Macro | Effect | +| ----- | ------ | +| `MRB_NO_BOXING` | Struct-based values (larger, debuggable) | +| `MRB_WORD_BOXING` | Single-word values (fast, 32-bit safe) | +| `MRB_NAN_BOXING` | NaN-tagged values (default on 32-bit) | +| `MRB_NO_FLOAT` | Disable Float support | +| `MRB_INT64` | 64-bit integers | +| `MRB_USE_FLOAT32` | 32-bit floats | + +Mismatching these between library and application causes silent +data corruption. diff --git a/doc/guides/getting-started.md b/doc/guides/getting-started.md new file mode 100644 index 000000000..9510fde79 --- /dev/null +++ b/doc/guides/getting-started.md @@ -0,0 +1,281 @@ + + +# Getting Started with mruby + +This guide walks you through building mruby, running your first Ruby program, +and embedding mruby in a C application. + +## Prerequisites + +You need: + +- C compiler (`gcc` or `clang`) +- Ruby 2.5 or later (for the build system) +- `rake` (bundled with Ruby) +- `git` (optional, for cloning the source) + +## Building mruby + +Clone the repository and build: + +```console +$ git clone https://github.com/mruby/mruby.git +$ cd mruby +$ rake +``` + +This compiles the default configuration and produces: + +- `bin/mruby` — Ruby script interpreter +- `bin/mirb` — interactive Ruby shell +- `bin/mrbc` — bytecode compiler +- `build/host/lib/libmruby.a` — library for embedding + +## Running Ruby Code + +### Interactive shell + +```console +$ bin/mirb +mirb - Pair interactive mruby +> puts "Hello, mruby!" +Hello, mruby! + => nil +> 1 + 2 + => 3 +``` + +### Running a script file + +Create `hello.rb`: + +```ruby +puts "Hello from mruby!" +``` + +Run it: + +```console +$ bin/mruby hello.rb +Hello from mruby! +``` + +### One-liner + +```console +$ bin/mruby -e 'puts "Hello!"' +Hello! +``` + +## Compiling to Bytecode + +mruby can compile Ruby scripts to bytecode (`.mrb` files) for faster +loading and deployment without source code: + +```console +$ bin/mrbc hello.rb # produces hello.mrb +$ bin/mruby -b hello.mrb # run bytecode +Hello from mruby! +``` + +You can also generate C source from Ruby scripts: + +```console +$ bin/mrbc -Bhello_code hello.rb # produces hello.c with byte array +``` + +This generates a C file with a `const uint8_t hello_code[]` array that +can be loaded with `mrb_load_irep()` in your C application. + +## Embedding mruby in C + +The primary use case of mruby is embedding in C/C++ applications. + +### Minimal example + +Create `embed.c`: + +```c +#include +#include + +int main(void) +{ + mrb_state *mrb = mrb_open(); + if (!mrb) return 1; + + mrb_load_string(mrb, "puts 'Hello from embedded mruby!'"); + if (mrb->exc) { + mrb_print_error(mrb); + } + + mrb_close(mrb); + return 0; +} +``` + +### Compile and link + +Use `mruby-config` to get the correct compiler and linker flags: + +```console +$ gcc -I include `build/host/bin/mruby-config --cflags` embed.c \ + `build/host/bin/mruby-config --ldflags --libs` -o embed +$ ./embed +Hello from embedded mruby! +``` + +**Important**: Always use `mruby-config --cflags` when compiling code +that uses mruby. The build configuration may define macros (such as +`MRB_NO_BOXING` or `MRB_USE_BIGINT`) that change the internal data +layout. Compiling without these flags causes silent data corruption. + +### Calling Ruby from C + +```c +#include +#include +#include +#include + +int main(void) +{ + mrb_state *mrb = mrb_open(); + + /* Define a Ruby method */ + mrb_load_string(mrb, "def greet(name) \"Hello, #{name}!\" end"); + + /* Call it from C */ + mrb_value result = mrb_funcall(mrb, mrb_top_self(mrb), + "greet", 1, mrb_str_new_lit(mrb, "World")); + printf("%s\n", mrb_str_to_cstr(mrb, result)); + + mrb_close(mrb); + return 0; +} +``` + +### Defining C functions callable from Ruby + +```c +#include +#include + +static mrb_value +my_add(mrb_state *mrb, mrb_value self) +{ + mrb_int a, b; + mrb_get_args(mrb, "ii", &a, &b); + return mrb_fixnum_value(a + b); +} + +int main(void) +{ + mrb_state *mrb = mrb_open(); + + /* Define method on Kernel (available everywhere) */ + mrb_define_method(mrb, mrb->kernel_module, "my_add", + my_add, MRB_ARGS_REQ(2)); + + mrb_load_string(mrb, "puts my_add(3, 4)"); /* prints 7 */ + + mrb_close(mrb); + return 0; +} +``` + +## Loading Precompiled Bytecode + +For deployment without the compiler gem, precompile your Ruby code: + +```console +$ bin/mrbc -Bruby_code app.rb +``` + +Then load in C: + +```c +#include +#include +#include "app.c" /* contains ruby_code[] */ + +int main(void) +{ + mrb_state *mrb = mrb_open(); + mrb_load_irep(mrb, ruby_code); + if (mrb->exc) { + mrb_print_error(mrb); + } + mrb_close(mrb); + return 0; +} +``` + +This approach does not require the `mruby-compiler` gem, resulting in +a smaller binary. + +## Customizing the Build + +mruby's functionality is controlled by the build configuration file. +The default is `build_config/default.rb`. + +### Using a custom configuration + +```console +$ MRUBY_CONFIG=build_config/minimal.rb rake +``` + +### Selecting gems + +Gems add features to mruby. A minimal configuration: + +```ruby +MRuby::Build.new do |conf| + conf.toolchain :gcc + + # Core language extensions + conf.gem core: 'mruby-array-ext' + conf.gem core: 'mruby-string-ext' + conf.gem core: 'mruby-hash-ext' + + # Tools + conf.gem core: 'mruby-bin-mruby' # mruby command + conf.gem core: 'mruby-bin-mirb' # interactive shell + conf.gem core: 'mruby-bin-mrbc' # bytecode compiler + + # Compiler (needed for mrb_load_string) + conf.gem core: 'mruby-compiler' +end +``` + +### Using a gembox + +Gemboxes are predefined collections of gems: + +```ruby +MRuby::Build.new do |conf| + conf.toolchain :gcc + conf.gembox 'default' # standard set of gems +end +``` + +## Amalgamation (Single-File Build) + +For the simplest integration, use amalgamation to combine all mruby +source into a single `mruby.c` and `mruby.h`: + +```console +$ rake amalgam +$ gcc -I build/host/amalgam your_app.c build/host/amalgam/mruby.c -o your_app -lm +``` + +See [amalgamation.md](amalgamation.md) for details. + +## What's Next + +- [Compile](compile.md) — full build system reference +- [mrbgems](mrbgems.md) — creating and using gems +- [Linking](link.md) — linking `libmruby` to applications +- [Build-time Configurations](mrbconf.md) — compile-time options +- [GC Arena](gc-arena-howto.md) — managing GC arena in C extensions +- [Limitations](../limitations.md) — differences from CRuby diff --git a/doc/internal/architecture.md b/doc/internal/architecture.md new file mode 100644 index 000000000..dadccdcf8 --- /dev/null +++ b/doc/internal/architecture.md @@ -0,0 +1,293 @@ + + +# mruby Architecture + +This document provides a map of mruby's internals for developers who +want to understand, debug, or contribute to the codebase. + +## Overview + +mruby's execution pipeline: + +```text +Ruby source → Parser → AST → Code Generator → Bytecode (irep) + ↓ + VM → Result +``` + +The design priority is **memory > performance > readability**. + +## Object Model + +All heap-allocated Ruby objects share a common header (`MRB_OBJECT_HEADER`): + +```text +struct RBasic (8 bytes on 64-bit) +┌──────────────┬─────┬──────────┬────────┬───────┐ +│ RClass *c │ tt │ gc_color │ frozen │ flags │ +│ (class ptr) │ 8b │ 3b │ 1b │ 20b │ +└──────────────┴─────┴──────────┴────────┴───────┘ +``` + +All object structs embed this header via `MRB_OBJECT_HEADER`: + +| Struct | Ruby Type | Extra Fields | +| ------ | --------- | ------------ | +| `RObject` | Object instances | `iv` (instance variables) | +| `RClass` | Class/Module | `iv`, `mt` (method table), `super` | +| `RString` | String | embedded or heap buffer, length | +| `RArray` | Array | embedded or heap buffer, length | +| `RHash` | Hash | hash table or k-v array | +| `RProc` | Proc/Lambda | `irep` or C function, environment | +| `RData` | C data wrapper | `void *data`, `mrb_data_type` | +| `RFiber` | Fiber | `mrb_context` | +| `RException` | Exception | `iv` | + +Immediate values (Integer, Symbol, `true`, `false`, `nil`) are encoded +directly in `mrb_value` without heap allocation. The encoding depends on +the boxing mode (see [boxing.md](boxing.md)). + +Objects must fit within 5 words (`mrb_static_assert_object_size`). + +## Virtual Machine + +### Execution Context + +The VM uses two stacks stored in `mrb_context`: + +```text +mrb_context +├── stbase..stend value stack (mrb_value[]) +├── cibase..ciend call info stack (mrb_callinfo[]) +├── ci current call frame (→ cibase[n]) +└── status fiber state +``` + +Each method call pushes a `mrb_callinfo` frame: + +```text +mrb_callinfo +├── mid method symbol +├── proc current RProc +├── stack pointer into value stack +├── pc program counter (bytecode position) +├── n, nk argument counts +├── cci nonzero if called from C +└── u.env / u.target_class +``` + +The value stack is register-based: local variables and temporaries +occupy fixed register slots (determined at compile time by `nregs`). + +### Dispatch Loop + +The main loop in `mrb_vm_run()` (`src/vm.c`) decodes and dispatches +opcodes. Each opcode operates on registers: + +```text +OP_MOVE R(a) = R(b) +OP_LOADI R(a) = integer +OP_ADD R(a) = R(a) + R(a+1) +OP_SEND R(a) = call R(a).method(R(a+1)..R(a+n)) +OP_RETURN return R(a) +``` + +See [opcode.md](opcode.md) for the full instruction set. + +### Method Dispatch + +When `OP_SEND` executes: + +1. Look up method in receiver's class method table (`mt`) +2. Walk superclass chain if not found +3. If method is a C function (`MRB_METHOD_CFUNC_P`), call directly +4. If method is Ruby (irep-based), push new `mrb_callinfo` and jump + +Method tables use a hash map (`mrb_mt_tbl`). A per-state method cache +speeds up repeated lookups. + +### Exception Handling + +mruby uses `setjmp`/`longjmp` for exception unwinding (or C++ exceptions +if `enable_cxx_exception` is configured). The `rescue`/`ensure` entries +are tracked in `mrb_callinfo` entries and unwound when an exception +propagates. + +## Garbage Collector + +### Tri-Color Mark-and-Sweep + +The GC uses tri-color marking with incremental execution: + +| Color | Meaning | +| ----- | ------- | +| White | Unmarked — candidate for collection | +| Gray | Marked but children not yet scanned | +| Black | Fully marked (reachable) | +| Red | Static/ROM — never collected | + +### GC Phases + +```text +GC_STATE_ROOT → GC_STATE_MARK → GC_STATE_SWEEP → GC_STATE_ROOT +``` + +1. **Root marking**: marks objects directly reachable from the VM + (stack, globals, arena) +2. **Incremental marking**: processes gray objects from `gray_stack[]`, + marking their children. Runs in small steps between VM instructions. +3. **Sweep**: iterates heap pages, freeing white objects and flipping + the white bit for the next cycle + +### Write Barriers + +When a black object stores a reference to a white object, a write +barrier is required to prevent premature collection: + +```c +mrb_field_write_barrier(mrb, parent, child); /* specific field */ +mrb_write_barrier(mrb, obj); /* general */ +``` + +The barrier paints the parent gray, adding it back to the scan queue. + +### GC Arena + +The arena (`gc.arena[]`) protects newly created objects from collection +before they are stored in a reachable location. C extensions must save +and restore the arena index when creating many temporary objects: + +```c +int ai = mrb_gc_arena_save(mrb); +/* ... create temporary objects ... */ +mrb_gc_arena_restore(mrb, ai); +``` + +See [../guides/gc-arena-howto.md](../guides/gc-arena-howto.md) for details. + +### Heap Structure + +Objects are allocated from fixed-size heap pages (`HEAP_PAGE_SIZE` +objects per page). Each page maintains a freelist of available slots. +Dead objects are returned to their page's freelist during sweep. + +The `mrb_gc_add_region()` API allows pre-allocating contiguous heap +regions for reduced fragmentation and faster allocation. + +### Generational Mode + +Optional generational GC (`mrb_gc_generational_mode_set`) treats +objects surviving a full GC as "old generation" and performs minor +collections that only scan young objects. + +## Compiler Pipeline + +### Stage 1: Parser (`mrbgems/mruby-compiler/core/parse.y`) + +The yacc/bison grammar (~16K lines) produces an AST of `mrb_ast_node` +linked structures. The parser tracks lexer state (expression context, +heredocs, string interpolation) and local variable scopes. + +### Stage 2: Code Generator (`mrbgems/mruby-compiler/core/codegen.c`) + +Walks the AST and emits bytecode into `mrb_irep` structures: + +```text +mrb_irep +├── iseq[] instruction stream (bytecode) +├── pool[] constant pool (strings, numbers) +├── syms[] symbol table (method names, variable names) +├── reps[] child ireps (nested methods, blocks) +├── nlocals local variable count +└── nregs register count (locals + temporaries) +``` + +The code generator: + +- Assigns register slots for local variables and temporaries +- Emits instructions for each AST node type +- Builds jump tables for control flow (if/unless/while/for) +- Encodes exception handler ranges for rescue/ensure + +### Stage 3: Execution + +The irep is wrapped in an `RProc` and executed by the VM. Alternative +loading paths: + +- `mrb_load_string()` — compile from source and execute +- `mrb_load_irep()` — load precompiled `.mrb` bytecode +- `mrbc` tool — ahead-of-time compilation to `.mrb` or C array + +## Source File Map + +### Core (`src/`) + +| File | Responsibility | +| ---- | -------------- | +| `vm.c` | Bytecode dispatch loop, method invocation | +| `state.c` | `mrb_state` init/close, irep management | +| `gc.c` | Garbage collector (mark-sweep, incremental) | +| `class.c` | Class/module definition, method tables | +| `object.c` | Core object operations | +| `variable.c` | Instance/class/global variables, object shapes | +| `proc.c` | Proc/Lambda/closure handling | +| `array.c` | Array implementation | +| `string.c` | String implementation (embedded, shared, heap) | +| `hash.c` | Hash implementation (open addressing) | +| `numeric.c` | Integer/Float arithmetic | +| `symbol.c` | Symbol table and interning | +| `range.c` | Range implementation | +| `error.c` | Exception creation, raise, backtrace | +| `kernel.c` | Kernel module methods | +| `load.c` | `.mrb` bytecode loading | +| `dump.c` | Bytecode serialization (write `.mrb`) | +| `print.c` | Print/puts/p output | +| `backtrace.c` | Stack trace generation | + +### Compiler (`mrbgems/mruby-compiler/core/`) + +| File | Responsibility | +| ---- | -------------- | +| `parse.y` | Yacc grammar → AST | +| `y.tab.c` | Generated parser (from parse.y) | +| `codegen.c` | AST → bytecode (irep) | +| `node.h` | AST node type definitions | + +### Key Headers (`include/mruby/`) + +| Header | Contents | +| ------ | -------- | +| `mruby.h` | `mrb_state`, core API declarations | +| `value.h` | `mrb_value`, type enums, value macros | +| `object.h` | `RBasic`, `RObject`, object header | +| `class.h` | `RClass`, method table types | +| `string.h` | `RString`, string macros | +| `array.h` | `RArray`, array macros | +| `hash.h` | `RHash`, hash API | +| `data.h` | `RData`, C data wrapping | +| `irep.h` | `mrb_irep`, bytecode structures | +| `compile.h` | Compiler context, `mrb_load_string` | +| `boxing_*.h` | Value boxing implementations | + +## mrbgems System + +Gems are the module system for mruby. Each gem lives in +`mrbgems/mruby-*/` and contains: + +```text +mruby-example/ +├── mrbgem.rake gem specification (name, deps, bins) +├── src/ C source files +├── mrblib/ Ruby source files (compiled to bytecode) +├── include/ C headers +├── test/ mrbtest test files +└── bintest/ binary test files (CRuby) +``` + +At build time, gem Ruby files are compiled with `mrbc` and linked into +`libmruby.a`. Gem initialization runs in dependency order via +`gem_init.c` (auto-generated). + +GemBoxes (`mrbgems/*.gembox`) define named collections of gems +(e.g., `default.gembox` includes `stdlib`, `stdlib-ext`, `stdlib-io`, +`math`, `metaprog`, and binary tools).