<!-- summary: Compiler Pipeline Internals -->

# Compiler Pipeline Internals

This document describes mruby's compilation pipeline for developers
working on the parser, code generator, or bytecode format.

**Read this if you are:** adding new syntax or modifying the parser,
debugging codegen issues (wrong registers, missing opcodes),
working with the `.mrb` binary format, or understanding how Ruby
constructs map to bytecode.

## Pipeline Overview

```text
Ruby source
    |
    v
 Lexer/Parser (parse.y)
    |
    v
   AST (mrb_ast_node)
    |
    v
 Code Generator (codegen.c)
    |
    v
 Bytecode (mrb_irep)
    |
    v
 VM execution  -or-  .mrb binary file
```

## Stage 1: Lexer and Parser

The lexer and parser are combined in a single Lrama/Bison grammar
file: `mrbgems/mruby-compiler/core/parse.y`.

### Parser State

The parser maintains extensive state in `mrb_parser_state`:

- **lstate**: current lexer state (EXPR_BEG, EXPR_END, EXPR_ARG,
  EXPR_DOT, EXPR_FNAME, etc.). Controls how tokens like `+`/`-`
  are interpreted (sign vs operator) and whether newlines are
  significant.
- **locals**: stack of local variable lists (one per scope), stored
  as cons-lists of symbols.
- **lex_strterm**: string/heredoc parsing state for handling nested
  interpolation.
- **cond_stack**, **cmdarg_stack**: bit stacks tracking
  conditional and command argument contexts.
- **tree**: root AST node after successful parse.
- **error_buffer**: accumulated parse errors.

### AST Nodes

The parser produces an AST using two node types:

- **Cons-list nodes**: traditional binary tree pairs (car/cdr)
- **Variable-sized nodes**: have a header with `node_type`, `lineno`,
  and `filename_index`

Key node types include `NODE_SCOPE` (new variable scope),
`NODE_STMTS` (statement sequence), `NODE_IF`, `NODE_WHILE`,
`NODE_CALL` (method call), `NODE_DEF` (method definition),
`NODE_CLASS`, `NODE_RESCUE`, `NODE_ENSURE`, etc. See
`mrbgems/mruby-compiler/core/node.h` for the complete list.

### Local Variable Tracking

Local variables are tracked per-scope during parsing:

- `local_add(sym)`: register a new local variable in current scope
- `local_var_p(sym)`: check if a symbol is a local variable (affects
  whether an identifier is parsed as a method call or variable
  reference)

## Stage 2: Code Generator

The code generator (`mrbgems/mruby-compiler/core/codegen.c`) walks
the AST and emits bytecode into `mrb_irep` structures.

### Codegen Scope

Each lexical scope (method, block, class body) has its own
`codegen_scope`:

```text
codegen_scope
+-- sp             current register index (stack pointer)
+-- pc             current instruction count
+-- nlocals        number of local variables
+-- nregs          maximum register index used
+-- lv             local variable list
+-- iseq[]         instruction sequence (grows dynamically)
+-- pool[]         literal pool (strings, numbers)
+-- syms[]         symbol table (method/variable names)
+-- reps[]         child ireps (nested methods/blocks)
+-- catch_table[]  exception handler entries
+-- loop           current loop context stack
+-- prev           parent scope
+-- mscope         true if method/module/class scope
```

Scopes nest for blocks, method definitions, and class/module bodies.
Each scope produces one `mrb_irep`.

### Register Allocation

The code generator uses a simple stack-based register allocator:

- Register 0 is always `self`
- Registers 1..nlocals-1 are local variables (in declaration order)
- Registers nlocals..nregs-1 are temporaries

`push()` increments `sp` and tracks the high-water mark in `nregs`.
`pop()` decrements `sp`. The allocator is linear - it does not
reuse temporaries within an expression.

### Instruction Emission

Instructions are emitted via helper functions:

- `genop_0(opcode)`: no operands
- `genop_1(opcode, a)`: one operand (auto-extends with OP_EXT1
  if a > 255)
- `genop_2(opcode, a, b)`: two operands (auto-extends with
  OP_EXT1/2/3 as needed)
- `genop_3(opcode, a, b, c)`: three operands
- `genop_W(opcode, a)`: 24-bit operand
- `genop_2S(opcode, a, b)`: one 8-bit + one 16-bit operand

### Peephole Optimization

The code generator performs limited peephole optimizations, such as
removing redundant `OP_MOVE` instructions and combining consecutive
literal loads. Optimization is disabled at jump targets and when
`no_optimize` is set in the compilation context.

### Loop Context

Loop constructs (`while`, `until`, `for`, blocks) push a
`loopinfo` structure that tracks jump destinations:

- `pc0`: destination for `next`
- `pc1`: destination for `redo`
- `pc2`: destination for `break`

Loop types (`LOOP_NORMAL`, `LOOP_BLOCK`, `LOOP_FOR`, `LOOP_BEGIN`,
`LOOP_RESCUE`) determine how `break`/`next`/`redo` behave.

## IRep Structure

The compiled bytecode is stored in `mrb_irep` (Instruction
REPresentation):

```text
mrb_irep
+-- iseq[]      instruction sequence (mrb_code array)
+-- pool[]      literal pool (mrb_irep_pool entries)
+-- syms[]      symbol table (mrb_sym array)
+-- reps[]      child ireps (nested scopes)
+-- lv[]        local variable names (for debugging)
+-- nlocals     local variable count
+-- nregs       register count (locals + temporaries)
+-- ilen        instruction count
+-- plen        pool entry count
+-- slen        symbol count
+-- rlen        child irep count
+-- clen        catch handler count
+-- debug_info  source file/line mapping
```

### Literal Pool

Pool entries store constants referenced by instructions:

| Type             | Tag | Description                     |
| ---------------- | --- | ------------------------------- |
| `IREP_TT_STR`    | 0   | Dynamic string (heap allocated) |
| `IREP_TT_SSTR`   | 2   | Static string (read-only)       |
| `IREP_TT_INT32`  | 1   | 32-bit integer                  |
| `IREP_TT_INT64`  | 3   | 64-bit integer                  |
| `IREP_TT_FLOAT`  | 5   | Floating-point number           |
| `IREP_TT_BIGINT` | 7   | Arbitrary-precision integer     |

The code generator deduplicates pool entries: identical strings
and equal numeric values share the same pool index.

### Catch Handler Table

Exception handler entries are appended after the instruction
sequence in memory:

```text
mrb_irep_catch_handler
+-- type       MRB_CATCH_RESCUE (0) or MRB_CATCH_ENSURE (1)
+-- begin[4]   start PC of protected range
+-- end[4]     end PC of protected range
+-- target[4]  jump target when handler fires
```

During exception unwinding, handlers are searched in reverse order
(last to first) for the current PC.

## Operand Encoding

Standard instructions use 8-bit operands. When a value exceeds
255, extension prefixes widen operands to 16 bits:

| Prefix    | Effect                            |
| --------- | --------------------------------- |
| `OP_EXT1` | First operand (a) becomes 16-bit  |
| `OP_EXT2` | Second operand (b) becomes 16-bit |
| `OP_EXT3` | Both a and b become 16-bit        |

Instruction formats:

| Format | Layout                        | Size    |
| ------ | ----------------------------- | ------- |
| Z      | opcode only                   | 1 byte  |
| B      | opcode + a(8)                 | 2 bytes |
| BB     | opcode + a(8) + b(8)          | 3 bytes |
| BBB    | opcode + a(8) + b(8) + c(8)   | 4 bytes |
| BS     | opcode + a(8) + b(16)         | 4 bytes |
| BSS    | opcode + a(8) + b(16) + c(16) | 6 bytes |
| S      | opcode + a(16)                | 3 bytes |
| W      | opcode + a(24)                | 4 bytes |

See [opcode.md](opcode.md) for the full instruction table.

## OP_ENTER: Argument Specification

`OP_ENTER` encodes a method's argument layout in a 24-bit value
(W format). The bit fields are defined by the `MRB_ARGS_*` macros:

```text
Bits 23       no-block flag
Bits 18-22    required argument count (5 bits, 0-31)
Bits 13-17    optional argument count (5 bits, 0-31)
Bit  12       rest argument flag (*args)
Bits 7-11     post-rest argument count (5 bits, 0-31)
Bits 2-6      keyword argument count (5 bits, 0-31)
Bit  1        keyword rest flag (**kwargs)
Bit  0        block argument flag (&block)
```

Example: `def foo(a, b=1, *rest, &block)` produces an aspec with
1 required, 1 optional, rest flag set, and block flag set.

## Presym: Compile-Time Symbols

The presym system pre-allocates symbol IDs at build time for
frequently used method names and operators. This avoids runtime
string interning for common symbols.

Generated by `lib/mruby/presym.rb`, the presym table maps symbol
names to compile-time constants:

| Macro             | Example               | Symbol        |
| ----------------- | --------------------- | ------------- |
| `MRB_SYM(name)`   | `MRB_SYM(initialize)` | `:initialize` |
| `MRB_SYM_B(name)` | `MRB_SYM_B(map)`      | `:map!`       |
| `MRB_SYM_Q(name)` | `MRB_SYM_Q(nil)`      | `:nil?`       |
| `MRB_SYM_E(name)` | `MRB_SYM_E(name)`     | `:name=`      |
| `MRB_OPSYM(op)`   | `MRB_OPSYM(add)`      | `:+`          |
| `MRB_IVSYM(name)` | `MRB_IVSYM(name)`     | `:@name`      |
| `MRB_CVSYM(name)` | `MRB_CVSYM(count)`    | `:@@count`    |
| `MRB_GVSYM(name)` | `MRB_GVSYM(stdout)`   | `:$stdout`    |

## Binary Format (.mrb)

Precompiled bytecode is stored in the RITE binary format:

```text
Header: "RITE" magic + version ("0400") + CRC + size
Section IREP: instruction sequences, pools, symbols
Section DBG:  debug info (optional, filename/line mapping)
Section LVAR: local variable names (optional)
Footer: "END\0"
```

Loading functions:

- `mrb_load_irep(mrb, bin)`: load and execute from byte array
- `mrb_load_irep_buf(mrb, buf, len)`: load with explicit size
  (safer)
- `mrb_read_irep(mrb, bin)`: load without executing (returns
  `mrb_irep*`)
- `mrb_load_irep_file(mrb, fp)`: load from file

The `mrbc` command-line tool performs ahead-of-time compilation:

```shell
mrbc -o output.mrb source.rb      # binary format
mrbc -Boutput source.rb           # C array format
```

## Compilation Limits

| Limit                  | Value                         |
| ---------------------- | ----------------------------- |
| Max nesting depth      | 256 (`MRB_CODEGEN_LEVEL_MAX`) |
| Max local variables    | 255 (uint16 `nlocals`)        |
| Max symbols per irep   | 65535                         |
| Max operand (standard) | 255 (8-bit)                   |
| Max operand (extended) | 65535 (16-bit)                |

## Source Files

| File                                    | Contents                  |
| --------------------------------------- | ------------------------- |
| `mrbgems/mruby-compiler/core/parse.y`   | Lrama/Bison grammar       |
| `mrbgems/mruby-compiler/core/y.tab.c`   | Generated parser          |
| `mrbgems/mruby-compiler/core/codegen.c` | Code generator            |
| `mrbgems/mruby-compiler/core/node.h`    | AST node types            |
| `include/mruby/irep.h`                  | IRep structure definition |
| `include/mruby/compile.h`               | Compiler context API      |
| `include/mruby/ops.h`                   | Opcode definitions        |
| `src/load.c`                            | Binary format loader      |
| `src/dump.c`                            | Binary format writer      |
| `lib/mruby/presym.rb`                   | Presym table generator    |
