mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
f5ca906852
The if/unless nil? optimization called codegen() on the call node's receiver, but a bare `nil?` is parsed as an FCALL whose receiver is NULL. codegen(NULL) emits OP_LOADNIL, so the JMPNIL was testing the literal nil instead of self, making `if nil?` always behave as `if nil.nil?` (always true) and `unless nil?` always skip its body. Load self when the receiver is implicit. Fixes #6874. Co-authored-by: Claude <noreply@anthropic.com>
7237 lines
208 KiB
C
7237 lines
208 KiB
C
/*
|
|
** codegen.c - mruby code generator
|
|
**
|
|
** See Copyright Notice in mruby.h
|
|
*/
|
|
|
|
/*
|
|
* ## Code Generator
|
|
*
|
|
* This file implements the mruby code generator, a crucial component of the mruby
|
|
* compilation pipeline. Its primary responsibility is to translate the Abstract
|
|
* Syntax Tree (AST), produced by the parser, into mruby bytecode (Instruction
|
|
* Sequence - iseq).
|
|
*
|
|
* ### Key Operational Aspects:
|
|
*
|
|
* - **AST Traversal:** The generator walks through the AST nodes, processing each
|
|
* node type and emitting corresponding bytecode instructions.
|
|
* - **Scope Management:** It manages lexical scopes, keeping track of local
|
|
* variables, upvalues (variables from enclosing scopes), and register
|
|
* allocation within each scope. This is vital for correct variable access
|
|
* and lifetime.
|
|
* - **Opcode Generation:** For different AST node types (e.g., literals,
|
|
* arithmetic operations, control flow statements, method calls, variable
|
|
* assignments), specific opcodes are generated. This involves selecting the
|
|
* appropriate instruction and its operands.
|
|
* - **Loop Handling:** It provides mechanisms to correctly generate bytecode for
|
|
* various loop constructs (e.g., `while`, `for`, `until`), including managing
|
|
* `break`, `next`, and `redo` statements by patching jump addresses.
|
|
* - **Instruction Sequence (iseq):** The output of this process is an `mrb_irep`
|
|
* structure, which contains the generated instruction sequence (iseq), literal
|
|
* pools, symbol tables, and other metadata required for execution by the
|
|
* mruby virtual machine.
|
|
* - **Error Handling:** Includes mechanisms for reporting errors encountered
|
|
* during code generation, such as syntax errors not caught by the parser or
|
|
* semantic errors.
|
|
*
|
|
* This code generator is essential for transforming human-readable mruby code
|
|
* into a format that the mruby VM can execute efficiently.
|
|
*/
|
|
|
|
#include <mruby.h>
|
|
#include <mruby/compile.h>
|
|
#include <mruby/proc.h>
|
|
#include <mruby/dump.h>
|
|
#include <mruby/numeric.h>
|
|
#include <mruby/string.h>
|
|
#include <mruby/debug.h>
|
|
#include "node.h"
|
|
#include <mruby/opcode.h>
|
|
#include <mruby/re.h>
|
|
#include <mruby/throw.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <mruby/internal.h>
|
|
|
|
/* Wrappers for mruby's memory management functions. */
|
|
#define mrbc_malloc(s) mrb_basic_alloc_func(NULL,(s)) /* Allocates memory. */
|
|
#define mrbc_realloc(p,s) mrb_basic_alloc_func((p),(s)) /* Reallocates memory. */
|
|
#define mrbc_free(p) mrb_basic_alloc_func((p),0) /* Frees memory. */
|
|
|
|
#ifndef MRB_CODEGEN_LEVEL_MAX
|
|
/* Maximum recursion depth for the codegen function to prevent stack overflows. */
|
|
#define MRB_CODEGEN_LEVEL_MAX 256
|
|
#endif
|
|
|
|
/* Maximum number of arguments for some opcodes like OP_SUPER or OP_ARGARY. */
|
|
#define MAXARG_S (1<<16)
|
|
|
|
/* Macro to detect (0 . 0) separators in literal arrays */
|
|
#define IS_LITERAL_DELIM(node) \
|
|
((node) && (node)->car && \
|
|
(node)->car->car == NULL && \
|
|
(node)->car->cdr == NULL)
|
|
|
|
typedef mrb_ast_node node;
|
|
typedef struct mrb_parser_state parser_state;
|
|
|
|
/* Represents the different kinds of loops or blocks encountered during code generation. */
|
|
enum looptype {
|
|
LOOP_NORMAL, /* A standard loop construct like `while` or `until`. */
|
|
LOOP_BLOCK, /* A block or lambda. */
|
|
LOOP_FOR, /* A `for` loop. */
|
|
LOOP_BEGIN, /* A `begin...end` block (often with `rescue` or `ensure`). */
|
|
LOOP_RESCUE, /* The `rescue` part of a `begin...rescue...end` block. */
|
|
};
|
|
|
|
/* Information about a loop currently being compiled, used for `break`, `next`, `redo`, etc. */
|
|
struct loopinfo {
|
|
enum looptype type; /* Type of the loop, using `enum looptype`. */
|
|
uint32_t pc0; /* Jump destination for `next`, or start of loop for `retry` in `rescue`. */
|
|
uint32_t pc1; /* Jump destination for `redo`. */
|
|
uint32_t pc2; /* Jump destination for `break`. */
|
|
int reg; /* Register to store the loop's return value (e.g., from `break val`), or -1 if no value. */
|
|
struct loopinfo *prev; /* Pointer to the previous `loopinfo` in a linked list (for nested loops). */
|
|
};
|
|
|
|
/* Represents the state of the code generator for a particular lexical scope. */
|
|
typedef struct scope {
|
|
mrb_state *mrb; /* Pointer to the mruby state. */
|
|
mempool *mpool; /* Pointer to the memory pool for this scope's allocations. */
|
|
|
|
struct scope *prev; /* Pointer to the previous (enclosing) scope. */
|
|
|
|
node *lv; /* AST node representing the list of local variables in this scope. */
|
|
|
|
uint16_t sp; /* Current stack pointer (register index) within this scope. */
|
|
uint32_t pc; /* Current program counter (instruction index) for the ISEQ being generated. */
|
|
uint32_t lastpc; /* Program counter of the previously emitted instruction (used for peephole optimization). */
|
|
uint32_t lastlabel; /* Program counter of the last label emitted (inhibits some peephole optimizations). */
|
|
uint16_t ainfo:15; /* Argument information bitfield (counts for req, opt, rest, post, key, kdict, block). */
|
|
mrb_bool mscope:1; /* Boolean flag: true if this is a method/module/class scope (not a block). */
|
|
|
|
struct loopinfo *loop; /* Pointer to the current innermost `loopinfo` structure for this scope. */
|
|
mrb_sym filename_sym; /* `mrb_sym` representing the current filename. */
|
|
uint16_t lineno; /* Current line number being processed. */
|
|
|
|
mrb_code *iseq; /* Pointer to the dynamically growing array of `mrb_code` (instructions). */
|
|
uint16_t *lines; /* Array to store line numbers corresponding to each instruction (for debugging). */
|
|
uint32_t icapa; /* Current capacity of the `iseq` and `lines` arrays. */
|
|
|
|
mrb_irep *irep; /* Pointer to the `mrb_irep` (instruction sequence representation) being built. */
|
|
mrb_irep_pool *pool; /* Pointer to the literal pool for the `irep`. */
|
|
mrb_sym *syms; /* Pointer to the symbol list for the `irep`. */
|
|
mrb_irep **reps; /* Pointer to the array of child `irep`s (for nested blocks/methods). */
|
|
struct mrb_irep_catch_handler *catch_table; /* Pointer to the table of catch handlers for this scope. */
|
|
uint32_t pcapa, scapa, rcapa; /* Current capacities of the `pool`, `syms`, and `reps` arrays respectively. */
|
|
|
|
uint16_t nlocals; /* Number of local variables in this scope. */
|
|
uint16_t nregs; /* Number of registers used in this scope (maximum value of `sp`). */
|
|
int ai; /* Arena index for mruby's garbage collector. */
|
|
|
|
int debug_start_pos; /* Starting ISEQ position for the current debug file information. */
|
|
uint16_t filename_index; /* Index of the current filename in the parser's filename table. */
|
|
parser_state* parser; /* Pointer to the `mrb_parser_state`. */
|
|
|
|
int rlev; /* Recursion level counter for `codegen` calls, to prevent stack overflow. */
|
|
} codegen_scope;
|
|
|
|
static codegen_scope* scope_new(mrb_state *mrb, codegen_scope *prev, node *lv);
|
|
static void scope_finish(codegen_scope *s);
|
|
static struct loopinfo *loop_push(codegen_scope *s, enum looptype t);
|
|
static void loop_break(codegen_scope *s, node *tree);
|
|
static void loop_pop(codegen_scope *s, int val);
|
|
|
|
/*
|
|
* The search for catch handlers starts at the end of the table in mrb_vm_run().
|
|
* Therefore, the next handler to be added must meet one of the following conditions.
|
|
* - Larger start position
|
|
* - Same start position but smaller end position
|
|
*/
|
|
static int catch_handler_new(codegen_scope *s);
|
|
static void catch_handler_set(codegen_scope *s, int ent, enum mrb_catch_type type, uint32_t begin, uint32_t end, uint32_t target);
|
|
|
|
static void gen_massignment(codegen_scope *s, node *tree, int sp, int val);
|
|
static void codegen_masgn(codegen_scope *s, node *varnode, node *rhs, int sp, int val);
|
|
static void gen_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val);
|
|
static void codegen_call_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val);
|
|
|
|
static void codegen(codegen_scope *s, node *tree, int val);
|
|
static void raise_error(codegen_scope *s, const char *msg);
|
|
|
|
/* Forward declarations for helper functions */
|
|
static struct mrb_ast_var_header* get_var_header(node *n);
|
|
|
|
/* NULL-safe node type accessor macro */
|
|
#define node_type(n) ((n) ? NODE_TYPE(n) : (enum node_type)0)
|
|
|
|
/*
|
|
* Reports a compilation error encountered during code generation.
|
|
*
|
|
* This function formats an error message, typically including the filename
|
|
* and line number where the error occurred. It then triggers a longjmp
|
|
* to unwind the compilation process, effectively halting further code generation.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param message The error message string.
|
|
*/
|
|
static void
|
|
codegen_error(codegen_scope *s, const char *message)
|
|
{
|
|
if (!s) return;
|
|
#ifndef MRB_NO_STDIO
|
|
if (s->filename_sym && s->lineno) {
|
|
const char *filename = mrb_sym_name_len(s->mrb, s->filename_sym, NULL);
|
|
fprintf(stderr, "%s:%d: %s\n", filename, s->lineno, message);
|
|
}
|
|
else {
|
|
fprintf(stderr, "%s\n", message);
|
|
}
|
|
#endif
|
|
while (s->prev) {
|
|
codegen_scope *tmp = s->prev;
|
|
if (s->irep) {
|
|
mrbc_free(s->iseq);
|
|
for (int i=0; i<s->irep->plen; i++) {
|
|
mrb_irep_pool *p = &s->pool[i];
|
|
if ((p->tt & 0x3) == IREP_TT_STR || p->tt == IREP_TT_BIGINT) {
|
|
mrbc_free((void*)p->u.str);
|
|
}
|
|
}
|
|
mrbc_free(s->pool);
|
|
mrbc_free(s->syms);
|
|
mrbc_free(s->catch_table);
|
|
if (s->reps) {
|
|
/* copied from mrb_irep_free() in state.c */
|
|
for (int i=0; i<s->irep->rlen; i++) {
|
|
if (s->reps[i])
|
|
mrb_irep_decref(s->mrb, (mrb_irep*)s->reps[i]);
|
|
}
|
|
mrbc_free(s->reps);
|
|
}
|
|
mrbc_free(s->lines);
|
|
}
|
|
mempool_close(s->mpool);
|
|
s = tmp;
|
|
}
|
|
MRB_THROW(s->mrb->jmp);
|
|
}
|
|
|
|
/*
|
|
* Allocates memory from the memory pool associated with the current codegen_scope.
|
|
*
|
|
* This function is used for allocations that are expected to have the same
|
|
* lifetime as the current scope. The memory allocated via this function will be
|
|
* freed automatically when the scope is finished and its memory pool is closed.
|
|
* It calls `codegen_error` if allocation fails.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param len The number of bytes to allocate.
|
|
* @return A pointer to the allocated memory.
|
|
*/
|
|
static void*
|
|
codegen_palloc(codegen_scope *s, size_t len)
|
|
{
|
|
void *p = mempool_alloc(s->mpool, len);
|
|
|
|
if (!p) codegen_error(s, "pool memory allocation");
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Checks if instruction operands `a` or `b` exceed 8 bits (0xff).
|
|
*
|
|
* If the parser option `no_ext_ops` is set (disallowing OP_EXT1/2/3),
|
|
* and either operand is larger than 0xff, this function calls `codegen_error`
|
|
* to report that an extended opcode would be required.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param a The first operand.
|
|
* @param b The second operand.
|
|
*/
|
|
static void
|
|
check_no_ext_ops(codegen_scope *s, uint16_t a, uint16_t b)
|
|
{
|
|
if (s->parser->no_ext_ops && (a | b) > 0xff) {
|
|
codegen_error(s, "need OP_EXTs instruction (currently OP_EXTs are prohibited)");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Creates a new label by returning the current program counter (pc)
|
|
* and updating `s->lastlabel` to this value.
|
|
*
|
|
* Marking a PC as a label (`s->lastlabel = s->pc`) can inhibit certain
|
|
* peephole optimizations that might otherwise modify instructions at this label.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @return The current program counter, which is now marked as a label.
|
|
*/
|
|
static int
|
|
new_label(codegen_scope *s)
|
|
{
|
|
return s->lastlabel = s->pc;
|
|
}
|
|
|
|
/*
|
|
* Emits a single byte (`i`) into the instruction sequence (`s->iseq`)
|
|
* at the specified program counter (`pc`).
|
|
*
|
|
* This function handles dynamic resizing of the `iseq` buffer and the
|
|
* associated `lines` array (if line number tracking is enabled).
|
|
* It also records the current line number (`s->lineno`) for the emitted
|
|
* instruction in `s->lines[pc]`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pc The program counter where the byte should be emitted.
|
|
* @param i The byte to emit.
|
|
*/
|
|
static void
|
|
emit_B(codegen_scope *s, uint32_t pc, uint8_t i)
|
|
{
|
|
if (pc >= s->icapa) {
|
|
if (pc == UINT32_MAX) {
|
|
codegen_error(s, "too big code block");
|
|
}
|
|
if (pc >= UINT32_MAX / 2) {
|
|
pc = UINT32_MAX;
|
|
}
|
|
else {
|
|
s->icapa *= 2;
|
|
}
|
|
s->iseq = (mrb_code*)mrbc_realloc(s->iseq, sizeof(mrb_code)*s->icapa);
|
|
if (s->lines) {
|
|
s->lines = (uint16_t*)mrbc_realloc(s->lines, sizeof(uint16_t)*s->icapa);
|
|
}
|
|
}
|
|
if (s->lines) {
|
|
if (s->lineno > 0 || pc == 0)
|
|
s->lines[pc] = s->lineno;
|
|
else
|
|
s->lines[pc] = s->lines[pc-1];
|
|
}
|
|
s->iseq[pc] = i;
|
|
}
|
|
|
|
/*
|
|
* Emits a 2-byte short integer (`i`) into the instruction sequence at `pc`.
|
|
* The short is emitted in big-endian format (most significant byte first).
|
|
* This is achieved by calling `emit_B` twice.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pc The program counter where the short should be emitted.
|
|
* @param i The 2-byte short to emit.
|
|
*/
|
|
static void
|
|
emit_S(codegen_scope *s, int pc, uint16_t i)
|
|
{
|
|
uint8_t hi = i>>8;
|
|
uint8_t lo = i&0xff;
|
|
|
|
emit_B(s, pc, hi);
|
|
emit_B(s, pc+1, lo);
|
|
}
|
|
|
|
/*
|
|
* Generates (emits) a single byte (`i`) at the current program counter (`s->pc`)
|
|
* and then increments `s->pc` by 1.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The byte to emit.
|
|
*/
|
|
static void
|
|
gen_B(codegen_scope *s, uint8_t i)
|
|
{
|
|
emit_B(s, s->pc, i);
|
|
s->pc++;
|
|
}
|
|
|
|
/*
|
|
* Generates (emits) a 2-byte short integer (`i`) at the current program
|
|
* counter (`s->pc`) and then increments `s->pc` by 2.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The 2-byte short to emit.
|
|
*/
|
|
static void
|
|
gen_S(codegen_scope *s, uint16_t i)
|
|
{
|
|
emit_S(s, s->pc, i);
|
|
s->pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` that takes no operands.
|
|
* Updates `s->lastpc` to the current `s->pc` before emitting.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
*/
|
|
static void
|
|
genop_0(codegen_scope *s, mrb_code i)
|
|
{
|
|
s->lastpc = s->pc;
|
|
gen_B(s, i);
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` with a single 16-bit operand `a`.
|
|
* If `a` is larger than 0xFF (255), it prepends `OP_EXT1` and emits `a` as a short.
|
|
* Otherwise, it emits `a` as a single byte.
|
|
* Updates `s->lastpc`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The 16-bit operand.
|
|
*/
|
|
static void
|
|
genop_1(codegen_scope *s, mrb_code i, uint16_t a)
|
|
{
|
|
s->lastpc = s->pc;
|
|
check_no_ext_ops(s, a, 0);
|
|
if (a > 0xff) {
|
|
gen_B(s, OP_EXT1);
|
|
gen_B(s, i);
|
|
gen_S(s, a);
|
|
}
|
|
else {
|
|
gen_B(s, i);
|
|
gen_B(s, (uint8_t)a);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` with two 16-bit operands `a` and `b`.
|
|
* It handles operand extensions (`OP_EXT1`, `OP_EXT2`, `OP_EXT3`)
|
|
* based on whether `a` and/or `b` are larger than 0xFF.
|
|
* Updates `s->lastpc`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The first 16-bit operand.
|
|
* @param b The second 16-bit operand.
|
|
*/
|
|
static void
|
|
genop_2(codegen_scope *s, mrb_code i, uint16_t a, uint16_t b)
|
|
{
|
|
s->lastpc = s->pc;
|
|
check_no_ext_ops(s, a, b);
|
|
if (a > 0xff && b > 0xff) {
|
|
gen_B(s, OP_EXT3);
|
|
gen_B(s, i);
|
|
gen_S(s, a);
|
|
gen_S(s, b);
|
|
}
|
|
else if (b > 0xff) {
|
|
gen_B(s, OP_EXT2);
|
|
gen_B(s, i);
|
|
gen_B(s, (uint8_t)a);
|
|
gen_S(s, b);
|
|
}
|
|
else if (a > 0xff) {
|
|
gen_B(s, OP_EXT1);
|
|
gen_B(s, i);
|
|
gen_S(s, a);
|
|
gen_B(s, (uint8_t)b);
|
|
}
|
|
else {
|
|
gen_B(s, i);
|
|
gen_B(s, (uint8_t)a);
|
|
gen_B(s, (uint8_t)b);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` with three operands `a`, `b`, and `c`.
|
|
* It uses `genop_2` to emit `i`, `a`, and `b` (handling extensions for `a` and `b`),
|
|
* and then emits `c` as a single byte using `gen_B`. `c` is assumed to fit in a byte.
|
|
* Updates `s->lastpc` (via `genop_2`).
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The first 16-bit operand.
|
|
* @param b The second 16-bit operand.
|
|
* @param c The third 16-bit operand (emitted as a byte).
|
|
*/
|
|
static void
|
|
genop_3(codegen_scope *s, mrb_code i, uint16_t a, uint16_t b, uint16_t c)
|
|
{
|
|
genop_2(s, i, a, b);
|
|
gen_B(s, (uint8_t)c);
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` with a 16-bit operand `a` and a 16-bit operand `b`.
|
|
* Operand `a` is emitted using `genop_1` (which handles `OP_EXT1` if needed).
|
|
* Operand `b` is emitted as a 2-byte short using `gen_S`.
|
|
* Updates `s->lastpc` (via `genop_1`).
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The first 16-bit operand.
|
|
* @param b The second 16-bit operand (emitted as a short).
|
|
*/
|
|
static void
|
|
genop_2S(codegen_scope *s, mrb_code i, uint16_t a, uint16_t b)
|
|
{
|
|
genop_1(s, i, a);
|
|
gen_S(s, b);
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` with a 16-bit operand `a` and a 32-bit operand `b`.
|
|
* Operand `a` is emitted using `genop_1` (handling `OP_EXT1`).
|
|
* Operand `b` is emitted as two 2-byte shorts (high word then low word).
|
|
* Updates `s->lastpc` (via `genop_1`).
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The first 16-bit operand.
|
|
* @param b The 32-bit operand (emitted as two shorts).
|
|
*/
|
|
static void
|
|
genop_2SS(codegen_scope *s, mrb_code i, uint16_t a, uint32_t b)
|
|
{
|
|
genop_1(s, i, a);
|
|
gen_S(s, b>>16);
|
|
gen_S(s, b&0xffff);
|
|
}
|
|
|
|
/*
|
|
* Generates an opcode `i` followed by a 3-byte "wide" operand `a`.
|
|
* The 3-byte operand is emitted as three separate bytes (a1, a2, a3).
|
|
* Updates `s->lastpc`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The opcode to generate.
|
|
* @param a The 32-bit operand, of which the lower 24 bits are used.
|
|
*/
|
|
static void
|
|
genop_W(codegen_scope *s, mrb_code i, uint32_t a)
|
|
{
|
|
uint8_t a1 = (a>>16) & 0xff;
|
|
uint8_t a2 = (a>>8) & 0xff;
|
|
uint8_t a3 = a & 0xff;
|
|
|
|
s->lastpc = s->pc;
|
|
gen_B(s, i);
|
|
gen_B(s, a1);
|
|
gen_B(s, a2);
|
|
gen_B(s, a3);
|
|
}
|
|
|
|
/* Indicates whether a codegen function should produce a value on the stack (VAL) or not (NOVAL). */
|
|
#define NOVAL 0
|
|
#define VAL 1
|
|
|
|
static mrb_bool
|
|
no_optimize(codegen_scope *s)
|
|
{
|
|
if (s && s->parser && s->parser->no_optimize)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Decodes a mruby bytecode instruction starting at the given program counter `pc`.
|
|
*
|
|
* It reads the opcode and its operands from the bytecode stream and populates
|
|
* a `mrb_insn_data` structure. This function handles standard opcodes as well
|
|
* as extended opcodes (OP_EXT1, OP_EXT2, OP_EXT3) to correctly parse operands
|
|
* of varying sizes. This is primarily used by the peephole optimizer and
|
|
* instruction analysis utilities.
|
|
*
|
|
* @param pc Pointer to the start of the instruction in the bytecode.
|
|
* @return A `mrb_insn_data` struct containing the decoded instruction,
|
|
* its operands (a, b, c), and the original address.
|
|
*/
|
|
struct mrb_insn_data
|
|
mrb_decode_insn(const mrb_code *pc)
|
|
{
|
|
struct mrb_insn_data data = { 0 };
|
|
if (pc == 0) return data;
|
|
data.addr = pc;
|
|
mrb_code insn = READ_B();
|
|
uint16_t a = 0;
|
|
uint16_t b = 0;
|
|
uint16_t c = 0;
|
|
|
|
switch (insn) {
|
|
#define FETCH_Z() /* empty */
|
|
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x (); break;
|
|
#include <mruby/ops.h>
|
|
#undef OPCODE
|
|
}
|
|
switch (insn) {
|
|
case OP_EXT1:
|
|
insn = READ_B();
|
|
switch (insn) {
|
|
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _1 (); break;
|
|
#include <mruby/ops.h>
|
|
#undef OPCODE
|
|
}
|
|
break;
|
|
case OP_EXT2:
|
|
insn = READ_B();
|
|
switch (insn) {
|
|
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _2 (); break;
|
|
#include <mruby/ops.h>
|
|
#undef OPCODE
|
|
}
|
|
break;
|
|
case OP_EXT3:
|
|
insn = READ_B();
|
|
switch (insn) {
|
|
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _3 (); break;
|
|
#include <mruby/ops.h>
|
|
#undef OPCODE
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
data.insn = insn;
|
|
data.a = a;
|
|
data.b = b;
|
|
data.c = c;
|
|
return data;
|
|
}
|
|
|
|
#undef OPCODE
|
|
#define Z 1
|
|
#define S 3
|
|
#define W 4
|
|
#define OPCODE(_,x) x,
|
|
/* instruction sizes */
|
|
static uint8_t mrb_insn_size[] = {
|
|
#define B 2
|
|
#define BB 3
|
|
#define BBB 4
|
|
#define BS 4
|
|
#define BSS 6
|
|
#include <mruby/ops.h>
|
|
#undef B
|
|
#undef BB
|
|
#undef BBB
|
|
#undef BS
|
|
#undef BSS
|
|
};
|
|
/* EXT1 instruction sizes */
|
|
static uint8_t mrb_insn_size1[] = {
|
|
#define B 3
|
|
#define BB 4
|
|
#define BBB 5
|
|
#define BS 5
|
|
#define BSS 7
|
|
#include <mruby/ops.h>
|
|
#undef B
|
|
#undef BS
|
|
#undef BSS
|
|
};
|
|
/* EXT2 instruction sizes */
|
|
static uint8_t mrb_insn_size2[] = {
|
|
#define B 2
|
|
#define BS 4
|
|
#define BSS 6
|
|
#include <mruby/ops.h>
|
|
#undef B
|
|
#undef BB
|
|
#undef BBB
|
|
#undef BS
|
|
#undef BSS
|
|
};
|
|
/* EXT3 instruction sizes */
|
|
#define B 3
|
|
#define BB 5
|
|
#define BBB 6
|
|
#define BS 5
|
|
#define BSS 7
|
|
static uint8_t mrb_insn_size3[] = {
|
|
#include <mruby/ops.h>
|
|
};
|
|
#undef B
|
|
#undef BB
|
|
#undef BBB
|
|
#undef BS
|
|
#undef BSS
|
|
#undef OPCODE
|
|
|
|
/*
|
|
* Finds the program counter (PC) of the instruction immediately preceding
|
|
* the instruction at the given `pc`.
|
|
*
|
|
* It iterates backward through the already generated instruction sequence (`s->iseq`)
|
|
* from its beginning up to `pc`, decoding each instruction to determine its size
|
|
* and thus find the start of the previous instruction.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pc Pointer to an instruction in `s->iseq`.
|
|
* @return Pointer to the start of the instruction preceding the one at `pc`,
|
|
* or NULL if `pc` is at the beginning of `s->iseq`.
|
|
*/
|
|
static const mrb_code*
|
|
mrb_prev_pc(codegen_scope *s, const mrb_code *pc)
|
|
{
|
|
const mrb_code *prev_pc = NULL;
|
|
const mrb_code *i = s->iseq;
|
|
|
|
mrb_assert(pc < s->iseq + s->icapa);
|
|
while (i<pc) {
|
|
prev_pc = i;
|
|
switch (i[0]) {
|
|
case OP_EXT1:
|
|
i += mrb_insn_size1[i[1]] + 1;
|
|
break;
|
|
case OP_EXT2:
|
|
i += mrb_insn_size2[i[1]] + 1;
|
|
break;
|
|
case OP_EXT3:
|
|
i += mrb_insn_size3[i[1]] + 1;
|
|
break;
|
|
default:
|
|
i += mrb_insn_size[i[0]];
|
|
break;
|
|
}
|
|
}
|
|
return prev_pc;
|
|
}
|
|
|
|
/* Gets the memory address of the current instruction pointed to by the program counter (pc). */
|
|
#define pc_addr(s) &((s)->iseq[(s)->pc])
|
|
/* Converts an instruction memory address to a program counter (pc) offset. */
|
|
#define addr_pc(s, addr) (uint32_t)((addr) - s->iseq)
|
|
/* Resets the program counter (pc) to the address of the previously generated instruction. Used in peephole optimizations. */
|
|
#define rewind_pc(s) s->pc = s->lastpc
|
|
|
|
/*
|
|
* Decodes and returns the last instruction that was emitted into the
|
|
* instruction sequence (`s->iseq`).
|
|
* It uses `mrb_decode_insn` on the instruction at `s->iseq[s->lastpc]`.
|
|
* If no instructions have been emitted (`s->pc == 0`), it returns a NOP.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @return A `mrb_insn_data` struct for the last emitted instruction.
|
|
*/
|
|
static struct mrb_insn_data
|
|
mrb_last_insn(codegen_scope *s)
|
|
{
|
|
if (s->pc == 0) {
|
|
struct mrb_insn_data data = { OP_NOP, 0 };
|
|
return data;
|
|
}
|
|
return mrb_decode_insn(&s->iseq[s->lastpc]);
|
|
}
|
|
|
|
/*
|
|
* Determines if peephole optimizations should be disabled for the current instruction.
|
|
* Peephole optimization is disabled if:
|
|
* - General optimization is off (`no_optimize(s)` is true).
|
|
* - The current program counter (`s->pc`) is the target of a label (`s->lastlabel == s->pc`).
|
|
* - It's the beginning of the bytecode (`s->pc == 0`).
|
|
* - The current PC is the same as the PC of the last emitted instruction (`s->pc == s->lastpc`),
|
|
* which can happen after a `rewind_pc`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @return TRUE if peephole optimizations should be skipped, FALSE otherwise.
|
|
*/
|
|
static mrb_bool
|
|
no_peephole(codegen_scope *s)
|
|
{
|
|
return no_optimize(s) || s->lastlabel == s->pc || s->pc == 0 || s->pc == s->lastpc;
|
|
}
|
|
|
|
/* Sentinel value for jump offsets that are not yet determined and need to be linked later. */
|
|
#define JMPLINK_START UINT32_MAX
|
|
|
|
/*
|
|
* Generates the 2-byte signed offset for a jump instruction.
|
|
*
|
|
* The `pc` argument is the absolute target program counter for the jump.
|
|
* The function calculates the relative offset from the instruction *after*
|
|
* the current jump instruction (i.e., `s->pc + 2` for the jump opcode and its offset)
|
|
* to the target `pc`. This offset is then emitted as a 16-bit signed integer.
|
|
* If the offset is too large to fit in 16 bits, it calls `codegen_error`.
|
|
* If `pc` is `JMPLINK_START`, it emits an offset of 0 (placeholder for later patching).
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pc The absolute target program counter for the jump.
|
|
*/
|
|
static void
|
|
gen_jmpdst(codegen_scope *s, uint32_t pc)
|
|
{
|
|
|
|
if (pc == JMPLINK_START) {
|
|
pc = 0;
|
|
}
|
|
uint32_t pos2 = s->pc+2;
|
|
int32_t off = pc - pos2;
|
|
|
|
if (off > INT16_MAX || INT16_MIN > off) {
|
|
codegen_error(s, "too big jump offset");
|
|
}
|
|
gen_S(s, (uint16_t)off);
|
|
}
|
|
|
|
/*
|
|
* Generates an unconditional jump instruction `i` (e.g., OP_JMP)
|
|
* that jumps to the absolute target program counter `pc`.
|
|
*
|
|
* It first emits the jump opcode `i` using `genop_0`, then emits
|
|
* the calculated jump offset using `gen_jmpdst`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The jump opcode to generate (e.g., OP_JMP).
|
|
* @param pc The absolute target program counter.
|
|
* @return The program counter where the jump offset was written. This is used for jump linking.
|
|
*/
|
|
static uint32_t
|
|
genjmp(codegen_scope *s, mrb_code i, uint32_t pc)
|
|
{
|
|
uint32_t pos;
|
|
|
|
genop_0(s, i);
|
|
pos = s->pc;
|
|
gen_jmpdst(s, pc);
|
|
return pos;
|
|
}
|
|
|
|
#define genjmp_0(s,i) genjmp(s,i,JMPLINK_START)
|
|
|
|
/*
|
|
* Generates a conditional jump instruction `i` (e.g., OP_JMPNOT, OP_JMPIF)
|
|
* based on the value in register `a`, targeting the absolute program counter `pc`.
|
|
*
|
|
* This function includes several peephole optimizations:
|
|
* - If the last instruction was a MOVE to register `a` from another temporary register,
|
|
* it rewinds and uses the source of the MOVE as the condition register.
|
|
* - If the last instruction loaded a constant (nil, false, true, integer) into register `a`,
|
|
* it may optimize the jump:
|
|
* - If the condition is known at compile time (e.g., JMPNOT after LOADF), it can
|
|
* transform the conditional jump into an unconditional OP_JMP.
|
|
* - If the condition is known and makes the jump always/never taken, it can
|
|
* remove the jump entirely (returning JMPLINK_START to signify this).
|
|
* The `val` parameter influences these optimizations: if `val` is false (NOVAL),
|
|
* it implies the preceding instruction producing `a` might be removable if the jump
|
|
* itself is optimized away.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param i The conditional jump opcode.
|
|
* @param a The register index holding the condition value.
|
|
* @param pc The absolute target program counter for the jump.
|
|
* @param val Indicates if the value in register `a` from a previous instruction is needed
|
|
* beyond this conditional jump.
|
|
* @return The program counter where the jump offset was written, or `JMPLINK_START` if the
|
|
* jump was optimized away.
|
|
*/
|
|
static uint32_t
|
|
genjmp2(codegen_scope *s, mrb_code i, uint16_t a, uint32_t pc, int val)
|
|
{
|
|
uint32_t pos;
|
|
|
|
if (!no_peephole(s) && !val) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
|
|
switch (data.insn) {
|
|
case OP_MOVE:
|
|
if (data.a == a && data.a > s->nlocals) {
|
|
rewind_pc(s);
|
|
a = data.b;
|
|
}
|
|
break;
|
|
case OP_LOADNIL:
|
|
case OP_LOADFALSE:
|
|
if (data.a == a || data.a > s->nlocals) {
|
|
s->pc = addr_pc(s, data.addr);
|
|
if (i == OP_JMPNOT || (i == OP_JMPNIL && data.insn == OP_LOADNIL)) {
|
|
return genjmp(s, OP_JMP, pc);
|
|
}
|
|
else { /* OP_JMPIF */
|
|
return JMPLINK_START;
|
|
}
|
|
}
|
|
break;
|
|
case OP_LOADTRUE: case OP_LOADI8: case OP_LOADINEG: case OP_LOADI__1:
|
|
case OP_LOADI_0: case OP_LOADI_1: case OP_LOADI_2: case OP_LOADI_3:
|
|
case OP_LOADI_4: case OP_LOADI_5: case OP_LOADI_6: case OP_LOADI_7:
|
|
if (data.a == a || data.a > s->nlocals) {
|
|
s->pc = addr_pc(s, data.addr);
|
|
if (i == OP_JMPIF) {
|
|
return genjmp(s, OP_JMP, pc);
|
|
}
|
|
else { /* OP_JMPNOT and OP_JMPNIL */
|
|
return JMPLINK_START;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (a > 0xff) {
|
|
check_no_ext_ops(s, a, 0);
|
|
gen_B(s, OP_EXT1);
|
|
genop_0(s, i);
|
|
gen_S(s, a);
|
|
}
|
|
else {
|
|
genop_0(s, i);
|
|
gen_B(s, (uint8_t)a);
|
|
}
|
|
pos = s->pc;
|
|
gen_jmpdst(s, pc);
|
|
return pos;
|
|
}
|
|
|
|
#define genjmp2_0(s,i,a,val) genjmp2(s,i,a,JMPLINK_START,val)
|
|
|
|
static mrb_bool get_int_operand(codegen_scope *s, struct mrb_insn_data *data, mrb_int *ns);
|
|
static void gen_int(codegen_scope *s, uint16_t dst, mrb_int i);
|
|
|
|
/*
|
|
* Generates an OP_MOVE instruction to copy the value from register `src` to register `dst`.
|
|
*
|
|
* This function incorporates several peephole optimizations to avoid redundant moves or
|
|
* to combine the move with preceding operations:
|
|
* - If `dst` and `src` are the same, the function does nothing.
|
|
* - If the previous instruction was also an `OP_MOVE` involving `src` or `dst`,
|
|
* it might combine or reorder them to eliminate redundant operations.
|
|
* - If the previous instruction loaded a literal (nil, self, true, false, integer,
|
|
* symbol, string, etc.) into `src`, and `src` is a temporary register,
|
|
* this function can rewind the program counter and generate the load operation
|
|
* directly into `dst`, effectively eliminating the `OP_MOVE`.
|
|
* - It can also perform constant folding for `OP_ADDI`/`OP_SUBI` if a sequence of
|
|
* `LOADI`, `MOVE`, `ADDI`/`SUBI` can be resolved at compile time.
|
|
*
|
|
* The `nopeep` parameter, if true, disables these peephole optimizations, forcing
|
|
* the generation of a direct `OP_MOVE` instruction.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param dst The destination register index.
|
|
* @param src The source register index.
|
|
* @param nopeep If non-zero, disables peephole optimizations for this move.
|
|
*/
|
|
static void
|
|
gen_move(codegen_scope *s, uint16_t dst, uint16_t src, int nopeep)
|
|
{
|
|
if (dst == src) return;
|
|
if (!(nopeep || no_peephole(s))) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
|
|
switch (data.insn) {
|
|
case OP_MOVE:
|
|
if (dst == src) return; /* remove useless MOVE */
|
|
if (data.a == src) {
|
|
if (data.b == dst) /* skip swapping MOVE */
|
|
return;
|
|
if (data.a < s->nlocals) break;
|
|
rewind_pc(s);
|
|
s->lastpc = addr_pc(s, mrb_prev_pc(s, data.addr));
|
|
gen_move(s, dst, data.b, FALSE);
|
|
return;
|
|
}
|
|
if (dst == data.a) { /* skip overwritten move */
|
|
rewind_pc(s);
|
|
s->lastpc = addr_pc(s, mrb_prev_pc(s, data.addr));
|
|
gen_move(s, dst, src, FALSE);
|
|
return;
|
|
}
|
|
break;
|
|
case OP_LOADNIL: case OP_LOADSELF: case OP_LOADTRUE: case OP_LOADFALSE:
|
|
case OP_LOADI__1:
|
|
case OP_LOADI_0: case OP_LOADI_1: case OP_LOADI_2: case OP_LOADI_3:
|
|
case OP_LOADI_4: case OP_LOADI_5: case OP_LOADI_6: case OP_LOADI_7:
|
|
if (data.a != src || data.a < s->nlocals) break;
|
|
rewind_pc(s);
|
|
genop_1(s, data.insn, dst);
|
|
return;
|
|
case OP_HASH:
|
|
if (data.b != 0) break;
|
|
/* fall through */
|
|
case OP_LOADI8: case OP_LOADINEG:
|
|
case OP_LOADL: case OP_LOADSYM:
|
|
case OP_GETGV: case OP_GETSV: case OP_GETIV: case OP_GETCV:
|
|
case OP_GETCONST: case OP_STRING:
|
|
case OP_LAMBDA: case OP_BLOCK: case OP_METHOD: case OP_BLKPUSH:
|
|
if (data.a != src || data.a < s->nlocals) break;
|
|
rewind_pc(s);
|
|
genop_2(s, data.insn, dst, data.b);
|
|
return;
|
|
case OP_LOADI16:
|
|
if (data.a != src || data.a < s->nlocals) break;
|
|
rewind_pc(s);
|
|
genop_2S(s, data.insn, dst, data.b);
|
|
return;
|
|
case OP_LOADI32:
|
|
if (data.a != src || data.a < s->nlocals) break;
|
|
else {
|
|
uint32_t i = (uint32_t)data.b<<16|data.c;
|
|
rewind_pc(s);
|
|
genop_2SS(s, data.insn, dst, i);
|
|
}
|
|
return;
|
|
case OP_ARRAY:
|
|
if (data.a != src || data.a < s->nlocals || data.a < dst) break;
|
|
rewind_pc(s);
|
|
if (data.b == 0 || dst == data.a)
|
|
genop_2(s, OP_ARRAY, dst, 0);
|
|
else
|
|
genop_3(s, OP_ARRAY2, dst, data.a, data.b);
|
|
return;
|
|
case OP_ARRAY2:
|
|
if (data.a != src || data.a < s->nlocals || data.a < dst) break;
|
|
rewind_pc(s);
|
|
genop_3(s, OP_ARRAY2, dst, data.b, data.c);
|
|
return;
|
|
case OP_AREF:
|
|
case OP_GETUPVAR:
|
|
if (data.a != src || data.a < s->nlocals) break;
|
|
rewind_pc(s);
|
|
genop_3(s, data.insn, dst, data.b, data.c);
|
|
return;
|
|
case OP_ADDI: case OP_SUBI:
|
|
if (addr_pc(s, data.addr) == s->lastlabel || data.a != src || data.a < s->nlocals) break;
|
|
else {
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
if (data0.insn != OP_MOVE || data0.a != data.a || data0.b != dst) break;
|
|
if (addr_pc(s, data0.addr) != s->lastlabel) {
|
|
/* constant folding */
|
|
struct mrb_insn_data data1 = mrb_decode_insn(mrb_prev_pc(s, data0.addr));
|
|
mrb_int n;
|
|
if (data1.a == dst && get_int_operand(s, &data1, &n)) {
|
|
if ((data.insn == OP_ADDI && !mrb_int_add_overflow(n, data.b, &n)) ||
|
|
(data.insn == OP_SUBI && !mrb_int_sub_overflow(n, data.b, &n))) {
|
|
s->pc = addr_pc(s, data1.addr);
|
|
gen_int(s, dst, n);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
/* ADDILV/SUBILV fusion: MOVE temp local; ADDI temp imm; MOVE local temp */
|
|
/* -> ADDILV local temp imm (temp is working space for method fallback) */
|
|
s->pc = addr_pc(s, data0.addr);
|
|
genop_3(s, data.insn == OP_ADDI ? OP_ADDILV : OP_SUBILV, dst, data.a, data.b);
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
genop_2(s, OP_MOVE, dst, src);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Searches for a local variable `id` in outer lexical scopes (upvalues).
|
|
*
|
|
* It first traverses the chain of enclosing `codegen_scope` structures
|
|
* (linked by `s->prev`). If not found, it then traverses the chain of
|
|
* `upper` RProc structures stored in the parser state.
|
|
*
|
|
* If the variable `id` is found in an outer scope:
|
|
* - It returns `lv`, the number of lexical levels (scopes) to go up
|
|
* to find the variable.
|
|
* - It sets the `*idx` output parameter to the variable's index within
|
|
* that outer scope's local variable table.
|
|
*
|
|
* If the variable is not found in any outer scope, it calls `codegen_error`
|
|
* to report an error (e.g., "No anonymous block parameter", "Can't find local variables").
|
|
*
|
|
* @param s The current code generation scope from which the search begins.
|
|
* @param id The `mrb_sym` (symbol) of the local variable to search for.
|
|
* @param idx Output parameter: pointer to an integer where the index of the
|
|
* variable in its defining scope will be stored.
|
|
* @return The lexical distance (number of scopes upwards) to the variable's
|
|
* defining scope.
|
|
*/
|
|
static int search_upvar(codegen_scope *s, mrb_sym id, int *idx);
|
|
|
|
/*
|
|
* Generates an `OP_GETUPVAR` instruction to retrieve an upvalue.
|
|
*
|
|
* The upvalue `id` is first located using `search_upvar` to determine its
|
|
* lexical level (`lv`) and index (`idx`) within that outer scope.
|
|
* Then, an `OP_GETUPVAR` instruction is generated to load this upvalue
|
|
* into the destination register `dst`.
|
|
*
|
|
* Peephole Optimization:
|
|
* - If the immediately preceding instruction was an `OP_SETUPVAR` for the
|
|
* same upvalue (`id`), lexical level (`lv`), and destination register (`dst`),
|
|
* this `OP_GETUPVAR` is skipped as the value is already in the target register.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param dst The destination register index where the upvalue will be loaded.
|
|
* @param id The `mrb_sym` (symbol) of the upvalue to retrieve.
|
|
*/
|
|
static void
|
|
gen_getupvar(codegen_scope *s, uint16_t dst, mrb_sym id)
|
|
{
|
|
int idx;
|
|
int lv = search_upvar(s, id, &idx);
|
|
|
|
if (!no_peephole(s)) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
if (data.insn == OP_SETUPVAR && data.a == dst && data.b == idx && data.c == lv) {
|
|
/* skip GETUPVAR right after SETUPVAR */
|
|
return;
|
|
}
|
|
}
|
|
genop_3(s, OP_GETUPVAR, dst, idx, lv);
|
|
}
|
|
|
|
/*
|
|
* Generates an `OP_SETUPVAR` instruction to set an upvalue.
|
|
*
|
|
* The upvalue `id` is first located using `search_upvar` to determine its
|
|
* lexical level (`lv`) and index (`idx`) within that outer scope.
|
|
* Then, an `OP_SETUPVAR` instruction is generated to set this upvalue
|
|
* using the value from register `dst`.
|
|
*
|
|
* Peephole Optimization:
|
|
* - If the immediately preceding instruction was an `OP_MOVE` where register `dst`
|
|
* was the destination (`data.a == dst`), this function will rewind the program
|
|
* counter and use the source register of that `OP_MOVE` (`data.b`) as the source
|
|
* for `OP_SETUPVAR` instead. This effectively uses the original value before the move.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param dst The register index holding the value to set the upvalue to.
|
|
* @param id The `mrb_sym` (symbol) of the upvalue to set.
|
|
*/
|
|
static void
|
|
gen_setupvar(codegen_scope *s, uint16_t dst, mrb_sym id)
|
|
{
|
|
int idx;
|
|
int lv = search_upvar(s, id, &idx);
|
|
|
|
if (!no_peephole(s)) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
if (data.insn == OP_MOVE && data.a == dst) {
|
|
dst = data.b;
|
|
rewind_pc(s);
|
|
}
|
|
}
|
|
genop_3(s, OP_SETUPVAR, dst, idx, lv);
|
|
}
|
|
|
|
/*
|
|
* Generates a return instruction (e.g., `OP_RETURN`, `OP_RETURN_BLK`).
|
|
*
|
|
* This function emits the specified return opcode `op` with the source register `src`
|
|
* containing the value to be returned.
|
|
*
|
|
* Peephole Optimization:
|
|
* - If peephole optimization is enabled and the immediately preceding instruction
|
|
* was an `OP_MOVE` into the `src` register (`data.insn == OP_MOVE && src == data.a`),
|
|
* this function will rewind the program counter and generate the return instruction
|
|
* using the original source register of that `OP_MOVE` (`data.b`). This avoids
|
|
* a redundant move before returning.
|
|
* - It also avoids emitting multiple consecutive `OP_RETURN` instructions.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param op The specific return opcode to generate (e.g., `OP_RETURN`, `OP_RETURN_BLK`).
|
|
* @param src The register index holding the value to be returned.
|
|
*/
|
|
static void
|
|
gen_return(codegen_scope *s, uint8_t op, uint16_t src)
|
|
{
|
|
if (no_peephole(s)) {
|
|
genop_1(s, op, src);
|
|
}
|
|
else {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
|
|
if (data.insn == OP_MOVE && src == data.a) {
|
|
rewind_pc(s);
|
|
genop_1(s, op, data.b);
|
|
}
|
|
else if (data.insn == OP_LOADSELF && src == data.a && op == OP_RETURN) {
|
|
/* LOADSELF + RETURN -> RETSELF */
|
|
rewind_pc(s);
|
|
genop_0(s, OP_RETSELF);
|
|
}
|
|
else if (data.insn == OP_LOADNIL && src == data.a && op == OP_RETURN) {
|
|
/* LOADNIL + RETURN -> RETNIL */
|
|
rewind_pc(s);
|
|
genop_0(s, OP_RETNIL);
|
|
}
|
|
else if (data.insn == OP_LOADTRUE && src == data.a && op == OP_RETURN) {
|
|
/* LOADTRUE + RETURN -> RETTRUE */
|
|
rewind_pc(s);
|
|
genop_0(s, OP_RETTRUE);
|
|
}
|
|
else if (data.insn == OP_LOADFALSE && src == data.a && op == OP_RETURN) {
|
|
/* LOADFALSE + RETURN -> RETFALSE */
|
|
rewind_pc(s);
|
|
genop_0(s, OP_RETFALSE);
|
|
}
|
|
else if (data.insn != OP_RETURN && data.insn != OP_RETSELF && data.insn != OP_RETNIL &&
|
|
data.insn != OP_RETTRUE && data.insn != OP_RETFALSE) {
|
|
genop_1(s, op, src);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Attempts to extract a compile-time integer value from a given instruction.
|
|
*
|
|
* This function checks if the instruction described by `data` is one of
|
|
* the integer loading opcodes (e.g., `OP_LOADI__1`, `OP_LOADINEG`, `OP_LOADI_0`
|
|
* through `OP_LOADI_7`, `OP_LOADI8`, `OP_LOADI16`, `OP_LOADI32`) or `OP_LOADL`
|
|
* where the literal pool entry is an integer.
|
|
*
|
|
* If successful, it stores the extracted integer value into the output
|
|
* parameter `*n` and returns `TRUE`. Otherwise, it returns `FALSE`.
|
|
*
|
|
* @param s The current code generation scope (used to access the literal pool for `OP_LOADL`).
|
|
* @param data Pointer to an `mrb_insn_data` structure describing the instruction.
|
|
* @param n Output parameter: pointer to an `mrb_int` where the extracted integer
|
|
* value will be stored if successful.
|
|
* @return `TRUE` if an integer value was successfully extracted, `FALSE` otherwise.
|
|
*/
|
|
static mrb_bool
|
|
get_int_operand(codegen_scope *s, struct mrb_insn_data *data, mrb_int *n)
|
|
{
|
|
switch (data->insn) {
|
|
case OP_LOADI__1:
|
|
*n = -1;
|
|
return TRUE;
|
|
|
|
case OP_LOADINEG:
|
|
*n = -data->b;
|
|
return TRUE;
|
|
|
|
case OP_LOADI_0: case OP_LOADI_1: case OP_LOADI_2: case OP_LOADI_3:
|
|
case OP_LOADI_4: case OP_LOADI_5: case OP_LOADI_6: case OP_LOADI_7:
|
|
*n = data->insn - OP_LOADI_0;
|
|
return TRUE;
|
|
|
|
case OP_LOADI8:
|
|
case OP_LOADI16:
|
|
*n = (int16_t)data->b;
|
|
return TRUE;
|
|
|
|
case OP_LOADI32:
|
|
*n = (int32_t)((uint32_t)data->b<<16)+data->c;
|
|
return TRUE;
|
|
|
|
case OP_LOADL:
|
|
{
|
|
mrb_irep_pool *p = &s->pool[data->b];
|
|
|
|
if (p->tt == IREP_TT_INT32) {
|
|
*n = (mrb_int)p->u.i32;
|
|
}
|
|
#ifdef MRB_INT64
|
|
else if (p->tt == IREP_TT_INT64) {
|
|
*n = (mrb_int)p->u.i64;
|
|
}
|
|
#endif
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static int new_lit_str2(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2);
|
|
static int find_pool_str(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2);
|
|
static void gen_string(codegen_scope *s, node *list, int val);
|
|
|
|
/*
|
|
* Reallocates or allocates memory for a string literal in the IREP's literal pool.
|
|
*
|
|
* This function is used when a string literal needs to be resized, typically
|
|
* during string concatenation optimizations (`merge_op_string`).
|
|
*
|
|
* - If the original pool entry `p` pointed to a shared string (e.g., a string
|
|
* from read-only data, `IREP_TT_SSTR`), new memory is allocated for the resized string.
|
|
* - If `p` was already a dynamically allocated string (`IREP_TT_STR`), its buffer
|
|
* is reallocated to the new `len`.
|
|
*
|
|
* After allocation/reallocation, the pool entry `p` is updated:
|
|
* - Its type `tt` is set to `IREP_TT_STR` (or kept as `IREP_TT_STR`).
|
|
* - The length in `tt` is updated to the new `len`.
|
|
* - The string is null-terminated.
|
|
* - `p->u.str` points to the new or reallocated buffer.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param p Pointer to the `mrb_irep_pool` entry for the string literal.
|
|
* @param len The new length of the string (excluding the null terminator).
|
|
*/
|
|
static void
|
|
realloc_pool_str(codegen_scope *s, mrb_irep_pool *p, mrb_int len)
|
|
{
|
|
char *str;
|
|
mrb_int olen = p->tt >> 2; /* original length */
|
|
if ((p->tt & 3) == IREP_TT_SSTR) { /* Check if it's a shared/static string */
|
|
const char *old = p->u.str;
|
|
str = (char*)mrbc_malloc(len+1); /* Allocate new memory if it was shared */
|
|
memcpy(str, old, olen); /* Copy original content */
|
|
}
|
|
else { /* It's already a heap-allocated string */
|
|
str = (char*)p->u.str;
|
|
str = (char*)mrbc_realloc(str, len+1);
|
|
}
|
|
p->tt = (uint32_t)(len<<2 | IREP_TT_STR);
|
|
str[len] = '\0';
|
|
p->u.str = (const char*)str;
|
|
}
|
|
|
|
/*
|
|
* Frees the memory associated with a string literal in the IREP's literal pool,
|
|
* if it's not a shared (static) string.
|
|
*
|
|
* This function is typically called when a string literal pool entry is being
|
|
* effectively removed or replaced due to optimizations like string merging.
|
|
*
|
|
* - It checks if the pool entry `p`'s type `tt` indicates it's a dynamically
|
|
* allocated string (not `IREP_TT_SSTR`).
|
|
* - If so, it frees the memory pointed to by `p->u.str`.
|
|
* - It then sets `p->u.str` to `NULL` and decrements the total count of literals
|
|
* in the pool (`s->irep->plen`). Note: This decrement might be problematic if
|
|
* pool entries are not compacted, as it could lead to an incorrect `plen`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param p Pointer to the `mrb_irep_pool` entry of the string to be freed.
|
|
*/
|
|
static void
|
|
free_pool_str(codegen_scope *s, mrb_irep_pool *p)
|
|
{
|
|
if ((p->tt & 3) != IREP_TT_SSTR) { /* Only free if not a shared/static string */
|
|
mrbc_free((char*)p->u.str);
|
|
}
|
|
p->u.str = NULL;
|
|
s->irep->plen--; /* Decrements the count of pool entries. */
|
|
}
|
|
|
|
/*
|
|
* Performs a peephole optimization for string concatenation.
|
|
*
|
|
* This function is called when an `OP_ADD` (string concatenation) instruction
|
|
* is encountered. It checks if the two operands to `OP_ADD` were themselves
|
|
* loaded by `OP_STRING` instructions (i.e., string literals from the pool
|
|
* at indices `b1` and `b2`).
|
|
*
|
|
* If this pattern is found, `merge_op_string` attempts to:
|
|
* 1. Determine if the literal pool entries `b1` and `b2` are used by any other
|
|
* `OP_STRING` instructions prior to the instruction at `pc` (the start of the
|
|
* first `OP_STRING` in the sequence).
|
|
* 2. Based on this usage (`used` flags), it decides on a strategy to merge
|
|
* the string content of `b1` and `b2`:
|
|
* - If neither `b1` nor `b2` is otherwise referenced, or only `b2` is, it reuses
|
|
* and resizes pool entry `b1` to hold the concatenated string. If `b2` was
|
|
* the last entry in the pool and not shared, `b2` is freed.
|
|
* - If only `b1` is referenced, it reuses and resizes pool entry `b2`.
|
|
* - If both `b1` and `b2` are referenced by other instructions, it creates a
|
|
* new literal pool entry for the concatenated string.
|
|
* 3. If an existing pool entry already matches the concatenated string, that entry is used.
|
|
* 4. Finally, it rewinds the program counter to `pc` (the location of the original
|
|
* first `OP_STRING`) and generates a single `OP_STRING` instruction to load the
|
|
* merged/reused literal into the destination register `dst`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param dst The destination register for the result of the concatenation.
|
|
* @param b1 The pool index of the first string literal.
|
|
* @param b2 The pool index of the second string literal.
|
|
* @param pc The program counter of the instruction that loaded the first string literal (`b1`).
|
|
* This is where the new merged `OP_STRING` will be generated.
|
|
*/
|
|
static void
|
|
merge_op_string(codegen_scope *s, uint16_t dst, uint16_t b1, uint16_t b2, const mrb_code *pc)
|
|
{
|
|
int used = 0;
|
|
const mrb_code *i = s->iseq;
|
|
|
|
/* scan OP_STRING that refers b1 or b2 */
|
|
mrb_assert(pc < s->iseq + s->icapa);
|
|
while (i<pc) {
|
|
struct mrb_insn_data data = mrb_decode_insn(i);
|
|
if (data.insn == OP_STRING) {
|
|
if (data.b == b1) used |= 1;
|
|
if (data.b == b2) used |= 2;
|
|
}
|
|
switch (i[0]) {
|
|
case OP_EXT1:
|
|
i += mrb_insn_size1[i[1]] + 1;
|
|
break;
|
|
case OP_EXT2:
|
|
i += mrb_insn_size2[i[1]] + 1;
|
|
break;
|
|
case OP_EXT3:
|
|
i += mrb_insn_size3[i[1]] + 1;
|
|
break;
|
|
default:
|
|
i += mrb_insn_size[i[0]];
|
|
break;
|
|
}
|
|
}
|
|
|
|
mrb_irep_pool *p1 = &s->pool[b1];
|
|
mrb_irep_pool *p2 = &s->pool[b2];
|
|
mrb_int len1 = p1->tt>>2;
|
|
mrb_int len2 = p2->tt>>2;
|
|
int off = find_pool_str(s, p1->u.str, len1, p2->u.str, len2);
|
|
|
|
if (off < 0) {
|
|
switch (used) {
|
|
case 0: /* both pools are free */
|
|
case 2: /* b2 is referenced */
|
|
/* overwrite p1; free b2 if possible */
|
|
off = b1;
|
|
realloc_pool_str(s, p1, len1+len2);
|
|
memcpy((void*)(p1->u.str+len1), (void*)p2->u.str, len2);
|
|
if (b1 != b2 && used == 0 && b2+1 == s->irep->plen) {
|
|
free_pool_str(s, p2);
|
|
}
|
|
break;
|
|
case 1: /* b1 is referenced */
|
|
/* overwrite p2 */
|
|
off = b2;
|
|
realloc_pool_str(s, p2, len1+len2);
|
|
memmove((void*)(p2->u.str+len1), (void*)p2->u.str, len2);
|
|
memcpy((void*)p2->u.str, p1->u.str, len1);
|
|
break;
|
|
case 3: /* both b1&b2 are referenced */
|
|
/* create new pool */
|
|
off = new_lit_str2(s, p1->u.str, len1, p2->u.str, len2);
|
|
break;
|
|
}
|
|
}
|
|
s->pc = addr_pc(s, pc);
|
|
genop_2(s, OP_STRING, dst, off);
|
|
}
|
|
|
|
/*
|
|
* Generates code for addition (`OP_ADD`) or subtraction (`OP_SUB`)
|
|
* operations, storing the result in register `dst`.
|
|
*
|
|
* This function includes several peephole optimizations:
|
|
* 1. String Concatenation: If `op` is `OP_ADD` and the two preceding instructions
|
|
* were `OP_STRING` (loading string literals), it calls `merge_op_string`
|
|
* to attempt compile-time concatenation of these literals.
|
|
* 2. Immediate Operations: If the last instruction loaded an integer literal (`n`)
|
|
* and the instruction before that loaded another integer (`n0`), but `n0` is not
|
|
* suitable for further folding (e.g., it's at a label, or the instruction
|
|
* before it isn't an integer load), it attempts to convert the operation to
|
|
* `OP_ADDI` or `OP_SUBI` if `n` fits within an 8-bit signed integer.
|
|
* 3. Constant Folding: If both the last two instructions loaded integer literals
|
|
* (`n0` and `n`), it performs the addition or subtraction at compile time.
|
|
* The program counter is rewound to the location of the first literal load,
|
|
* and code is generated to load the folded result directly using `gen_int`.
|
|
*
|
|
* If no optimizations are applicable, it generates the standard `OP_ADD` or `OP_SUB`
|
|
* instruction.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param op The operation code, either `OP_ADD` or `OP_SUB`.
|
|
* @param dst The destination register index for the result.
|
|
*/
|
|
static void
|
|
gen_addsub(codegen_scope *s, uint8_t op, uint16_t dst)
|
|
{
|
|
if (no_peephole(s)) {
|
|
normal:
|
|
genop_1(s, op, dst);
|
|
return;
|
|
}
|
|
else {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
mrb_int n;
|
|
|
|
if (!get_int_operand(s, &data, &n)) {
|
|
/* not integer immediate */
|
|
if (op == OP_ADD && data.insn == OP_STRING) {
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
if (data0.insn == OP_STRING) {
|
|
merge_op_string(s, dst, data0.b, data.b, data0.addr);
|
|
return;
|
|
}
|
|
}
|
|
goto normal;
|
|
}
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
mrb_int n0;
|
|
if (addr_pc(s, data.addr) == s->lastlabel || !get_int_operand(s, &data0, &n0)) {
|
|
/* Fold to OP_ADDI/OP_SUBI only for non-negative 8-bit n; flipping op
|
|
for negative n would change the method sent on user override (#2557). */
|
|
if (n < 0 || n > UINT8_MAX) goto normal;
|
|
rewind_pc(s);
|
|
if (n == 0) return;
|
|
if (op == OP_ADD) genop_2(s, OP_ADDI, dst, (uint16_t)n);
|
|
else genop_2(s, OP_SUBI, dst, (uint16_t)n);
|
|
return;
|
|
}
|
|
if (op == OP_ADD) {
|
|
if (mrb_int_add_overflow(n0, n, &n)) goto normal;
|
|
}
|
|
else { /* OP_SUB */
|
|
if (mrb_int_sub_overflow(n0, n, &n)) goto normal;
|
|
}
|
|
s->pc = addr_pc(s, data0.addr);
|
|
gen_int(s, dst, n);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generates code for multiplication (`OP_MUL`) or division (`OP_DIV`)
|
|
* operations, storing the result in register `dst`.
|
|
*
|
|
* Peephole Optimization (Constant Folding):
|
|
* - If peephole optimization is enabled and the two immediately preceding
|
|
* instructions loaded integer literals (into registers that are operands
|
|
* for this multiplication/division), this function performs the operation
|
|
* at compile time.
|
|
* - The program counter is rewound to the location of the first literal load,
|
|
* and code is generated to load the folded result directly using `gen_int`.
|
|
* - For division, if the divisor is zero or if it's `MRB_INT_MIN / -1` (which
|
|
* would overflow), the optimization is skipped.
|
|
*
|
|
* If no optimization is applicable, it generates the standard `OP_MUL` or `OP_DIV`
|
|
* instruction.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param op The operation code, either `OP_MUL` or `OP_DIV`.
|
|
* @param dst The destination register index for the result.
|
|
*/
|
|
static void
|
|
gen_muldiv(codegen_scope *s, uint8_t op, uint16_t dst)
|
|
{
|
|
if (no_peephole(s)) {
|
|
normal:
|
|
genop_1(s, op, dst);
|
|
return;
|
|
}
|
|
else {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
mrb_int n, n0;
|
|
if (addr_pc(s, data.addr) == s->lastlabel || !get_int_operand(s, &data, &n)) {
|
|
/* not integer immediate */
|
|
goto normal;
|
|
}
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
if (!get_int_operand(s, &data0, &n0)) {
|
|
goto normal;
|
|
}
|
|
if (op == OP_MUL) {
|
|
if (mrb_int_mul_overflow(n0, n, &n)) goto normal;
|
|
}
|
|
else { /* OP_DIV */
|
|
if (n == 0) goto normal;
|
|
if (n0 == MRB_INT_MIN && n == -1) goto normal;
|
|
n = mrb_div_int(n0, n);
|
|
}
|
|
s->pc = addr_pc(s, data0.addr);
|
|
gen_int(s, dst, n);
|
|
}
|
|
}
|
|
|
|
mrb_bool mrb_num_shift(mrb_state *mrb, mrb_int val, mrb_int width, mrb_int *num);
|
|
|
|
/*
|
|
* Generates code for various binary operations, identified by `sym_op`,
|
|
* storing the result in register `dst`.
|
|
*
|
|
* This function handles specific binary operations and includes peephole
|
|
* optimizations for constant folding when operands are integer literals.
|
|
*
|
|
* Operations Handled & Optimizations:
|
|
* - `aref` (`[]`): Generates `OP_GETIDX`.
|
|
* - Bitwise shifts (`<<`, `>>`): If both operands are integer literals,
|
|
* performs the shift at compile time using `mrb_num_shift` and loads the result.
|
|
* - Modulo (`%`): If both operands are integer literals, performs modulo
|
|
* at compile time and loads the result. Handles `MRB_INT_MIN % -1`.
|
|
* - Bitwise AND (`&`), OR (`|`), XOR (`^`): If both operands are integer
|
|
* literals, performs the operation at compile time and loads the result.
|
|
*
|
|
* If an optimization is applied (e.g., constant folding), the program counter
|
|
* is rewound, and `gen_int` is used to load the computed result.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param op The `mrb_sym` representing the binary operator (e.g., `MRB_OPSYM_LSHIFT`).
|
|
* @param dst The destination register index for the result.
|
|
* @return `TRUE` if a specific optimization was applied (like `OP_GETIDX` or constant folding),
|
|
* `FALSE` otherwise. A `FALSE` return typically indicates that a generic
|
|
* `OP_SEND` instruction should be generated for the operation.
|
|
*/
|
|
static mrb_bool
|
|
gen_binop(codegen_scope *s, mrb_sym op, uint16_t dst)
|
|
{
|
|
if (no_peephole(s)) return FALSE;
|
|
else if (op == MRB_OPSYM(aref)) {
|
|
/* GETIDX0 fusion: MOVE dst arr; LOADI_0 dst+1 -> GETIDX0 dst arr */
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
if (data.insn == OP_LOADI_0 && data.a == (uint32_t)dst+1 && addr_pc(s, data.addr) != s->lastlabel) {
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
if (data0.insn == OP_MOVE && data0.a == dst && data0.b != dst) {
|
|
s->pc = addr_pc(s, data0.addr);
|
|
genop_2(s, OP_GETIDX0, dst, data0.b);
|
|
return TRUE;
|
|
}
|
|
}
|
|
genop_1(s, OP_GETIDX, dst);
|
|
return TRUE;
|
|
}
|
|
else {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
mrb_int n, n0;
|
|
if (addr_pc(s, data.addr) == s->lastlabel || !get_int_operand(s, &data, &n)) {
|
|
/* not integer immediate */
|
|
return FALSE;
|
|
}
|
|
struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr));
|
|
if (!get_int_operand(s, &data0, &n0)) {
|
|
return FALSE;
|
|
}
|
|
if (op == MRB_OPSYM(lshift)) {
|
|
if (!mrb_num_shift(s->mrb, n0, n, &n)) return FALSE;
|
|
}
|
|
else if (op == MRB_OPSYM(rshift)) {
|
|
if (n == MRB_INT_MIN) return FALSE;
|
|
if (!mrb_num_shift(s->mrb, n0, -n, &n)) return FALSE;
|
|
}
|
|
else if (op == MRB_OPSYM(mod) && n != 0) {
|
|
if (n0 == MRB_INT_MIN && n == -1) {
|
|
n = 0;
|
|
}
|
|
else {
|
|
mrb_int n1 = n0 % n;
|
|
if ((n0 < 0) != (n < 0) && n1 != 0) {
|
|
n1 += n;
|
|
}
|
|
n = n1;
|
|
}
|
|
}
|
|
else if (op == MRB_OPSYM(and)) {
|
|
n = n0 & n;
|
|
}
|
|
else if (op == MRB_OPSYM(or)) {
|
|
n = n0 | n;
|
|
}
|
|
else if (op == MRB_OPSYM(xor)) {
|
|
n = n0 ^ n;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
s->pc = addr_pc(s, data0.addr);
|
|
gen_int(s, dst, n);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Resolves the target address of a previously generated jump instruction.
|
|
*
|
|
* Jump instructions are often generated with placeholder offsets (e.g., 0 or a
|
|
* link to another jump) when their final target is not yet known. This function
|
|
* patches such a jump.
|
|
*
|
|
* `pos0` is the program counter (address) of the 2-byte field within a jump
|
|
* instruction that holds its offset (or a link in a jump chain).
|
|
*
|
|
* The function calculates the correct relative offset from the instruction
|
|
* *after* the jump's offset field (`pos0 + 2`) to the current program
|
|
* counter (`s->pc`), which is the actual target of the jump. This calculated
|
|
* offset is then written back into the bytecode at `pos0`.
|
|
*
|
|
* If the original value at `pos0` was not 0 (i.e., it was part of a jump chain,
|
|
* pointing to the next jump to patch), this original value (which is an offset
|
|
* relative to `pos0 + 2`) is returned so that `dispatch_linked` can continue
|
|
* patching the chain. If the original value was 0, it signifies the end of a chain,
|
|
* and 0 is returned.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pos0 The address of the 2-byte offset field within a jump instruction.
|
|
* @return The next position in a jump chain to dispatch (calculated from the
|
|
* original offset stored at `pos0`), or 0 if it's the end of a chain.
|
|
*/
|
|
static uint32_t
|
|
dispatch(codegen_scope *s, uint32_t pos0)
|
|
{
|
|
int32_t pos1;
|
|
int32_t offset;
|
|
int16_t newpos;
|
|
|
|
if (pos0 == JMPLINK_START) return 0;
|
|
|
|
pos1 = pos0 + 2;
|
|
offset = s->pc - pos1;
|
|
if (offset > INT16_MAX) {
|
|
codegen_error(s, "too big jmp offset");
|
|
}
|
|
s->lastlabel = s->pc;
|
|
newpos = (int16_t)PEEK_S(s->iseq+pos0);
|
|
emit_S(s, pos0, (uint16_t)offset);
|
|
if (newpos == 0) return 0;
|
|
return pos1+newpos;
|
|
}
|
|
|
|
/*
|
|
* Patches a chain of linked jump instructions to all point to the current
|
|
* program counter (`s->pc`).
|
|
*
|
|
* Jump instructions whose targets are not yet known can be linked together.
|
|
* Each jump's offset field initially stores the relative offset to the next
|
|
* jump in the chain (or 0 if it's the last one). `pos` is the address of the
|
|
* first jump's offset field in such a chain.
|
|
*
|
|
* This function iterates through the chain:
|
|
* - It calls `dispatch(s, pos)` to patch the jump at `pos` to target the current `s->pc`.
|
|
* - `dispatch` returns the address of the next jump in the chain (or 0 if the end).
|
|
* - The process repeats until the end of the chain is reached.
|
|
*
|
|
* If `pos` is `JMPLINK_START`, it means there's no chain to dispatch, so it returns early.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param pos The address of the offset field of the first jump instruction in a linked chain.
|
|
*/
|
|
static void
|
|
dispatch_linked(codegen_scope *s, uint32_t pos)
|
|
{
|
|
if (pos==JMPLINK_START) return;
|
|
for (;;) {
|
|
pos = dispatch(s, pos);
|
|
if (pos==0) break;
|
|
}
|
|
}
|
|
|
|
/* Updates the nregs (number of registers used) if the current stack pointer (sp) exceeds it. */
|
|
#define nregs_update do {if (s->sp > s->nregs) s->nregs = s->sp;} while (0)
|
|
static void
|
|
push_n_(codegen_scope *s, int n)
|
|
{
|
|
if (s->sp+n >= 0xffff) {
|
|
codegen_error(s, "too complex expression");
|
|
}
|
|
s->sp+=n;
|
|
nregs_update;
|
|
}
|
|
|
|
static void
|
|
pop_n_(codegen_scope *s, int n)
|
|
{
|
|
if ((int)s->sp-n < 0) {
|
|
codegen_error(s, "stack pointer underflow");
|
|
}
|
|
s->sp-=n;
|
|
}
|
|
|
|
/* Increments the stack pointer (sp) by 1 and updates nregs. */
|
|
#define push() push_n_(s,1)
|
|
/* Increments the stack pointer (sp) by n and updates nregs. */
|
|
#define push_n(n) push_n_(s,n)
|
|
/* Decrements the stack pointer (sp) by 1. */
|
|
#define pop() pop_n_(s,1)
|
|
/* Decrements the stack pointer (sp) by n. */
|
|
#define pop_n(n) pop_n_(s,n)
|
|
/* Returns the current stack pointer (sp) value. */
|
|
#define cursp() (s->sp)
|
|
|
|
/*
|
|
* Extends the literal pool (`s->pool`) of the current IREP (`s->irep`) if necessary.
|
|
*
|
|
* If the number of literals currently in the pool (`s->irep->plen`) has reached
|
|
* the pool's capacity (`s->pcapa`), this function doubles the capacity by
|
|
* reallocating the `s->pool` array.
|
|
* After ensuring there's space, it increments `s->irep->plen` and returns a pointer
|
|
* to the newly available slot in the literal pool.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @return A pointer to the next available (or newly allocated) `mrb_irep_pool` entry.
|
|
*/
|
|
static mrb_irep_pool*
|
|
lit_pool_extend(codegen_scope *s)
|
|
{
|
|
if (s->irep->plen == s->pcapa) {
|
|
s->pcapa *= 2;
|
|
s->pool = (mrb_irep_pool*)mrbc_realloc(s->pool, sizeof(mrb_irep_pool)*s->pcapa);
|
|
}
|
|
|
|
return &s->pool[s->irep->plen++];
|
|
}
|
|
|
|
/* Helper functions for simple load operations that follow the pattern:
|
|
* if (!val) return; <prepare>; genop_X(...); push(); */
|
|
static void
|
|
gen_load_op1(codegen_scope *s, mrb_code op, int val)
|
|
{
|
|
if (!val) return;
|
|
genop_1(s, op, cursp());
|
|
push();
|
|
}
|
|
|
|
static void
|
|
gen_load_op2(codegen_scope *s, mrb_code op, uint16_t arg, int val)
|
|
{
|
|
if (!val) return;
|
|
genop_2(s, op, cursp(), arg);
|
|
push();
|
|
}
|
|
|
|
/* Helper function for conditional nil loading - loads nil only if val is needed */
|
|
static void
|
|
gen_load_nil(codegen_scope *s, int val)
|
|
{
|
|
if (!val) return;
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
|
|
/* Helper function for loading literal and pushing */
|
|
static void
|
|
gen_load_lit(codegen_scope *s, int off)
|
|
{
|
|
genop_2(s, OP_LOADL, cursp(), off);
|
|
push();
|
|
}
|
|
|
|
/*
|
|
* Adds a big integer literal (BigInt) to the IREP's literal pool.
|
|
* The BigInt is provided as a string `p` in the given `base`.
|
|
*
|
|
* - It first searches the existing literal pool to see if an identical BigInt
|
|
* (same string representation and base) already exists. If so, its index is returned.
|
|
* - If not found, a new entry is created in the pool:
|
|
* - The pool is extended if necessary using `lit_pool_extend`.
|
|
* - The new pool entry's type `tt` is set to `IREP_TT_BIGINT`.
|
|
* - Memory is allocated to store the BigInt's string representation, its length (1 byte),
|
|
* and its base (1 byte). The string `p` is copied into this buffer.
|
|
* - `pv->u.str` points to this allocated buffer.
|
|
* - If the length of the string `p` exceeds 255, a "integer too big" error is raised.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param p A string representing the big integer.
|
|
* @param base The base of the string representation (e.g., 10 for decimal).
|
|
* @return The index of the BigInt literal in the pool.
|
|
*/
|
|
static int
|
|
new_litbint(codegen_scope *s, const char *p, int base)
|
|
{
|
|
int i;
|
|
size_t plen;
|
|
mrb_irep_pool *pv;
|
|
|
|
plen = strlen(p);
|
|
if (plen > 255) {
|
|
codegen_error(s, "integer too big");
|
|
}
|
|
for (i=0; i<s->irep->plen; i++) {
|
|
size_t len;
|
|
pv = &s->pool[i];
|
|
if (pv->tt != IREP_TT_BIGINT) continue;
|
|
len = pv->u.str[0];
|
|
if (len == plen && pv->u.str[1] == base && memcmp(pv->u.str+2, p, len) == 0)
|
|
return i;
|
|
}
|
|
|
|
pv = lit_pool_extend(s);
|
|
|
|
char *buf;
|
|
pv->tt = IREP_TT_BIGINT;
|
|
buf = (char*)mrbc_malloc(plen+3);
|
|
buf[0] = (char)plen;
|
|
buf[1] = base;
|
|
memcpy(buf+2, p, plen);
|
|
buf[plen+2] = '\0';
|
|
pv->u.str = buf;
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Searches the IREP's literal pool for an existing string that is identical
|
|
* to the concatenation of `str1` (of length `len1`) and `str2` (of length `len2`).
|
|
*
|
|
* It iterates through the existing literal pool entries:
|
|
* - Skips entries that are not strings or are marked with `IREP_TT_NFLAG`.
|
|
* - Compares the total length (`len1 + len2`) with the length of the pool string.
|
|
* - If lengths match, it performs a `memcmp` to check if the content is identical
|
|
* to the concatenation of `str1` and `str2`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param str1 Pointer to the first part of the string to find.
|
|
* @param len1 Length of `str1`.
|
|
* @param str2 Pointer to the second part of the string to find (can be NULL if `len2` is 0).
|
|
* @param len2 Length of `str2`.
|
|
* @return The index of the matching string literal in the pool if found, otherwise -1.
|
|
*/
|
|
static int
|
|
find_pool_str(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2)
|
|
{
|
|
mrb_irep_pool *pool;
|
|
mrb_int len = len1 + len2;
|
|
int i;
|
|
|
|
for (i=0; i<s->irep->plen; i++) {
|
|
pool = &s->pool[i];
|
|
if (pool->tt & IREP_TT_NFLAG) continue;
|
|
mrb_int plen = pool->tt>>2;
|
|
if (len != plen) continue;
|
|
if (memcmp(pool->u.str, str1, len1) == 0 &&
|
|
(len2 == 0 || memcmp(pool->u.str + len1, str2, len2) == 0))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Adds a string literal, potentially formed by concatenating `str1` and `str2`,
|
|
* to the IREP's literal pool.
|
|
*
|
|
* - It first calls `find_pool_str` to check if an identical concatenated string
|
|
* already exists in the pool. If so, its index is returned.
|
|
* - If not found:
|
|
* - A new slot in the literal pool is obtained using `lit_pool_extend`.
|
|
* - If `str1` points to read-only data (`mrb_ro_data_p(str1)`) and `str2` is NULL
|
|
* (meaning `str1` is the complete string and it's from a static source),
|
|
* the pool entry is marked as `IREP_TT_SSTR` (shared string) and `pool->u.str`
|
|
* points directly to `str1`.
|
|
* - Otherwise (if the string needs to be dynamically created or is not from
|
|
* read-only data), memory is allocated for the combined length of `str1` and
|
|
* `str2` plus a null terminator. `str1` and `str2` (if present) are copied
|
|
* into this new buffer. The pool entry is marked as `IREP_TT_STR`, and
|
|
* `pool->u.str` points to this newly allocated buffer.
|
|
* - The index of the new or found literal is returned.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param str1 Pointer to the first part of the string.
|
|
* @param len1 Length of `str1`.
|
|
* @param str2 Pointer to the second part of the string (can be NULL if `len2` is 0).
|
|
* @param len2 Length of `str2`.
|
|
* @return The index of the string literal in the pool.
|
|
*/
|
|
static int
|
|
new_lit_str2(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2)
|
|
{
|
|
int i = find_pool_str(s, str1, len1, str2, len2);
|
|
|
|
if (i >= 0) return i;
|
|
i = s->irep->plen;
|
|
|
|
mrb_irep_pool *pool = lit_pool_extend(s);
|
|
mrb_int len = len1 + len2;
|
|
|
|
if (mrb_ro_data_p(str1) && !str2) {
|
|
pool->tt = (uint32_t)(len<<2) | IREP_TT_SSTR;
|
|
pool->u.str = str1;
|
|
}
|
|
else {
|
|
char *p;
|
|
pool->tt = (uint32_t)(len<<2) | IREP_TT_STR;
|
|
p = (char*)mrbc_malloc(len+1);
|
|
memcpy(p, str1, len1);
|
|
if (str2) memcpy(p+len1, str2, len2);
|
|
p[len] = '\0';
|
|
pool->u.str = p;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Adds a string literal (from `str` with length `len`) to the IREP's literal pool.
|
|
* This is a wrapper around `new_lit_str2`, passing NULL for `str2` and 0 for `len2`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param str Pointer to the string.
|
|
* @param len Length of the string.
|
|
* @return The index of the string literal in the pool.
|
|
*/
|
|
static int
|
|
new_lit_str(codegen_scope *s, const char *str, mrb_int len)
|
|
{
|
|
return new_lit_str2(s, str, len, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
* Adds a C-string literal (null-terminated string `str`) to the IREP's literal pool.
|
|
* This is a wrapper around `new_lit_str`, calculating the length of `str` using `strlen`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param str Pointer to the null-terminated C-string.
|
|
* @return The index of the string literal in the pool.
|
|
*/
|
|
static int
|
|
new_lit_cstr(codegen_scope *s, const char *str)
|
|
{
|
|
return new_lit_str(s, str, (mrb_int)strlen(str));
|
|
}
|
|
|
|
/*
|
|
* Adds an integer literal `num` to the IREP's literal pool.
|
|
*
|
|
* - It first searches the existing literal pool to see if an identical integer
|
|
* value already exists. If so, its index is returned.
|
|
* - If not found, a new entry is created:
|
|
* - The pool is extended if necessary using `lit_pool_extend`.
|
|
* - The new pool entry's type `tt` is set to `IREP_TT_INT32` or `IREP_TT_INT64`
|
|
* depending on whether `MRB_INT64` is defined.
|
|
* - The integer `num` is stored in `pool->u.i32` or `pool->u.i64`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param num The `mrb_int` value to add to the pool.
|
|
* @return The index of the integer literal in the pool.
|
|
*/
|
|
static int
|
|
new_lit_int(codegen_scope *s, mrb_int num)
|
|
{
|
|
int i;
|
|
mrb_irep_pool *pool;
|
|
|
|
for (i=0; i<s->irep->plen; i++) {
|
|
pool = &s->pool[i];
|
|
if (pool->tt == IREP_TT_INT32) {
|
|
if (num == pool->u.i32) return i;
|
|
}
|
|
#ifdef MRB_64BIT
|
|
else if (pool->tt == IREP_TT_INT64) {
|
|
if (num == pool->u.i64) return i;
|
|
}
|
|
continue;
|
|
#endif
|
|
}
|
|
|
|
pool = lit_pool_extend(s);
|
|
|
|
#ifdef MRB_INT64
|
|
pool->tt = IREP_TT_INT64;
|
|
pool->u.i64 = num;
|
|
#else
|
|
pool->tt = IREP_TT_INT32;
|
|
pool->u.i32 = num;
|
|
#endif
|
|
|
|
return i;
|
|
}
|
|
|
|
#ifndef MRB_NO_FLOAT
|
|
/*
|
|
* Adds a float literal `num` to the IREP's literal pool.
|
|
* This function is only compiled if `MRB_NO_FLOAT` is not defined.
|
|
*
|
|
* - It first searches the existing literal pool to see if an identical float
|
|
* value (considering both value and sign bit) already exists. If so, its
|
|
* index is returned.
|
|
* - If not found, a new entry is created:
|
|
* - The pool is extended if necessary using `lit_pool_extend`.
|
|
* - The new pool entry's type `tt` is set to `IREP_TT_FLOAT`.
|
|
* - The float `num` is stored in `pool->u.f`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param num The `mrb_float` value to add to the pool.
|
|
* @return The index of the float literal in the pool.
|
|
*/
|
|
static int
|
|
new_lit_float(codegen_scope *s, mrb_float num)
|
|
{
|
|
int i;
|
|
mrb_irep_pool *pool;
|
|
|
|
for (i=0; i<s->irep->plen; i++) {
|
|
mrb_float f;
|
|
pool = &s->pool[i];
|
|
if (pool->tt != IREP_TT_FLOAT) continue;
|
|
f = pool->u.f;
|
|
if (f == num && !signbit(f) == !signbit(num)) return i;
|
|
}
|
|
|
|
pool = lit_pool_extend(s);
|
|
|
|
pool->tt = IREP_TT_FLOAT;
|
|
pool->u.f = num;
|
|
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Adds a symbol `sym` to the IREP's symbol list (`s->syms`).
|
|
*
|
|
* - It first iterates through the existing symbols in `s->syms` (up to `s->irep->slen`)
|
|
* to check if the symbol `sym` already exists. If found, its index is returned.
|
|
* - If the symbol is not found:
|
|
* - It checks if the current symbol list capacity (`s->scapa`) is sufficient.
|
|
* If not, `s->scapa` is doubled, and `s->syms` is reallocated.
|
|
* If the new capacity would exceed 0xFFFF, a "too many symbols" error is raised.
|
|
* - The symbol `sym` is added to `s->syms` at the current end of the list (`s->irep->slen`).
|
|
* - `s->irep->slen` is incremented.
|
|
* - The index of the (newly added or existing) symbol is returned.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param sym The `mrb_sym` to add to the symbol list.
|
|
* @return The index of the symbol in the IREP's symbol list.
|
|
*/
|
|
static int
|
|
sym_idx(codegen_scope *s, mrb_sym sym)
|
|
{
|
|
int i, len;
|
|
|
|
mrb_assert(s->irep);
|
|
|
|
len = s->irep->slen;
|
|
for (i=0; i<len; i++) {
|
|
if (s->syms[i] == sym) return i;
|
|
}
|
|
if (s->irep->slen >= s->scapa) {
|
|
s->scapa *= 2;
|
|
if (s->scapa > 0xffff) {
|
|
codegen_error(s, "too many symbols");
|
|
}
|
|
s->syms = (mrb_sym*)mrbc_realloc(s->syms, sizeof(mrb_sym)*s->scapa);
|
|
}
|
|
s->syms[s->irep->slen] = sym;
|
|
return s->irep->slen++;
|
|
}
|
|
|
|
/*
|
|
* Generates an instruction to set a variable, where the variable is identified by a symbol.
|
|
* This is a generic helper for opcodes like `OP_SETGV`, `OP_SETIV`, `OP_SETCV`, `OP_SETCONST`.
|
|
*
|
|
* - It first ensures the symbol `sym` is in the IREP's symbol list by calling `sym_idx`,
|
|
* obtaining its index `idx`.
|
|
* - Peephole Optimization: If `val` is `NOVAL` (false) and peephole optimization is enabled,
|
|
* it checks if the immediately preceding instruction was an `OP_MOVE` into the `dst`
|
|
* register. If so, it means the value intended for the variable assignment was moved
|
|
* into `dst`. In this case, it rewinds the program counter and uses the original source
|
|
* register of that `OP_MOVE` as the source for the set operation, effectively using
|
|
* the value before it was moved to `dst`.
|
|
* - Finally, it generates the specified opcode `op` with operands `dst` (the source
|
|
* register for the value, possibly modified by peephole optimization) and `idx`
|
|
* (the symbol index) using `genop_2`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param op The specific set variable opcode (e.g., `OP_SETGV`, `OP_SETIV`).
|
|
* @param dst The register index holding the value to be assigned to the variable.
|
|
* @param sym The `mrb_sym` (symbol) identifying the variable.
|
|
* @param val A flag indicating context (often whether the value in `dst` is from an
|
|
* expression that should be preserved if the set operation is part of a larger one).
|
|
* If `NOVAL`, it enables the peephole optimization.
|
|
*/
|
|
static void
|
|
gen_setxv(codegen_scope *s, uint8_t op, uint16_t dst, mrb_sym sym, int val)
|
|
{
|
|
int idx = sym_idx(s, sym);
|
|
if (!val && !no_peephole(s)) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
if (data.insn == OP_MOVE && data.a == dst) {
|
|
dst = data.b;
|
|
rewind_pc(s);
|
|
}
|
|
}
|
|
genop_2(s, op, dst, idx);
|
|
}
|
|
|
|
/*
|
|
* Generates the most compact instruction(s) to load an integer literal `i`
|
|
* into the destination register `dst`.
|
|
*
|
|
* It employs a series of checks to use specialized, shorter opcodes for common integer values:
|
|
* - `OP_LOADI__1` for -1.
|
|
* - `OP_LOADINEG` for negative integers between -255 and -2 (operand is positive magnitude).
|
|
* - `OP_LOADI16` for negative integers fitting in a signed 16-bit integer (INT16_MIN to -256).
|
|
* - `OP_LOADI32` for negative integers fitting in a signed 32-bit integer (INT32_MIN to not fitting in 16-bit).
|
|
* - `OP_LOADI_0` through `OP_LOADI_7` for integers 0 through 7.
|
|
* - `OP_LOADI8` for positive integers between 8 and 255.
|
|
* - `OP_LOADI16` for positive integers fitting in a signed 16-bit integer (256 to INT16_MAX).
|
|
* - `OP_LOADI32` for positive integers fitting in a signed 32-bit integer (not fitting in 16-bit to INT32_MAX).
|
|
*
|
|
* If the integer `i` does not fit any of these specialized opcodes (i.e., it's too large
|
|
* or too small for `OP_LOADI32`), it falls back to `OP_LOADL`. This involves adding
|
|
* the integer to the IREP's literal pool using `new_lit_int` and then generating
|
|
* `OP_LOADL` with the resulting pool index.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param dst The destination register index where the integer will be loaded.
|
|
* @param i The `mrb_int` value to load.
|
|
*/
|
|
static void
|
|
gen_int(codegen_scope *s, uint16_t dst, mrb_int i)
|
|
{
|
|
if (i < 0) {
|
|
if (i == -1) genop_1(s, OP_LOADI__1, dst);
|
|
else if (i >= -0xff) genop_2(s, OP_LOADINEG, dst, (uint16_t)-i);
|
|
else if (i >= INT16_MIN) genop_2S(s, OP_LOADI16, dst, (uint16_t)i);
|
|
else if (i >= INT32_MIN) genop_2SS(s, OP_LOADI32, dst, (uint32_t)i);
|
|
else goto int_lit;
|
|
}
|
|
else if (i < 8) genop_1(s, OP_LOADI_0 + (uint8_t)i, dst);
|
|
else if (i <= 0xff) genop_2(s, OP_LOADI8, dst, (uint16_t)i);
|
|
else if (i <= INT16_MAX) genop_2S(s, OP_LOADI16, dst, (uint16_t)i);
|
|
else if (i <= INT32_MAX) genop_2SS(s, OP_LOADI32, dst, (uint32_t)i);
|
|
else {
|
|
int_lit:
|
|
genop_2(s, OP_LOADL, dst, new_lit_int(s, i));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generates code for a unary operation specified by `sym`, operating on the
|
|
* value in register `dst`, and storing the result back into `dst`.
|
|
*
|
|
* Supported unary operations:
|
|
* - Unary plus (`+`): This is a no-op in terms of value change, but the function
|
|
* still processes it.
|
|
* - Unary minus (`-`): Negates the integer value.
|
|
* - Bitwise NOT (`~`): Performs a bitwise complement on the integer value.
|
|
*
|
|
* Peephole Optimization (Constant Folding):
|
|
* - If peephole optimization is enabled and the immediately preceding instruction
|
|
* loaded an integer literal into register `dst` (which is also the operand
|
|
* register for this unary operation), this function performs the unary operation
|
|
* at compile time.
|
|
* - The program counter is rewound to the location of the literal load, and code
|
|
* is generated to load the folded result directly using `gen_int`.
|
|
* - For unary minus, if the original integer is `MRB_INT_MIN`, constant folding
|
|
* is skipped to avoid overflow.
|
|
*
|
|
* If the operation is not one of the recognized unary ops or if constant folding
|
|
* is not applicable, the function returns `FALSE`.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param sym The `mrb_sym` representing the unary operator (e.g., `MRB_OPSYM_PLUS`, `MRB_OPSYM_MINUS`).
|
|
* @param dst The register index which holds the operand and will store the result.
|
|
* @return `TRUE` if a constant folding optimization was successfully applied,
|
|
* `FALSE` otherwise (e.g., if the operation is not supported for folding,
|
|
* or if the preceding instruction was not a suitable integer load).
|
|
*/
|
|
static mrb_bool
|
|
gen_uniop(codegen_scope *s, mrb_sym sym, uint16_t dst)
|
|
{
|
|
if (no_peephole(s)) return FALSE;
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
mrb_int n;
|
|
|
|
if (!get_int_operand(s, &data, &n)) return FALSE;
|
|
if (sym == MRB_OPSYM(plus)) {
|
|
/* unary plus does nothing */
|
|
}
|
|
else if (sym == MRB_OPSYM(minus)) {
|
|
if (n == MRB_INT_MIN) return FALSE;
|
|
n = -n;
|
|
}
|
|
else if (sym == MRB_OPSYM(neg)) {
|
|
n = ~n;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
s->pc = addr_pc(s, data.addr);
|
|
gen_int(s, dst, n);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Calculates and returns the number of elements in a linked list of AST nodes.
|
|
* The list is traversed via the `cdr` field of each `node`.
|
|
*
|
|
* @param tree Pointer to the head of the AST node list.
|
|
* @return The number of nodes in the list.
|
|
*/
|
|
static int
|
|
node_len(node *tree)
|
|
{
|
|
int n = 0;
|
|
|
|
/* Validate pointer before using it */
|
|
if (!tree || ((uintptr_t)tree < 0x1000)) {
|
|
return 0;
|
|
}
|
|
|
|
while (tree) {
|
|
n++;
|
|
tree = tree->cdr;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* Casts a void* (typically from an AST node part) to an int. */
|
|
#define node_to_sym(x) ((mrb_sym)(intptr_t)(x))
|
|
#define node_to_int(x) ((int)(intptr_t)(x))
|
|
/* Casts a void* (typically from an AST node part) to a char. */
|
|
#define node_to_char(x) ((char)(intptr_t)(x))
|
|
/* Casts a void* (typically from an AST node part) to an mrb_sym. */
|
|
|
|
/* Extracts the symbol (name) of a local variable from its AST node representation. */
|
|
#define lv_name(lv) node_to_sym((lv)->car)
|
|
|
|
/*
|
|
* Searches for a local variable `id` within the current scope's local variable list (`s->lv`).
|
|
* The local variable list `s->lv` is a linked list of AST nodes, where each node's
|
|
* `car` holds the symbol of the local variable.
|
|
*
|
|
* @param s The current code generation scope.
|
|
* @param id The `mrb_sym` (symbol) of the local variable to search for.
|
|
* @return The 1-based index of the local variable in the current scope if found;
|
|
* otherwise, returns 0.
|
|
*/
|
|
static int
|
|
lv_idx(codegen_scope *s, mrb_sym id)
|
|
{
|
|
node *lv = s->lv;
|
|
int n = 1;
|
|
|
|
while (lv) {
|
|
if (lv_name(lv) == id) return n;
|
|
n++;
|
|
lv = lv->cdr;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
search_upvar(codegen_scope *s, mrb_sym id, int *idx)
|
|
{
|
|
const struct RProc *u;
|
|
int lv = 0;
|
|
codegen_scope *up = s->prev;
|
|
|
|
while (up) {
|
|
*idx = lv_idx(up, id);
|
|
if (*idx > 0) {
|
|
return lv;
|
|
}
|
|
lv++;
|
|
up = up->prev;
|
|
}
|
|
|
|
if (lv < 1) lv = 1;
|
|
u = s->parser->upper;
|
|
while (u && !MRB_PROC_CFUNC_P(u)) {
|
|
const struct mrb_irep *ir = u->body.irep;
|
|
uint_fast16_t n = ir->nlocals;
|
|
int i;
|
|
|
|
const mrb_sym *v = ir->lv;
|
|
if (v) {
|
|
for (i=1; n > 1; n--, v++, i++) {
|
|
if (*v == id) {
|
|
*idx = i;
|
|
return lv - 1;
|
|
}
|
|
}
|
|
}
|
|
if (MRB_PROC_SCOPE_P(u)) break;
|
|
u = u->upper;
|
|
lv++;
|
|
}
|
|
|
|
if (id == MRB_OPSYM(and)) {
|
|
codegen_error(s, "No anonymous block parameter");
|
|
}
|
|
else if (id == MRB_OPSYM(mul)) {
|
|
codegen_error(s, "No anonymous rest parameter");
|
|
}
|
|
else if (id == MRB_OPSYM(pow)) {
|
|
codegen_error(s, "No anonymous keyword rest parameter");
|
|
}
|
|
else {
|
|
codegen_error(s, "Can't find local variables");
|
|
}
|
|
return -1; /* not reached */
|
|
}
|
|
|
|
/*
|
|
* Generates the bytecode for the body of a lambda or a block.
|
|
* This function is responsible for creating a new scope, handling arguments
|
|
* (including optional, rest, keyword, and block arguments), generating code
|
|
* for the body's expressions, and finalizing the resulting `mrb_irep`.
|
|
*
|
|
* @param s The parent code generation scope.
|
|
* @param tree The AST node representing the lambda or block.
|
|
* `tree->car` contains the argument list AST.
|
|
* `tree->cdr->car` is the body of the lambda/block.
|
|
* @param blk A flag indicating if this is a block (`TRUE`) or a lambda (`FALSE`).
|
|
* This affects `s->mscope` and loop setup.
|
|
* @return The index of the newly created `mrb_irep` in the parent scope's `reps` array.
|
|
*/
|
|
static int
|
|
lambda_body(codegen_scope *s, node *locals, struct mrb_ast_args *args, node *body, int blk)
|
|
{
|
|
codegen_scope *parent = s;
|
|
/* Create a new scope for the lambda/block body. */
|
|
s = scope_new(s->mrb, s, locals);
|
|
|
|
/* `mscope` is false for blocks, true for lambdas/methods. */
|
|
s->mscope = !blk;
|
|
|
|
/* If it's a block, push a LOOP_BLOCK structure for break/next/return handling. */
|
|
if (blk) {
|
|
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
|
|
lp->pc0 = new_label(s); /* Mark entry point for potential retry/redo. */
|
|
}
|
|
|
|
/* Argument processing */
|
|
if (args == NULL) { /* No arguments */
|
|
genop_W(s, OP_ENTER, 0); /* Generate OP_ENTER with no argument specification. */
|
|
s->ainfo = 0;
|
|
}
|
|
else { /* Has arguments */
|
|
mrb_aspec a;
|
|
int ma, oa, ra, pa, ka, kd, ba, i;
|
|
uint32_t pos;
|
|
node *opt;
|
|
node *margs, *pargs;
|
|
|
|
/* args is already struct mrb_ast_args * */
|
|
|
|
/* mandatory arguments */
|
|
ma = node_len(args->mandatory_args);
|
|
margs = args->mandatory_args;
|
|
|
|
/* optional arguments */
|
|
oa = node_len(args->optional_args);
|
|
/* rest argument? */
|
|
ra = args->rest_arg ? 1 : 0;
|
|
/* mandatory arguments after rest argument */
|
|
pa = node_len(args->post_mandatory_args);
|
|
pargs = args->post_mandatory_args;
|
|
|
|
/* keyword arguments */
|
|
ka = args->keyword_args ? node_len(args->keyword_args) : 0;
|
|
kd = args->kwrest_arg ? 1 : 0;
|
|
/* &nil: no block accepted (noblock flag in aspec) */
|
|
mrb_bool noblock = args->block_arg == MRB_SYM(nil);
|
|
ba = (args->block_arg && !noblock) ? 1 : 0;
|
|
|
|
if (ma > 0x1f || oa > 0x1f || pa > 0x1f || ka > 0x1f) {
|
|
codegen_error(s, "too many formal arguments");
|
|
}
|
|
/* (24bits = 1:5:5:1:5:5:1:1) */
|
|
a = (noblock ? MRB_ARGS_NOBLOCK() : 0)
|
|
| MRB_ARGS_REQ(ma)
|
|
| MRB_ARGS_OPT(oa)
|
|
| (ra ? MRB_ARGS_REST() : 0)
|
|
| MRB_ARGS_POST(pa)
|
|
| MRB_ARGS_KEY(ka, kd)
|
|
| (ba ? MRB_ARGS_BLOCK() : 0);
|
|
genop_W(s, OP_ENTER, a);
|
|
/* (13bits = 6:1:5:1:1) - Store argument counts for block argument passing (OP_BLKPUSH) */
|
|
s->ainfo = (((ma+oa) & 0x3f) << 7)
|
|
| ((ra & 0x1) << 6)
|
|
| ((pa & 0x1f) << 1)
|
|
| (ka || kd)
|
|
| ((ba & 0x1) << 13);
|
|
|
|
/* Optional argument default value initialization */
|
|
pos = new_label(s); /* Start of the optional argument jump table. */
|
|
for (i=0; i<oa; i++) {
|
|
new_label(s);
|
|
genjmp_0(s, OP_JMP); /* Placeholder jump for each optional arg. */
|
|
}
|
|
if (oa > 0) {
|
|
genjmp_0(s, OP_JMP); /* Jump to skip all default assignments if all optional args are provided. */
|
|
}
|
|
opt = args->optional_args; /* AST node for optional arguments. */
|
|
i = 0;
|
|
while (opt) { /* Iterate through optional arguments. */
|
|
int idx;
|
|
mrb_sym id = node_to_sym(opt->car->car); /* Symbol of the optional argument. */
|
|
|
|
dispatch(s, pos+i*3+1); /* Patch the jump to this argument's default value code. */
|
|
codegen(s, opt->car->cdr, VAL); /* Generate code for the default value expression. */
|
|
pop();
|
|
idx = lv_idx(s, id); /* Get local variable index. */
|
|
if (idx > 0) {
|
|
gen_move(s, idx, cursp(), 0); /* Move default value to the local variable. */
|
|
}
|
|
else { /* Should not happen for optional args, but handle as upvar if it does. */
|
|
gen_getupvar(s, cursp(), id);
|
|
}
|
|
i++;
|
|
opt = opt->cdr;
|
|
}
|
|
if (oa > 0) {
|
|
dispatch(s, pos+i*3+1); /* Patch the final jump to after all default assignments. */
|
|
}
|
|
|
|
/* Keyword argument processing */
|
|
if (ka > 0 || kd > 0) { /* Has keyword arguments or keyword rest */
|
|
node *kwds;
|
|
int kwrest = kd; /* Flag for keyword rest argument (e.g., **kwargs) */
|
|
|
|
kwds = args->keyword_args;
|
|
|
|
while (kwds) {
|
|
int jmpif_key_p, jmp_def_set = -1;
|
|
node *kwd = kwds->car;
|
|
mrb_sym kwd_sym = node_to_sym(kwd->car); /* Direct access to key */
|
|
node *def_arg = kwd->cdr; /* Direct access to value */
|
|
|
|
if (def_arg) {
|
|
int idx;
|
|
genop_2(s, OP_KEY_P, lv_idx(s, kwd_sym), sym_idx(s, kwd_sym));
|
|
jmpif_key_p = genjmp2_0(s, OP_JMPIF, lv_idx(s, kwd_sym), NOVAL);
|
|
codegen(s, def_arg, VAL);
|
|
pop();
|
|
idx = lv_idx(s, kwd_sym);
|
|
if (idx > 0) {
|
|
gen_move(s, idx, cursp(), 0);
|
|
}
|
|
else {
|
|
gen_getupvar(s, cursp(), kwd_sym);
|
|
}
|
|
jmp_def_set = genjmp_0(s, OP_JMP);
|
|
dispatch(s, jmpif_key_p);
|
|
}
|
|
genop_2(s, OP_KARG, lv_idx(s, kwd_sym), sym_idx(s, kwd_sym));
|
|
if (jmp_def_set != -1) {
|
|
dispatch(s, jmp_def_set);
|
|
}
|
|
i++;
|
|
|
|
kwds = kwds->cdr;
|
|
}
|
|
/* Check if there are keyword args but no keyword rest */
|
|
int has_keywords = args->keyword_args != NULL;
|
|
|
|
if (has_keywords && !kwrest) { /* If there are keyword args but no keyword rest. */
|
|
genop_0(s, OP_KEYEND); /* Signal end of keyword arguments. */
|
|
|
|
/* Reconstruct keyword hash for super to use */
|
|
/* After KEYEND, the hash at kw_pos is empty (all keys deleted by KARG) */
|
|
/* Build a fresh hash from the extracted keyword local variables */
|
|
int kw_dict_pos = ma + oa + ra + pa + 1;
|
|
int sp_save = cursp();
|
|
|
|
/* Load key-value pairs for each keyword argument starting at stack position */
|
|
node *kw_list = args->keyword_args;
|
|
int num_pairs = 0;
|
|
while (kw_list) {
|
|
node *kw = kw_list->car;
|
|
mrb_sym kw_sym = node_to_sym(kw->car);
|
|
|
|
/* Load symbol (key) */
|
|
genop_2(s, OP_LOADSYM, cursp(), sym_idx(s, kw_sym));
|
|
push();
|
|
|
|
/* Load keyword local variable value */
|
|
genop_2(s, OP_MOVE, cursp(), lv_idx(s, kw_sym));
|
|
push();
|
|
|
|
num_pairs++;
|
|
kw_list = kw_list->cdr;
|
|
}
|
|
|
|
/* Create hash at current stack position, then move to keyword dict position */
|
|
if (num_pairs > 0) {
|
|
genop_2(s, OP_HASH, sp_save, num_pairs);
|
|
genop_2(s, OP_MOVE, kw_dict_pos, sp_save);
|
|
}
|
|
else {
|
|
/* No keyword args, create empty hash */
|
|
genop_2(s, OP_HASH, sp_save, 0);
|
|
genop_2(s, OP_MOVE, kw_dict_pos, sp_save);
|
|
}
|
|
|
|
/* Restore stack pointer */
|
|
s->sp = sp_save;
|
|
}
|
|
}
|
|
|
|
/* Block argument processing */
|
|
if (ba) { /* If a block argument (e.g., &blk) is present. */
|
|
mrb_sym bparam = args->block_arg;
|
|
pos = ma+oa+ra+pa+(ka||kd); /* Calculate register offset for the block parameter. */
|
|
if (bparam) { /* If it's a named block parameter. */
|
|
int idx = lv_idx(s, bparam);
|
|
genop_2(s, OP_MOVE, idx, pos+1); /* Move the block from its argument slot to the local variable. */
|
|
}
|
|
}
|
|
|
|
/* Argument destructuring for mandatory and post-mandatory arguments */
|
|
if (margs) { /* Mandatory arguments */
|
|
node *n = margs;
|
|
pos = 1; /* Start from register 1 (after self). */
|
|
while (n) {
|
|
if (node_type(n->car) == NODE_MARG) { /* If the argument is a mass assignment (e.g., |(a,b)| ). */
|
|
struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car;
|
|
/* Use dedicated parameter destructuring logic instead of general codegen_masgn */
|
|
int nn = 0;
|
|
/* Handle pre variables */
|
|
if (masgn_n->pre) {
|
|
node *pre = masgn_n->pre;
|
|
while (pre) {
|
|
int sp = cursp();
|
|
genop_3(s, OP_AREF, sp, pos, nn);
|
|
push();
|
|
gen_assignment(s, pre->car, NULL, sp, NOVAL);
|
|
pop();
|
|
nn++;
|
|
pre = pre->cdr;
|
|
}
|
|
}
|
|
/* For now, only handle simple pre variables - rest/post would need more complex logic */
|
|
}
|
|
pos++;
|
|
n = n->cdr;
|
|
}
|
|
}
|
|
if (pargs) { /* Post-mandatory arguments */
|
|
node *n = pargs;
|
|
pos = ma+oa+ra+1; /* Calculate starting register for post-mandatory args. */
|
|
while (n) {
|
|
if (node_type(n->car) == NODE_MARG) { /* If argument is a mass assignment. */
|
|
struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car;
|
|
/* Use dedicated parameter destructuring logic instead of general codegen_masgn */
|
|
int nn = 0;
|
|
/* Handle pre variables */
|
|
if (masgn_n->pre) {
|
|
node *pre = masgn_n->pre;
|
|
while (pre) {
|
|
int sp = cursp();
|
|
genop_3(s, OP_AREF, sp, pos, nn);
|
|
push();
|
|
gen_assignment(s, pre->car, NULL, sp, NOVAL);
|
|
pop();
|
|
nn++;
|
|
pre = pre->cdr;
|
|
}
|
|
}
|
|
/* For now, only handle simple pre variables - rest/post would need more complex logic */
|
|
}
|
|
pos++;
|
|
n = n->cdr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Generate code for the actual body of the lambda/block. */
|
|
codegen(s, body, VAL);
|
|
pop(); /* Pop the result of the body. */
|
|
|
|
/* Implicit return of the last evaluated expression. */
|
|
if (s->pc > 0) { /* Ensure there's some code before adding return. */
|
|
gen_return(s, OP_RETURN, cursp());
|
|
}
|
|
|
|
if (blk) {
|
|
loop_pop(s, NOVAL); /* Pop the LOOP_BLOCK structure. */
|
|
}
|
|
scope_finish(s); /* Finalize the IREP for this lambda/block. */
|
|
return parent->irep->rlen - 1; /* Return the index of this IREP in the parent's REP list. */
|
|
}
|
|
|
|
/*
|
|
* Generates code for a new lexical scope, typically for class/module definitions
|
|
* or the top-level script.
|
|
*
|
|
* This function handles the creation of a new `codegen_scope`, recursively
|
|
* generates code for the body of that scope, and then finalizes the scope
|
|
* to produce an `mrb_irep`.
|
|
*
|
|
* @param s The parent code generation scope.
|
|
* @param tree The AST node representing the scope.
|
|
* `tree->car` contains the list of local variables for the new scope.
|
|
* `tree->cdr` is the body (sequence of expressions) of the scope.
|
|
* @param val Unused in this specific function's direct logic for return value,
|
|
* but passed to `codegen` for the body.
|
|
* @return The index of the newly created `mrb_irep` in the parent scope's `reps` array.
|
|
* Returns 0 if `s->irep` is NULL (should not happen in normal operation).
|
|
*/
|
|
static int
|
|
scope_body(codegen_scope *s, node *locals, node *body, int val)
|
|
{
|
|
/* Create a new scope, inheriting from `s`, with local variables from `locals`. */
|
|
codegen_scope *scope = scope_new(s->mrb, s, locals);
|
|
|
|
/* Generate code for the body of the scope. */
|
|
codegen(scope, body, val);
|
|
|
|
/* If this is the outermost scope (e.g., top-level script), add OP_STOP. */
|
|
if (!s->iseq) { /* s->iseq would be NULL for the initial dummy scope. */
|
|
if (val) {
|
|
gen_return(scope, OP_RETURN, scope->sp-1);
|
|
}
|
|
/* skip RETURN when no_return_value; STOP will terminate VM */
|
|
genop_0(scope, OP_STOP);
|
|
}
|
|
else {
|
|
/* Ensure the scope returns the value of its last expression. */
|
|
if (val) {
|
|
gen_return(scope, OP_RETURN, scope->sp-1);
|
|
}
|
|
else {
|
|
gen_return(scope, OP_RETURN, 0); /* return nil */
|
|
}
|
|
}
|
|
|
|
/* Finalize the IREP for this scope. */
|
|
scope_finish(scope);
|
|
|
|
if (!s->irep) {
|
|
/* This case should ideally not be reached in normal compilation. */
|
|
return 0;
|
|
}
|
|
/* Return the index of the newly created IREP in the parent's list of REPs. */
|
|
return s->irep->rlen - 1;
|
|
}
|
|
|
|
static struct mrb_ast_var_header*
|
|
get_var_header(node *n)
|
|
{
|
|
if (!n) return NULL;
|
|
|
|
/* Try to interpret as variable-sized node */
|
|
struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n;
|
|
return header;
|
|
}
|
|
|
|
/* Helper to detect splat nodes in variable-sized format */
|
|
static mrb_bool
|
|
is_splat_node(node *n)
|
|
{
|
|
return (node_type(n) == NODE_SPLAT);
|
|
}
|
|
|
|
static mrb_bool
|
|
nosplat(node *t)
|
|
{
|
|
while (t) {
|
|
if (is_splat_node(t->car)) return FALSE;
|
|
t = t->cdr;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Check if node is a simple literal that can be generated into any register */
|
|
static mrb_bool
|
|
is_simple_literal(node *n)
|
|
{
|
|
switch (node_type(n)) {
|
|
case NODE_INT:
|
|
case NODE_NIL:
|
|
case NODE_TRUE:
|
|
case NODE_FALSE:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Check if all lhs are local variables and get their registers */
|
|
static mrb_bool
|
|
all_lvar_pre(codegen_scope *s, node *pre, int *regs, int max)
|
|
{
|
|
int i = 0;
|
|
while (pre && i < max) {
|
|
if (node_type(pre->car) != NODE_LVAR) return FALSE;
|
|
int idx = lv_idx(s, var_node(pre->car)->symbol);
|
|
if (idx <= 0) return FALSE; /* not a local variable */
|
|
regs[i++] = idx;
|
|
pre = pre->cdr;
|
|
}
|
|
return pre == NULL; /* all processed */
|
|
}
|
|
|
|
/* Generate a simple literal directly into a specific register */
|
|
static void
|
|
gen_literal_to_reg(codegen_scope *s, node *n, int reg)
|
|
{
|
|
switch (node_type(n)) {
|
|
case NODE_INT:
|
|
gen_int(s, reg, int_node(n)->value);
|
|
break;
|
|
case NODE_NIL:
|
|
genop_1(s, OP_LOADNIL, reg);
|
|
break;
|
|
case NODE_TRUE:
|
|
genop_1(s, OP_LOADTRUE, reg);
|
|
break;
|
|
case NODE_FALSE:
|
|
genop_1(s, OP_LOADFALSE, reg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static mrb_sym
|
|
attrsym(codegen_scope *s, mrb_sym a)
|
|
{
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(s->mrb, a, &len);
|
|
char *name2 = (char*)codegen_palloc(s,
|
|
(size_t)len
|
|
+ 1 /* '=' */
|
|
+ 1 /* '\0' */
|
|
);
|
|
mrb_assert_int_fit(mrb_int, len, size_t, SIZE_MAX);
|
|
memcpy(name2, name, (size_t)len);
|
|
name2[len] = '=';
|
|
name2[len+1] = '\0';
|
|
|
|
return mrb_intern(s->mrb, name2, len+1);
|
|
}
|
|
|
|
/* Maximum number of arguments for a call that can be encoded directly in some opcodes (e.g. OP_SEND). */
|
|
#define CALL_MAXARGS 15
|
|
/* Maximum number of elements in a literal array/hash handled by simpler opcodes before needing OP_ARYPUSH/OP_HASHADD. */
|
|
#define GEN_LIT_ARY_MAX 64
|
|
/* Stack pointer threshold during value sequence generation; if cursp() exceeds this, intermediate arrays might be formed. */
|
|
#define GEN_VAL_STACK_MAX 99
|
|
|
|
static int
|
|
gen_values(codegen_scope *s, node *t, int val, int limit)
|
|
{
|
|
int n = 0;
|
|
int first = 1;
|
|
int slimit = GEN_VAL_STACK_MAX;
|
|
|
|
if (limit == 0) limit = GEN_LIT_ARY_MAX;
|
|
if (cursp() >= slimit) slimit = INT16_MAX;
|
|
|
|
if (!val) {
|
|
while (t) {
|
|
codegen(s, t->car, NOVAL);
|
|
n++;
|
|
t = t->cdr;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
while (t) {
|
|
int is_splat = is_splat_node(t->car);
|
|
|
|
/* Optimization: skip or inline literal splat arrays
|
|
* - Empty splat (`*[]`/`*zarray`): contributes nothing; skip.
|
|
* - Non-empty literal array with no inner splat (`*[a,b]`): inline
|
|
* as normal positional args to avoid building/concatenating arrays.
|
|
*/
|
|
if (is_splat) {
|
|
struct mrb_ast_splat_node *splat = splat_node(t->car);
|
|
node *sv = splat->value;
|
|
if (sv) {
|
|
enum node_type nt = node_type(sv);
|
|
if (nt == NODE_ARRAY) {
|
|
struct mrb_ast_array_node *an = array_node(sv);
|
|
if (an->elements == NULL) {
|
|
/* empty splat; contributes nothing */
|
|
t = t->cdr;
|
|
continue;
|
|
}
|
|
else if (nosplat(an->elements)) {
|
|
/* Inline non-empty literal array elements as regular args */
|
|
node *e = an->elements;
|
|
while (e) {
|
|
/* Honor evaluation order */
|
|
codegen(s, e->car, val);
|
|
n++;
|
|
e = e->cdr;
|
|
}
|
|
t = t->cdr;
|
|
continue;
|
|
}
|
|
}
|
|
else if (nt == NODE_ZARRAY) {
|
|
/* explicit empty array literal */
|
|
t = t->cdr;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_splat || cursp() >= slimit) { /* flush stack */
|
|
pop_n(n);
|
|
if (first) {
|
|
if (n == 0) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
else {
|
|
genop_2(s, OP_ARRAY, cursp(), n);
|
|
}
|
|
push();
|
|
first = 0;
|
|
limit = GEN_LIT_ARY_MAX;
|
|
}
|
|
else if (n > 0) {
|
|
pop();
|
|
genop_2(s, OP_ARYPUSH, cursp(), n);
|
|
push();
|
|
}
|
|
n = 0;
|
|
}
|
|
codegen(s, t->car, val);
|
|
if (is_splat) {
|
|
pop(); pop();
|
|
genop_1(s, OP_ARYCAT, cursp());
|
|
push();
|
|
}
|
|
else {
|
|
n++;
|
|
}
|
|
t = t->cdr;
|
|
}
|
|
if (!first) {
|
|
pop();
|
|
if (n > 0) {
|
|
pop_n(n);
|
|
genop_2(s, OP_ARYPUSH, cursp(), n);
|
|
}
|
|
return -1; /* variable length */
|
|
}
|
|
else if (n > limit) {
|
|
pop_n(n);
|
|
genop_2(s, OP_ARRAY, cursp(), n);
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
gen_hash(codegen_scope *s, node *tree, int val, int limit)
|
|
{
|
|
int slimit = GEN_VAL_STACK_MAX;
|
|
if (cursp() >= GEN_LIT_ARY_MAX) slimit = INT16_MAX;
|
|
int len = 0;
|
|
mrb_bool update = FALSE;
|
|
mrb_bool first = TRUE;
|
|
|
|
while (tree) {
|
|
if (node_to_sym(tree->car->car) == MRB_OPSYM(pow)) {
|
|
if (val && first) {
|
|
genop_2(s, OP_HASH, cursp(), 0);
|
|
push();
|
|
update = TRUE;
|
|
}
|
|
else if (val && len > 0) {
|
|
pop_n(len*2);
|
|
if (!update) {
|
|
genop_2(s, OP_HASH, cursp(), len);
|
|
}
|
|
else {
|
|
pop();
|
|
genop_2(s, OP_HASHADD, cursp(), len);
|
|
}
|
|
push();
|
|
}
|
|
codegen(s, tree->car->cdr, val);
|
|
if (val && (len > 0 || update)) {
|
|
pop(); pop();
|
|
genop_1(s, OP_HASHCAT, cursp());
|
|
push();
|
|
}
|
|
update = TRUE;
|
|
len = 0;
|
|
}
|
|
else {
|
|
codegen(s, tree->car->car, val);
|
|
codegen(s, tree->car->cdr, val);
|
|
len++;
|
|
}
|
|
tree = tree->cdr;
|
|
if (val && cursp() >= slimit) {
|
|
pop_n(len*2);
|
|
if (!update) {
|
|
genop_2(s, OP_HASH, cursp(), len);
|
|
}
|
|
else {
|
|
pop();
|
|
genop_2(s, OP_HASHADD, cursp(), len);
|
|
}
|
|
push();
|
|
update = TRUE;
|
|
len = 0;
|
|
}
|
|
first = FALSE;
|
|
}
|
|
if (val && len > limit) {
|
|
pop_n(len*2);
|
|
genop_2(s, OP_HASH, cursp(), len);
|
|
push();
|
|
return -1;
|
|
}
|
|
if (update) {
|
|
if (val && len > 0) {
|
|
pop_n(len*2+1);
|
|
genop_2(s, OP_HASHADD, cursp(), len);
|
|
push();
|
|
}
|
|
return -1; /* variable length */
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
static void
|
|
gen_colon_assign_common(codegen_scope *s, node *rhs, int sp, int val, int idx, int final_op)
|
|
{
|
|
if (rhs) {
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
gen_move(s, sp, cursp(), 0);
|
|
}
|
|
pop(); pop();
|
|
genop_2(s, final_op, cursp(), idx);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
gen_colon2_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val)
|
|
{
|
|
struct mrb_ast_colon2_node *n = (struct mrb_ast_colon2_node*)varnode;
|
|
int idx;
|
|
|
|
if (sp) {
|
|
gen_move(s, cursp(), sp, 0);
|
|
}
|
|
sp = cursp();
|
|
push();
|
|
codegen(s, n->base, VAL);
|
|
idx = sym_idx(s, n->name);
|
|
gen_colon_assign_common(s, rhs, sp, val, idx, OP_SETMCNST);
|
|
}
|
|
|
|
static void
|
|
gen_colon3_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val)
|
|
{
|
|
struct mrb_ast_colon3_node *n = (struct mrb_ast_colon3_node*)varnode;
|
|
int idx;
|
|
|
|
if (sp) {
|
|
gen_move(s, cursp(), sp, 0);
|
|
}
|
|
sp = cursp();
|
|
push();
|
|
genop_1(s, OP_OCLASS, cursp());
|
|
push();
|
|
idx = sym_idx(s, n->name);
|
|
gen_colon_assign_common(s, rhs, sp, val, idx, OP_SETCONST);
|
|
}
|
|
|
|
static void
|
|
gen_xvar_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val, uint8_t op)
|
|
{
|
|
struct mrb_ast_var_node *var = (struct mrb_ast_var_node*)tree;
|
|
if (rhs) {
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
sp = cursp();
|
|
}
|
|
gen_setxv(s, op, sp, var->symbol, val);
|
|
}
|
|
|
|
static void
|
|
gen_xvar(codegen_scope *s, mrb_sym sym, int val, uint8_t op)
|
|
{
|
|
if (!val) return;
|
|
int i = sym_idx(s, sym);
|
|
|
|
genop_2(s, op, cursp(), i);
|
|
push();
|
|
}
|
|
|
|
static void
|
|
gen_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val)
|
|
{
|
|
int idx;
|
|
|
|
/* Check if this is a variable-sized node first */
|
|
enum node_type var_type = node_type(tree);
|
|
switch (var_type) {
|
|
case NODE_NIL:
|
|
if (rhs) {
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
sp = cursp();
|
|
}
|
|
/* NODE_NIL assignment is complete - just break (splat without assignment) */
|
|
break;
|
|
case NODE_COLON2:
|
|
gen_colon2_assign(s, tree, rhs, sp, val);
|
|
return;
|
|
case NODE_COLON3:
|
|
gen_colon3_assign(s, tree, rhs, sp, val);
|
|
return;
|
|
case NODE_GVAR:
|
|
gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETGV);
|
|
break;
|
|
case NODE_IVAR:
|
|
gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETIV);
|
|
break;
|
|
case NODE_CVAR:
|
|
gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETCV);
|
|
break;
|
|
case NODE_CONST:
|
|
gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETCONST);
|
|
break;
|
|
case NODE_MASGN:
|
|
case NODE_MARG:
|
|
/* Multiple assignment: expressions (MASGN) and parameter destructuring (MARG) */
|
|
codegen_masgn(s, tree, rhs, sp, val);
|
|
return;
|
|
case NODE_LVAR:
|
|
{
|
|
mrb_sym sym = var_node(tree)->symbol;
|
|
if (rhs) {
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
sp = cursp();
|
|
}
|
|
idx = lv_idx(s, sym);
|
|
if (idx > 0) {
|
|
if (idx != sp) {
|
|
gen_move(s, idx, sp, val);
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
gen_setupvar(s, sp, sym);
|
|
}
|
|
}
|
|
break;
|
|
case NODE_CALL:
|
|
codegen_call_assign(s, tree, rhs, sp, val);
|
|
return;
|
|
default:
|
|
codegen_error(s, "unsupported variable-sized lhs");
|
|
break;
|
|
}
|
|
if (val) push();
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gen_massignment(codegen_scope *s, node *tree, int rhs, int val)
|
|
{
|
|
int n = 0, post = 0;
|
|
node *t, *p;
|
|
|
|
if (tree->car) { /* pre */
|
|
t = tree->car;
|
|
n = 0;
|
|
while (t) {
|
|
int sp = cursp();
|
|
|
|
genop_3(s, OP_AREF, sp, rhs, n);
|
|
push();
|
|
gen_assignment(s, t->car, NULL, sp, NOVAL);
|
|
pop();
|
|
n++;
|
|
t = t->cdr;
|
|
}
|
|
}
|
|
t = tree->cdr;
|
|
if (t) {
|
|
if (t->cdr) { /* post count */
|
|
p = t->cdr->car;
|
|
while (p) {
|
|
post++;
|
|
p = p->cdr;
|
|
}
|
|
}
|
|
gen_move(s, cursp(), rhs, val);
|
|
push_n(post+1);
|
|
pop_n(post+1);
|
|
genop_3(s, OP_APOST, cursp(), n, post);
|
|
n = 1;
|
|
if (t->car && t->car != (node*)-1) { /* rest */
|
|
gen_assignment(s, t->car, NULL, cursp(), NOVAL);
|
|
}
|
|
if (t->cdr && t->cdr->car) {
|
|
t = t->cdr->car;
|
|
while (t) {
|
|
gen_assignment(s, t->car, NULL, cursp()+n, NOVAL);
|
|
t = t->cdr;
|
|
n++;
|
|
}
|
|
}
|
|
if (val) {
|
|
gen_move(s, cursp(), rhs, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gen_intern(codegen_scope *s)
|
|
{
|
|
pop();
|
|
if (!no_peephole(s)) {
|
|
struct mrb_insn_data data = mrb_last_insn(s);
|
|
|
|
if (data.insn == OP_STRING && data.a == cursp()) {
|
|
rewind_pc(s);
|
|
genop_2(s, OP_SYMBOL, data.a, data.b);
|
|
push();
|
|
return;
|
|
}
|
|
}
|
|
genop_1(s, OP_INTERN, cursp());
|
|
push();
|
|
}
|
|
|
|
static void
|
|
gen_literal_array(codegen_scope *s, node *tree, mrb_bool sym, int val)
|
|
{
|
|
if (val) {
|
|
int array_size = 0;
|
|
int first = 1;
|
|
int slimit = GEN_LIT_ARY_MAX;
|
|
node *current = tree;
|
|
|
|
if (cursp() >= slimit) slimit = GEN_VAL_STACK_MAX;
|
|
|
|
/* Process each segment separated by NODE_LITERAL_DELIM */
|
|
while (current) {
|
|
/* Find the segment boundaries without allocating */
|
|
node *segment_start = current;
|
|
node *segment_prev = NULL;
|
|
|
|
/* Find end of segment (delimiter or end of list) */
|
|
while (current && !IS_LITERAL_DELIM(current)) {
|
|
segment_prev = current;
|
|
current = current->cdr;
|
|
}
|
|
|
|
/* Process the segment if it has content */
|
|
if (segment_start != current) {
|
|
/* Check if this is an empty string segment (for %w[] case) */
|
|
mrb_bool is_empty_segment = TRUE;
|
|
node *check = segment_start;
|
|
while (check != current) {
|
|
if (check->car) {
|
|
mrb_int len = node_to_int(check->car->car);
|
|
if (len > 0) {
|
|
is_empty_segment = FALSE;
|
|
break;
|
|
}
|
|
else if (len < 0) {
|
|
/* Expression node - not empty */
|
|
is_empty_segment = FALSE;
|
|
break;
|
|
}
|
|
/* len == 0 means empty string, continue checking */
|
|
}
|
|
check = check->cdr;
|
|
}
|
|
|
|
/* Only process non-empty segments */
|
|
if (!is_empty_segment) {
|
|
/* Flush accumulated elements when stack is full */
|
|
if (cursp() >= slimit) {
|
|
if (array_size > 0) {
|
|
pop_n(array_size);
|
|
if (first) {
|
|
genop_2(s, OP_ARRAY, cursp(), array_size);
|
|
push();
|
|
first = 0;
|
|
}
|
|
else {
|
|
pop();
|
|
genop_2(s, OP_ARYPUSH, cursp(), array_size);
|
|
push();
|
|
}
|
|
array_size = 0;
|
|
}
|
|
}
|
|
|
|
/* Temporarily terminate the segment by saving and clearing the cdr */
|
|
node *saved_cdr = NULL;
|
|
if (segment_prev) {
|
|
saved_cdr = segment_prev->cdr;
|
|
segment_prev->cdr = NULL;
|
|
}
|
|
|
|
/* Use gen_string for this segment */
|
|
gen_string(s, segment_start, VAL);
|
|
|
|
/* Restore the original cdr */
|
|
if (segment_prev) {
|
|
segment_prev->cdr = saved_cdr;
|
|
}
|
|
|
|
/* Apply symbol conversion if needed */
|
|
if (sym) {
|
|
gen_intern(s);
|
|
}
|
|
|
|
array_size++;
|
|
}
|
|
}
|
|
|
|
/* Skip the delimiter if present */
|
|
if (current && IS_LITERAL_DELIM(current)) {
|
|
current = current->cdr;
|
|
}
|
|
}
|
|
|
|
/* Handle remaining elements */
|
|
if (!first) {
|
|
if (array_size > 0) {
|
|
pop_n(array_size + 1);
|
|
genop_2(s, OP_ARYPUSH, cursp(), array_size);
|
|
}
|
|
}
|
|
else if (array_size > 0) {
|
|
pop_n(array_size);
|
|
genop_2(s, OP_ARRAY, cursp(), array_size);
|
|
}
|
|
else {
|
|
genop_2(s, OP_ARRAY, cursp(), 0);
|
|
}
|
|
push();
|
|
}
|
|
else {
|
|
/* NOVAL case: only evaluate expressions for side effects */
|
|
node *current = tree;
|
|
|
|
while (current) {
|
|
/* Process nodes until delimiter */
|
|
while (current && !IS_LITERAL_DELIM(current)) {
|
|
node *elem = current->car;
|
|
if (elem) {
|
|
mrb_int len = node_to_int(elem->car);
|
|
if (len < 0) {
|
|
/* Expression: (-1 . node) - evaluate for side effects */
|
|
codegen(s, (node*)elem->cdr, NOVAL);
|
|
}
|
|
/* String literals: (len . str) - no side effects, skip */
|
|
}
|
|
current = current->cdr;
|
|
}
|
|
|
|
/* Skip delimiter */
|
|
if (current && IS_LITERAL_DELIM(current)) {
|
|
current = current->cdr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
raise_error(codegen_scope *s, const char *msg)
|
|
{
|
|
int idx = new_lit_cstr(s, msg);
|
|
|
|
genop_1(s, OP_ERR, idx);
|
|
}
|
|
|
|
static void
|
|
gen_retval(codegen_scope *s, node *tree)
|
|
{
|
|
if (is_splat_node(tree)) {
|
|
codegen(s, tree, VAL);
|
|
pop();
|
|
genop_1(s, OP_ARYSPLAT, cursp());
|
|
}
|
|
else {
|
|
codegen(s, tree, VAL);
|
|
pop();
|
|
}
|
|
}
|
|
|
|
static mrb_bool
|
|
true_always(node *tree)
|
|
{
|
|
/* Check if this is a variable-sized node first */
|
|
enum node_type var_type = node_type(tree);
|
|
switch (var_type) {
|
|
case NODE_INT:
|
|
case NODE_BIGINT:
|
|
case NODE_FLOAT:
|
|
case NODE_TRUE:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static mrb_bool
|
|
false_always(node *tree)
|
|
{
|
|
/* Check variable-sized nodes that are always false */
|
|
switch (node_type(tree)) {
|
|
case NODE_FALSE:
|
|
case NODE_NIL:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gen_blkmove(codegen_scope *s, uint16_t ainfo, int lv)
|
|
{
|
|
int m1 = (ainfo>>7)&0x3f;
|
|
int r = (ainfo>>6)&0x1;
|
|
int m2 = (ainfo>>1)&0x1f;
|
|
int kd = (ainfo)&0x1;
|
|
int off = m1+r+m2+kd+1;
|
|
if (lv == 0) {
|
|
gen_move(s, cursp(), off, 0);
|
|
}
|
|
else {
|
|
genop_3(s, OP_GETUPVAR, cursp(), off, lv);
|
|
}
|
|
push();
|
|
}
|
|
|
|
static void
|
|
gen_lvar(codegen_scope *s, mrb_sym sym, int val)
|
|
{
|
|
if (!val) return;
|
|
int idx = lv_idx(s, sym);
|
|
|
|
if (idx > 0) {
|
|
gen_move(s, cursp(), idx, val);
|
|
}
|
|
else {
|
|
gen_getupvar(s, cursp(), sym);
|
|
}
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_hash(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_hash_node *hash = hash_node(varnode);
|
|
node *pairs = hash->pairs;
|
|
int regular_pairs = 0;
|
|
mrb_bool update = FALSE;
|
|
mrb_bool first = TRUE;
|
|
|
|
if (!val) return;
|
|
|
|
if (!pairs) {
|
|
genop_2(s, OP_HASH, cursp(), 0);
|
|
push();
|
|
return;
|
|
}
|
|
|
|
/* Process each key-value pair using cons-list iteration, handling double-splat (**) cases */
|
|
node *current = pairs;
|
|
while (current) {
|
|
/* Each current->car is a cons (key . value) */
|
|
node *pair = current->car;
|
|
struct mrb_ast_node *key = pair->car;
|
|
struct mrb_ast_node *value = pair->cdr;
|
|
|
|
/* Check if this is a double-splat (**kwargs) */
|
|
if (node_to_sym(key) == MRB_OPSYM(pow)) {
|
|
/* Flush any accumulated regular pairs first */
|
|
if (val && first && regular_pairs == 0) {
|
|
/* First element is splat - create empty hash */
|
|
genop_2(s, OP_HASH, cursp(), 0);
|
|
push();
|
|
update = TRUE;
|
|
}
|
|
else if (val && regular_pairs > 0) {
|
|
/* Create/add hash from accumulated pairs */
|
|
pop_n(regular_pairs * 2);
|
|
if (!update) {
|
|
genop_2(s, OP_HASH, cursp(), regular_pairs);
|
|
}
|
|
else {
|
|
pop();
|
|
genop_2(s, OP_HASHADD, cursp(), regular_pairs);
|
|
}
|
|
push();
|
|
}
|
|
|
|
/* Generate the splat hash */
|
|
codegen(s, value, val);
|
|
|
|
/* Merge the splat hash */
|
|
if (val && (regular_pairs > 0 || update)) {
|
|
pop(); pop();
|
|
genop_1(s, OP_HASHCAT, cursp());
|
|
push();
|
|
}
|
|
|
|
update = TRUE;
|
|
regular_pairs = 0;
|
|
}
|
|
else {
|
|
/* Regular key-value pair */
|
|
codegen(s, key, val);
|
|
codegen(s, value, val);
|
|
regular_pairs++;
|
|
}
|
|
first = FALSE;
|
|
|
|
current = current->cdr;
|
|
}
|
|
|
|
/* Handle any remaining regular pairs */
|
|
if (val) {
|
|
if (!update && regular_pairs > 0) {
|
|
/* Simple case: no splats, just create hash */
|
|
pop_n(regular_pairs * 2);
|
|
genop_2(s, OP_HASH, cursp(), regular_pairs);
|
|
push();
|
|
}
|
|
else if (update && regular_pairs > 0) {
|
|
/* Add remaining pairs to existing hash */
|
|
pop_n(regular_pairs * 2 + 1);
|
|
genop_2(s, OP_HASHADD, cursp(), regular_pairs);
|
|
push();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Common function to generate bytecode for cons list string representation
|
|
* Handles list of elements where each element is either:
|
|
* - (len . str) for string literals
|
|
* - (-1 . node) for expressions that need evaluation
|
|
*/
|
|
/* Common function to generate bytecode for cons list string representation
|
|
* Handles list of elements where each element is either:
|
|
* - (len . str) for string literals
|
|
* - (-1 . node) for expressions that need evaluation
|
|
*/
|
|
/* Common function to generate bytecode for cons list string representation
|
|
* Handles list of elements where each element is either:
|
|
* - (len . str) for string literals
|
|
* - (-1 . node) for expressions that need evaluation
|
|
*/
|
|
/* Common function to generate bytecode for cons list string representation
|
|
* Handles list of elements where each element is either:
|
|
* - (len . str) for string literals
|
|
* - (-1 . node) for expressions that need evaluation
|
|
*/
|
|
static void
|
|
gen_string(codegen_scope *s, node *list, int val)
|
|
{
|
|
if (val) {
|
|
/* Handle as cons list of string parts with safety checks */
|
|
node *n = list;
|
|
mrb_bool first = TRUE;
|
|
|
|
while (n) {
|
|
node *elem = n->car;
|
|
if (!elem) break;
|
|
|
|
mrb_int len = node_to_int(elem->car);
|
|
|
|
if (len >= 0) {
|
|
/* String literal: (len . str) */
|
|
const char *str = (char*)elem->cdr;
|
|
if (!str) {str = ""; len = 0;}
|
|
int off = new_lit_str(s, str, len);
|
|
genop_2(s, OP_STRING, cursp(), off);
|
|
push();
|
|
}
|
|
else {
|
|
/* Expression: (-1 . node) */
|
|
codegen(s, (node*)elem->cdr, VAL);
|
|
}
|
|
|
|
/* Concatenate with previous parts (except for first element) */
|
|
if (!first) {
|
|
pop(); pop();
|
|
genop_1(s, OP_STRCAT, cursp());
|
|
push();
|
|
}
|
|
else {
|
|
first = FALSE;
|
|
}
|
|
|
|
n = n->cdr;
|
|
}
|
|
|
|
/* Handle empty list case */
|
|
if (first) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
}
|
|
else {
|
|
/* NOVAL case: only evaluate expressions for side effects */
|
|
node *n = list;
|
|
while (n) {
|
|
node *elem = n->car;
|
|
if (!elem) break;
|
|
if (node_to_int(elem->car) < 0) {
|
|
/* Expression: (-1 . node) - evaluate for side effects */
|
|
codegen(s, (node*)elem->cdr, NOVAL);
|
|
}
|
|
/* String literals: (len . str) - no side effects, skip */
|
|
n = n->cdr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Handle variable-sized node types */
|
|
static void
|
|
codegen_call(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_call_node *call = call_node(varnode);
|
|
mrb_sym sym = call->method_name;
|
|
int skip = 0, n = 0, nk = 0, noop = no_optimize(s), noself = 0, blk = 0, sp_save = cursp();
|
|
enum mrb_insn opt_op = OP_NOP;
|
|
int safe = call->safe_call;
|
|
node *args = call->args;
|
|
|
|
if (!noop) {
|
|
if (sym == MRB_OPSYM(add)) opt_op = OP_ADD;
|
|
else if (sym == MRB_OPSYM(sub)) opt_op = OP_SUB;
|
|
else if (sym == MRB_OPSYM(mul)) opt_op = OP_MUL;
|
|
else if (sym == MRB_OPSYM(div)) opt_op = OP_DIV;
|
|
else if (sym == MRB_OPSYM(lt)) opt_op = OP_LT;
|
|
else if (sym == MRB_OPSYM(le)) opt_op = OP_LE;
|
|
else if (sym == MRB_OPSYM(gt)) opt_op = OP_GT;
|
|
else if (sym == MRB_OPSYM(ge)) opt_op = OP_GE;
|
|
else if (sym == MRB_OPSYM(eq)) opt_op = OP_EQ;
|
|
else if (sym == MRB_OPSYM(aref)) opt_op = OP_GETIDX;
|
|
else if (sym == MRB_OPSYM(aset)) opt_op = OP_SETIDX;
|
|
}
|
|
|
|
if (!call->receiver || (opt_op == OP_NOP && node_type(call->receiver) == NODE_SELF)) {
|
|
noself = 1;
|
|
push();
|
|
}
|
|
else {
|
|
codegen(s, call->receiver, VAL); /* receiver */
|
|
}
|
|
|
|
if (safe) {
|
|
int recv = cursp()-1;
|
|
gen_move(s, cursp(), recv, 1);
|
|
skip = genjmp2_0(s, OP_JMPNIL, cursp(), val);
|
|
}
|
|
|
|
/* Generate arguments - use gen_values to properly handle splat */
|
|
if (args) {
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args;
|
|
if (callargs->regular_args) {
|
|
n = gen_values(s, callargs->regular_args, VAL, 14);
|
|
if (n < 0) { /* variable length (contains splat) */
|
|
n = 15;
|
|
push();
|
|
noop = 1;
|
|
}
|
|
}
|
|
|
|
/* Handle keyword arguments if present */
|
|
if (callargs->keyword_args) {
|
|
nk = gen_hash(s, callargs->keyword_args, VAL, 14);
|
|
if (nk < 0) {
|
|
nk = 15;
|
|
}
|
|
noop = 1;
|
|
}
|
|
|
|
/* Handle block if present */
|
|
if (callargs->block_arg) {
|
|
codegen(s, callargs->block_arg, VAL);
|
|
pop();
|
|
blk = 1;
|
|
noop = 1;
|
|
}
|
|
}
|
|
|
|
push();
|
|
s->sp = sp_save;
|
|
|
|
/* Apply optimizations */
|
|
if (opt_op == OP_ADD && n == 1) {
|
|
gen_addsub(s, OP_ADD, cursp());
|
|
}
|
|
else if (opt_op == OP_SUB && n == 1) {
|
|
gen_addsub(s, OP_SUB, cursp());
|
|
}
|
|
else if (opt_op == OP_MUL && n == 1) {
|
|
gen_muldiv(s, OP_MUL, cursp());
|
|
}
|
|
else if (opt_op == OP_DIV && n == 1) {
|
|
gen_muldiv(s, OP_DIV, cursp());
|
|
}
|
|
else if (opt_op == OP_LT && n == 1) {
|
|
genop_1(s, OP_LT, cursp());
|
|
}
|
|
else if (opt_op == OP_LE && n == 1) {
|
|
genop_1(s, OP_LE, cursp());
|
|
}
|
|
else if (opt_op == OP_GT && n == 1) {
|
|
genop_1(s, OP_GT, cursp());
|
|
}
|
|
else if (opt_op == OP_GE && n == 1) {
|
|
genop_1(s, OP_GE, cursp());
|
|
}
|
|
else if (opt_op == OP_EQ && n == 1) {
|
|
genop_1(s, OP_EQ, cursp());
|
|
}
|
|
else if (opt_op == OP_SETIDX && n == 2) {
|
|
genop_1(s, OP_SETIDX, cursp());
|
|
}
|
|
else if (!noop && n == 0 && gen_uniop(s, sym, cursp())) {
|
|
/* constant folding succeeded */
|
|
}
|
|
else if (!noop && n == 1 && gen_binop(s, sym, cursp())) {
|
|
/* constant folding succeeded */
|
|
}
|
|
else if (noself) {
|
|
if (!blk && n == 0 && nk == 0) {
|
|
genop_2(s, OP_SSEND0, cursp(), sym_idx(s, sym));
|
|
}
|
|
else {
|
|
genop_3(s, blk ? OP_SSENDB : OP_SSEND, cursp(), sym_idx(s, sym), n|(nk<<4));
|
|
}
|
|
}
|
|
else if (!blk && n == 0 && nk == 0) {
|
|
genop_2(s, OP_SEND0, cursp(), sym_idx(s, sym));
|
|
}
|
|
else {
|
|
genop_3(s, blk ? OP_SENDB : OP_SEND, cursp(), sym_idx(s, sym), n|(nk<<4));
|
|
}
|
|
|
|
if (safe) {
|
|
dispatch(s, skip);
|
|
}
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_call_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val)
|
|
{
|
|
enum node_type var_type = NODE_TYPE(varnode);
|
|
int noself = 0, safe = 0, skip = 0, top, callsp, n = 0, nk = 0;
|
|
mrb_sym mid = 0;
|
|
node *args = NULL;
|
|
node *receiver = NULL;
|
|
enum mrb_insn opt_op = OP_NOP;
|
|
int noop = no_optimize(s);
|
|
|
|
/* Extract information based on node type */
|
|
if (var_type == NODE_CALL) {
|
|
struct mrb_ast_call_node *call = call_node(varnode);
|
|
mid = call->method_name;
|
|
args = call->args;
|
|
receiver = call->receiver;
|
|
safe = call->safe_call;
|
|
}
|
|
else {
|
|
codegen_error(s, "unsupported call type in assignment");
|
|
return;
|
|
}
|
|
|
|
/* Convert method name to assignment form (e.g., [] -> []=) */
|
|
mrb_sym assign_mid = attrsym(s, mid);
|
|
|
|
/* Check for optimizable operations */
|
|
if (!noop) {
|
|
if (mid == MRB_OPSYM(aref)) opt_op = OP_SETIDX;
|
|
}
|
|
|
|
top = cursp();
|
|
if (val || sp == cursp()) {
|
|
push(); /* room for retval */
|
|
}
|
|
callsp = cursp();
|
|
|
|
/* Generate receiver */
|
|
if (!receiver) {
|
|
noself = 1;
|
|
push();
|
|
}
|
|
else {
|
|
codegen(s, receiver, VAL); /* receiver */
|
|
}
|
|
|
|
/* Handle safe navigation */
|
|
if (safe) {
|
|
int recv = cursp()-1;
|
|
gen_move(s, cursp(), recv, 1);
|
|
skip = genjmp2_0(s, OP_JMPNIL, cursp(), val);
|
|
}
|
|
|
|
/* Generate arguments from original call */
|
|
if (args) {
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args;
|
|
if (callargs->regular_args) {
|
|
node *regular_args = callargs->regular_args;
|
|
node *arg_iter = regular_args;
|
|
while (arg_iter) {
|
|
codegen(s, arg_iter->car, VAL);
|
|
n++;
|
|
arg_iter = arg_iter->cdr;
|
|
}
|
|
if (n > 13) { /* leave room for rhs */
|
|
pop_n(n);
|
|
genop_2(s, OP_ARRAY, cursp(), n);
|
|
push();
|
|
n = 15;
|
|
noop = 1;
|
|
}
|
|
}
|
|
|
|
/* Handle keyword arguments if present */
|
|
if (callargs->keyword_args) {
|
|
node *kwargs = callargs->keyword_args;
|
|
if (n == 13 || n == 14) {
|
|
pop_n(n);
|
|
genop_2(s, OP_ARRAY, cursp(), n);
|
|
push();
|
|
n = 15;
|
|
}
|
|
gen_hash(s, kwargs->cdr, VAL, 0);
|
|
if (n < 14) {
|
|
n++;
|
|
}
|
|
else {
|
|
pop_n(2);
|
|
genop_2(s, OP_ARYPUSH, cursp(), 1);
|
|
}
|
|
push();
|
|
noop = 1;
|
|
}
|
|
}
|
|
|
|
/* Generate rhs (the assigned value) */
|
|
if (rhs) {
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
}
|
|
else {
|
|
/* For compound assignments, move the computed value from sp to cursp() */
|
|
gen_move(s, cursp(), sp, 0);
|
|
}
|
|
if (val) {
|
|
gen_move(s, top, cursp(), 1);
|
|
}
|
|
/* Account for the value being assigned (either from rhs or already on stack) */
|
|
if (n < 14) {
|
|
n++;
|
|
}
|
|
else {
|
|
if (rhs) {
|
|
pop_n(2);
|
|
genop_2(s, OP_ARYPUSH, cursp(), 1);
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* Generate the optimized instruction or method call */
|
|
push(); push();
|
|
s->sp = callsp;
|
|
|
|
if (opt_op == OP_SETIDX && n == 2) {
|
|
/* Always preserve return value for SETIDX - assignments return the assigned value */
|
|
genop_1(s, OP_SETIDX, cursp());
|
|
}
|
|
else if (noself) {
|
|
genop_3(s, OP_SSEND, cursp(), sym_idx(s, assign_mid), n|(nk<<4));
|
|
}
|
|
else {
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, assign_mid), n|(nk<<4));
|
|
}
|
|
|
|
if (safe) {
|
|
dispatch(s, skip);
|
|
}
|
|
|
|
/* Restore stack pointer like legacy code */
|
|
s->sp = top;
|
|
|
|
if (val) {
|
|
push();
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_array(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_array_node *array = array_node(varnode);
|
|
node *elements = array->elements;
|
|
int regular_elements = 0;
|
|
int first = 1;
|
|
int slimit = GEN_LIT_ARY_MAX;
|
|
|
|
if (!val) return;
|
|
|
|
if (!elements) {
|
|
genop_2(s, OP_ARRAY, cursp(), 0);
|
|
push();
|
|
return;
|
|
}
|
|
|
|
if (cursp() >= slimit) slimit = GEN_VAL_STACK_MAX;
|
|
|
|
/* Process each element using cons-list iteration, handling splats */
|
|
node *current = elements;
|
|
while (current) {
|
|
struct mrb_ast_node *element = current->car;
|
|
int is_splat = is_splat_node(element);
|
|
|
|
/* Skip splat of an empty literal array: [*[]] => [] without ARYCAT noise */
|
|
if (is_splat) {
|
|
struct mrb_ast_splat_node *splat = splat_node(element);
|
|
node *sv = splat->value;
|
|
if (sv) {
|
|
enum node_type nt = node_type(sv);
|
|
if (nt == NODE_ARRAY) {
|
|
struct mrb_ast_array_node *an = array_node(sv);
|
|
if (an->elements == NULL) {
|
|
current = current->cdr;
|
|
continue;
|
|
}
|
|
}
|
|
else if (nt == NODE_ZARRAY) {
|
|
current = current->cdr;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_splat || cursp() >= slimit) { /* flush accumulated elements */
|
|
if (regular_elements > 0) {
|
|
pop_n(regular_elements);
|
|
if (first) {
|
|
genop_2(s, OP_ARRAY, cursp(), regular_elements);
|
|
push();
|
|
first = 0;
|
|
}
|
|
else {
|
|
pop();
|
|
genop_2(s, OP_ARYPUSH, cursp(), regular_elements);
|
|
push();
|
|
}
|
|
regular_elements = 0;
|
|
}
|
|
else if (first && is_splat) {
|
|
/* First element is splat - create empty array */
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
genop_2(s, OP_ARRAY, cursp(), 0);
|
|
push();
|
|
first = 0;
|
|
}
|
|
}
|
|
|
|
codegen(s, element, val);
|
|
|
|
if (is_splat) {
|
|
/* Concatenate splat array */
|
|
pop(); pop();
|
|
genop_1(s, OP_ARYCAT, cursp());
|
|
push();
|
|
}
|
|
else {
|
|
regular_elements++;
|
|
}
|
|
|
|
current = current->cdr;
|
|
}
|
|
|
|
/* Handle any remaining regular elements */
|
|
if (!first) {
|
|
/* Variable length - we have an array from splats */
|
|
if (regular_elements > 0) {
|
|
pop_n(regular_elements + 1);
|
|
genop_2(s, OP_ARYPUSH, cursp(), regular_elements);
|
|
push();
|
|
}
|
|
}
|
|
else {
|
|
/* Simple case: no splats, just create array */
|
|
pop_n(regular_elements);
|
|
genop_2(s, OP_ARRAY, cursp(), regular_elements);
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* Control flow and definition node codegen functions */
|
|
static mrb_bool
|
|
callargs_empty(node *n)
|
|
{
|
|
if (!n) return TRUE;
|
|
return (callargs_node(n)->regular_args == 0 && callargs_node(n)->keyword_args == 0 && callargs_node(n)->block_arg == 0);
|
|
}
|
|
|
|
static void
|
|
codegen_if(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_if_node *if_n = if_node(varnode);
|
|
node *condition = if_n->condition;
|
|
node *then_body = if_n->then_body;
|
|
node *else_body = if_n->else_body;
|
|
uint32_t pos1, pos2;
|
|
mrb_bool nil_p = FALSE;
|
|
|
|
if (!condition) {
|
|
codegen(s, else_body, val);
|
|
return;
|
|
}
|
|
if (true_always(condition)) {
|
|
codegen(s, then_body, val);
|
|
return;
|
|
}
|
|
if (false_always(condition)) {
|
|
codegen(s, else_body, val);
|
|
return;
|
|
}
|
|
|
|
/* Check for nil? optimization */
|
|
if (node_type(condition) == NODE_CALL) {
|
|
/* Variable-sized NODE_CALL */
|
|
struct mrb_ast_call_node *call_n = (struct mrb_ast_call_node*)condition;
|
|
mrb_sym sym_nil_p = MRB_SYM_Q(nil);
|
|
if (call_n->method_name == sym_nil_p && callargs_empty(call_n->args)) {
|
|
nil_p = TRUE;
|
|
if (call_n->receiver) {
|
|
codegen(s, call_n->receiver, VAL);
|
|
}
|
|
else {
|
|
/* implicit receiver: bare `nil?` means `self.nil?` */
|
|
gen_load_op1(s, OP_LOADSELF, VAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!nil_p) {
|
|
/* Generate condition code */
|
|
codegen(s, condition, VAL);
|
|
}
|
|
pop();
|
|
|
|
if (val || then_body) {
|
|
if (nil_p) {
|
|
pos2 = genjmp2_0(s, OP_JMPNIL, cursp(), val);
|
|
pos1 = genjmp_0(s, OP_JMP);
|
|
dispatch(s, pos2);
|
|
}
|
|
else {
|
|
pos1 = genjmp2_0(s, OP_JMPNOT, cursp(), val);
|
|
}
|
|
codegen(s, then_body, val);
|
|
if (val) pop();
|
|
if (else_body || val) {
|
|
pos2 = genjmp_0(s, OP_JMP);
|
|
dispatch(s, pos1);
|
|
codegen(s, else_body, val);
|
|
dispatch(s, pos2);
|
|
}
|
|
else {
|
|
dispatch(s, pos1);
|
|
}
|
|
}
|
|
else { /* empty then-part */
|
|
if (else_body) {
|
|
if (nil_p) {
|
|
pos1 = genjmp2_0(s, OP_JMPNIL, cursp(), val);
|
|
}
|
|
else {
|
|
pos1 = genjmp2_0(s, OP_JMPIF, cursp(), val);
|
|
}
|
|
codegen(s, else_body, val);
|
|
dispatch(s, pos1);
|
|
}
|
|
else if (val && !nil_p) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Shared codegen for while/until pre-tested loops.
|
|
is_until: FALSE for while (exit on false), TRUE for until (exit on true) */
|
|
static void
|
|
codegen_loop(codegen_scope *s, node *varnode, int val, mrb_bool is_until)
|
|
{
|
|
struct mrb_ast_while_node *loop_n = while_node(varnode);
|
|
node *condition = loop_n->condition;
|
|
node *body = loop_n->body;
|
|
|
|
if (is_until ? false_always(condition) : true_always(condition)) {
|
|
struct loopinfo *lp = loop_push(s, LOOP_NORMAL);
|
|
if (!val) lp->reg = -1;
|
|
lp->pc0 = new_label(s);
|
|
lp->pc1 = new_label(s);
|
|
genop_0(s, OP_NOP); /* for redo */
|
|
codegen(s, body, NOVAL);
|
|
genjmp(s, OP_JMP, lp->pc0);
|
|
loop_pop(s, val);
|
|
return;
|
|
}
|
|
if (is_until ? true_always(condition) : false_always(condition)) {
|
|
if (val) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct loopinfo *lp = loop_push(s, LOOP_NORMAL);
|
|
uint32_t pos;
|
|
|
|
if (!val) lp->reg = -1;
|
|
lp->pc0 = new_label(s);
|
|
codegen(s, condition, VAL);
|
|
pop();
|
|
pos = genjmp2_0(s, is_until ? OP_JMPIF : OP_JMPNOT, cursp(), NOVAL);
|
|
lp->pc1 = new_label(s);
|
|
genop_0(s, OP_NOP); /* for redo */
|
|
codegen(s, body, NOVAL);
|
|
genjmp(s, OP_JMP, lp->pc0);
|
|
dispatch(s, pos);
|
|
loop_pop(s, val);
|
|
}
|
|
|
|
/* Shared codegen for while/until post-tested (modifier) loops.
|
|
is_until: FALSE for while (exit on false), TRUE for until (exit on true) */
|
|
static void
|
|
codegen_loop_mod(codegen_scope *s, node *varnode, int val, mrb_bool is_until)
|
|
{
|
|
struct mrb_ast_while_node *loop_n = while_node(varnode);
|
|
node *condition = loop_n->condition;
|
|
node *body = loop_n->body;
|
|
|
|
if (is_until ? true_always(condition) : false_always(condition)) {
|
|
/* execute body once then exit */
|
|
codegen(s, body, val);
|
|
if (val) push();
|
|
return;
|
|
}
|
|
if (is_until ? false_always(condition) : true_always(condition)) {
|
|
/* infinite loop after first execution */
|
|
struct loopinfo *lp = loop_push(s, LOOP_NORMAL);
|
|
if (!val) lp->reg = -1;
|
|
|
|
uint32_t pos0 = genjmp_0(s, OP_JMP);
|
|
lp->pc0 = new_label(s);
|
|
lp->pc1 = new_label(s);
|
|
genop_0(s, OP_NOP); /* for redo */
|
|
dispatch(s, pos0);
|
|
codegen(s, body, NOVAL);
|
|
genjmp(s, OP_JMP, lp->pc0);
|
|
loop_pop(s, val);
|
|
return;
|
|
}
|
|
|
|
struct loopinfo *lp = loop_push(s, LOOP_NORMAL);
|
|
if (!val) lp->reg = -1;
|
|
|
|
uint32_t pos0 = genjmp_0(s, OP_JMP);
|
|
lp->pc0 = new_label(s);
|
|
codegen(s, condition, VAL);
|
|
pop();
|
|
uint32_t pos = genjmp2_0(s, is_until ? OP_JMPIF : OP_JMPNOT, cursp(), NOVAL);
|
|
lp->pc1 = new_label(s);
|
|
genop_0(s, OP_NOP); /* for redo */
|
|
dispatch(s, pos0);
|
|
codegen(s, body, NOVAL);
|
|
genjmp(s, OP_JMP, lp->pc0);
|
|
dispatch(s, pos);
|
|
loop_pop(s, val);
|
|
}
|
|
|
|
static void
|
|
codegen_for(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_for_node *for_n = for_node(varnode);
|
|
node *var = for_n->var;
|
|
node *iterable = for_n->iterable;
|
|
node *body = for_n->body;
|
|
|
|
codegen_scope *prev = s;
|
|
int idx;
|
|
struct loopinfo *lp;
|
|
|
|
/* generate receiver */
|
|
codegen(s, iterable, VAL);
|
|
/* generate loop-block */
|
|
s = scope_new(s->mrb, s, NULL);
|
|
|
|
push(); /* push for a block parameter */
|
|
|
|
/* generate loop variable */
|
|
genop_W(s, OP_ENTER, 0x40000);
|
|
if (var->car && !var->car->cdr && !var->cdr) {
|
|
gen_assignment(s, var->car->car, NULL, 1, NOVAL);
|
|
}
|
|
else {
|
|
gen_massignment(s, var, 1, VAL);
|
|
}
|
|
/* construct loop */
|
|
lp = loop_push(s, LOOP_FOR);
|
|
lp->pc1 = new_label(s);
|
|
genop_0(s, OP_NOP); /* for redo */
|
|
|
|
/* loop body */
|
|
codegen(s, body, VAL);
|
|
pop();
|
|
gen_return(s, OP_RETURN, cursp());
|
|
loop_pop(s, NOVAL);
|
|
scope_finish(s);
|
|
s = prev;
|
|
genop_2(s, OP_BLOCK, cursp(), s->irep->rlen-1);
|
|
push();pop(); /* space for a block */
|
|
pop();
|
|
idx = sym_idx(s, MRB_SYM(each));
|
|
genop_3(s, OP_SENDB, cursp(), idx, 0);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_case(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_case_node *case_n = case_node(varnode);
|
|
node *value = case_n->value;
|
|
node *body = case_n->body;
|
|
|
|
int head = 0;
|
|
uint32_t case_end_jumps, tmp;
|
|
uint32_t next_when_pos = JMPLINK_START;
|
|
node *n;
|
|
|
|
case_end_jumps = JMPLINK_START;
|
|
|
|
/* Handle case value exactly like original */
|
|
if (value) {
|
|
head = cursp();
|
|
codegen(s, value, VAL);
|
|
}
|
|
|
|
/* Iterate through when clauses list with JMPNOT optimization */
|
|
node *current_when = body;
|
|
while (current_when) {
|
|
node *when_clause = current_when->car;
|
|
|
|
/* Dispatch previous when's "next" jump to this location */
|
|
if (next_when_pos != JMPLINK_START) {
|
|
dispatch_linked(s, next_when_pos);
|
|
next_when_pos = JMPLINK_START;
|
|
}
|
|
|
|
/* when_clause is (condition . body) cons node */
|
|
node *args = when_clause->car; /* when conditions */
|
|
node *when_body = when_clause->cdr; /* when body */
|
|
|
|
/* Process when conditions with JMPNOT optimization */
|
|
n = args;
|
|
uint32_t condition_success_pos = JMPLINK_START;
|
|
|
|
while (n) {
|
|
codegen(s, n->car, VAL);
|
|
if (head) {
|
|
gen_move(s, cursp(), head, 0);
|
|
push(); push(); pop(); pop(); pop();
|
|
if (is_splat_node(n->car)) {
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM(__case_eqq)), 1);
|
|
}
|
|
else {
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(eqq)), 1);
|
|
}
|
|
}
|
|
else {
|
|
pop();
|
|
}
|
|
|
|
if (n->cdr) {
|
|
/* More conditions in this when - use JMPIF to success handler */
|
|
tmp = genjmp2(s, OP_JMPIF, cursp(), condition_success_pos, !head);
|
|
condition_success_pos = tmp;
|
|
}
|
|
else {
|
|
/* Last condition - use JMPNOT to next when clause */
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), next_when_pos, !head);
|
|
next_when_pos = tmp;
|
|
}
|
|
n = n->cdr;
|
|
}
|
|
|
|
/* Dispatch multiple condition success jumps to body */
|
|
if (condition_success_pos != JMPLINK_START) {
|
|
dispatch_linked(s, condition_success_pos);
|
|
}
|
|
|
|
/* Generate when body */
|
|
codegen(s, when_body, val);
|
|
if (val) pop();
|
|
|
|
/* Check if this is the last when clause before else, or if there's no else clause */
|
|
node *next_node = current_when->cdr;
|
|
|
|
tmp = genjmp(s, OP_JMP, case_end_jumps);
|
|
case_end_jumps = tmp;
|
|
|
|
current_when = next_node;
|
|
}
|
|
|
|
/* Handle case where no else clause was found */
|
|
if (next_when_pos != JMPLINK_START) {
|
|
dispatch_linked(s, next_when_pos);
|
|
/* No else clause, generate LOADNIL for VAL case */
|
|
if (val) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
}
|
|
|
|
/* Apply stack management strategy for cases without else clause */
|
|
if (val) {
|
|
/* Dispatch remaining case_end_jumps */
|
|
if (case_end_jumps != JMPLINK_START) {
|
|
dispatch_linked(s, case_end_jumps);
|
|
}
|
|
if (head) {
|
|
/* Move result to original case value position */
|
|
gen_move(s, head, cursp(), 0);
|
|
pop();
|
|
}
|
|
/* Always push to maintain stack alignment */
|
|
push();
|
|
}
|
|
else {
|
|
/* NOVAL case */
|
|
if (case_end_jumps != JMPLINK_START) {
|
|
dispatch_linked(s, case_end_jumps);
|
|
}
|
|
if (head) {
|
|
pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Forward declaration for pattern matching code generation
|
|
* known_array_len: -1 if unknown, >= 0 if target is known to be an array of that length
|
|
*/
|
|
static void codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, int known_array_len);
|
|
|
|
/* Return the static element count of an array literal AST node, or -1 if any
|
|
* element is a splat (whose runtime length is unknown).
|
|
*/
|
|
static int
|
|
array_literal_known_len(node *value)
|
|
{
|
|
if (node_type(value) != NODE_ARRAY) return -1;
|
|
struct mrb_ast_array_node *arr = array_node(value);
|
|
int len = 0;
|
|
for (node *elem = arr->elements; elem; elem = elem->cdr) {
|
|
if (is_splat_node(elem->car)) return -1;
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* Pattern matching case/in expression */
|
|
static void
|
|
codegen_case_match(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_case_match_node *case_match_n = case_match_node(varnode);
|
|
node *value = case_match_n->value;
|
|
node *in_clauses = case_match_n->in_clauses;
|
|
|
|
int head = cursp();
|
|
uint32_t case_end_jumps = JMPLINK_START;
|
|
uint32_t tmp;
|
|
|
|
/* Check if value is an array literal - allows optimizations in pattern matching */
|
|
int known_array_len = array_literal_known_len(value);
|
|
|
|
/* Generate code for the case value */
|
|
codegen(s, value, VAL);
|
|
|
|
/* Iterate through in clauses */
|
|
node *current_in = in_clauses;
|
|
while (current_in) {
|
|
struct mrb_ast_in_node *in_n = in_node(current_in->car);
|
|
node *pattern = in_n->pattern;
|
|
node *guard = in_n->guard;
|
|
mrb_bool guard_is_unless = in_n->guard_is_unless;
|
|
node *body = in_n->body;
|
|
|
|
uint32_t fail_pos = JMPLINK_START;
|
|
|
|
if (pattern) {
|
|
/* Generate pattern matching code */
|
|
codegen_pattern(s, pattern, head, &fail_pos, known_array_len);
|
|
}
|
|
|
|
/* Generate guard clause if present */
|
|
if (guard) {
|
|
codegen(s, guard, VAL);
|
|
pop(); /* pop before jump - cursp() now points to guard result */
|
|
if (guard_is_unless) {
|
|
/* unless guard: fail if guard is true */
|
|
tmp = genjmp2(s, OP_JMPIF, cursp(), fail_pos, 0);
|
|
}
|
|
else {
|
|
/* if guard: fail if guard is false */
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), fail_pos, 0);
|
|
}
|
|
fail_pos = tmp;
|
|
}
|
|
|
|
/* Generate in-clause body */
|
|
codegen(s, body, val);
|
|
if (val) pop();
|
|
|
|
/* Jump to end of case/in */
|
|
tmp = genjmp(s, OP_JMP, case_end_jumps);
|
|
case_end_jumps = tmp;
|
|
|
|
/* Dispatch fail jumps to next in-clause */
|
|
if (fail_pos != JMPLINK_START) {
|
|
dispatch_linked(s, fail_pos);
|
|
}
|
|
|
|
current_in = current_in->cdr;
|
|
}
|
|
|
|
/* No pattern matched - raise NoMatchingPatternError */
|
|
genop_1(s, OP_LOADFALSE, cursp());
|
|
genop_1(s, OP_MATCHERR, cursp());
|
|
|
|
/* Dispatch all end jumps */
|
|
if (case_end_jumps != JMPLINK_START) {
|
|
dispatch_linked(s, case_end_jumps);
|
|
}
|
|
|
|
if (val) {
|
|
/* Move result to original case value position */
|
|
gen_move(s, head, cursp(), 0);
|
|
pop();
|
|
push();
|
|
}
|
|
else {
|
|
pop(); /* pop the case value */
|
|
}
|
|
}
|
|
|
|
/* Generate pattern matching code for a single pattern.
|
|
* target: stack position of the value being matched
|
|
* fail_pos: linked list of jump positions for pattern match failure
|
|
* known_array_len: -1 if unknown, >= 0 if target is known to be an array of that length
|
|
*/
|
|
/* generate code to load a hash pattern key onto the stack */
|
|
static void
|
|
gen_pat_key(codegen_scope *s, node *key)
|
|
{
|
|
if (node_type(key) == NODE_SYM) {
|
|
genop_2(s, OP_LOADSYM, cursp(), sym_idx(s, sym_node(key)->symbol));
|
|
}
|
|
else {
|
|
codegen(s, key, VAL);
|
|
}
|
|
}
|
|
|
|
/* generate OP_ARRAY of hash pattern keys on the stack */
|
|
static void
|
|
gen_pat_keys_ary(codegen_scope *s, node *pairs, int num_keys)
|
|
{
|
|
int i = 0;
|
|
node *pair;
|
|
for (pair = pairs; pair; pair = pair->cdr, i++) {
|
|
gen_pat_key(s, pair->car->car);
|
|
push();
|
|
}
|
|
genop_2(s, OP_ARRAY, cursp() - num_keys, num_keys);
|
|
for (i = 1; i < num_keys; i++) pop();
|
|
}
|
|
|
|
static void
|
|
codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, int known_array_len)
|
|
{
|
|
uint32_t tmp;
|
|
|
|
switch (node_type(pattern)) {
|
|
case NODE_PAT_VALUE:
|
|
{
|
|
struct mrb_ast_pat_value_node *pat_val = pat_value_node(pattern);
|
|
/* Generate: pattern_value === target */
|
|
codegen(s, pat_val->value, VAL);
|
|
gen_move(s, cursp(), target, 0);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(eqq)), 1);
|
|
/* Jump to fail if not matched */
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_VAR:
|
|
{
|
|
struct mrb_ast_pat_var_node *pat_var = pat_var_node(pattern);
|
|
if (pat_var->name) {
|
|
/* Bind the matched value to the variable */
|
|
int idx = lv_idx(s, pat_var->name);
|
|
if (idx > 0) {
|
|
gen_move(s, idx, target, 1); /* nopeep=1 to prevent optimization */
|
|
}
|
|
}
|
|
/* Variable pattern always matches (wildcard if name is 0) */
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_ALT:
|
|
{
|
|
struct mrb_ast_pat_alt_node *pat_alt = pat_alt_node(pattern);
|
|
uint32_t left_fail = JMPLINK_START;
|
|
uint32_t success_pos = JMPLINK_START;
|
|
|
|
/* Try left pattern */
|
|
codegen_pattern(s, pat_alt->left, target, &left_fail, known_array_len);
|
|
|
|
/* Optimize JMPNOT+JMP to JMPIF when:
|
|
* 1. Left pattern is not another NODE_PAT_ALT (avoid recursion issues)
|
|
* 2. Left pattern generated at least one JMPNOT
|
|
* 3. The last JMPNOT is immediately before current position
|
|
* 4. The instruction is actually OP_JMPNOT (not OP_JMP which has
|
|
* different format S vs BS - converting OP_JMP would corrupt bytecode)
|
|
* In this case, convert JMPNOT to JMPIF and skip generating JMP */
|
|
if (node_type(pat_alt->left) != NODE_PAT_ALT &&
|
|
left_fail != JMPLINK_START && left_fail + 2 == s->pc &&
|
|
s->iseq[left_fail - 2] == OP_JMPNOT) {
|
|
/* Extract the previous link from the JMPNOT chain.
|
|
* The chain uses relative offsets where the end is marked by
|
|
* an offset that points to address 0 (i.e., (pos+2)+offset == 0) */
|
|
int16_t prev_offset = (int16_t)PEEK_S(s->iseq + left_fail);
|
|
int32_t next_addr = (int32_t)(left_fail + 2) + prev_offset;
|
|
uint32_t prev_link = (next_addr == 0) ? JMPLINK_START : (uint32_t)next_addr;
|
|
/* Convert JMPNOT to JMPIF */
|
|
s->iseq[left_fail - 2] = OP_JMPIF;
|
|
/* Clear offset to mark end of success chain */
|
|
emit_S(s, left_fail, 0);
|
|
success_pos = left_fail;
|
|
/* Continue with remaining fail chain */
|
|
left_fail = prev_link;
|
|
}
|
|
else {
|
|
/* Left succeeded - jump to success */
|
|
tmp = genjmp(s, OP_JMP, success_pos);
|
|
success_pos = tmp;
|
|
}
|
|
|
|
/* Left failed - try right pattern */
|
|
if (left_fail != JMPLINK_START) {
|
|
dispatch_linked(s, left_fail);
|
|
}
|
|
codegen_pattern(s, pat_alt->right, target, fail_pos, known_array_len);
|
|
|
|
/* Dispatch success jumps */
|
|
if (success_pos != JMPLINK_START) {
|
|
dispatch_linked(s, success_pos);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_AS:
|
|
{
|
|
struct mrb_ast_pat_as_node *pat_as = pat_as_node(pattern);
|
|
/* First match the pattern */
|
|
codegen_pattern(s, pat_as->pattern, target, fail_pos, known_array_len);
|
|
/* Then bind the value to the variable */
|
|
int idx = lv_idx(s, pat_as->name);
|
|
if (idx > 0) {
|
|
gen_move(s, idx, target, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_PIN:
|
|
{
|
|
struct mrb_ast_pat_pin_node *pat_pin = pat_pin_node(pattern);
|
|
/* Get the current value of the pinned variable */
|
|
int idx = lv_idx(s, pat_pin->name);
|
|
if (idx > 0) {
|
|
/* Compare: pinned_value === target */
|
|
gen_move(s, cursp(), idx, 0); /* Load pinned variable */
|
|
push();
|
|
gen_move(s, cursp(), target, 0); /* Load target */
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(eqq)), 1);
|
|
/* Jump to fail if not matched */
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
}
|
|
else {
|
|
/* Variable not found - raise compile error like CRuby */
|
|
codegen_error(s, "no such local variable for pin operator");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_ARRAY:
|
|
{
|
|
struct mrb_ast_pat_array_node *pat_arr = pat_array_node(pattern);
|
|
int pre_len = 0, post_len = 0;
|
|
int arr_reg;
|
|
node *elem;
|
|
int i;
|
|
|
|
/* Count pre and post elements */
|
|
for (elem = pat_arr->pre; elem; elem = elem->cdr) pre_len++;
|
|
for (elem = pat_arr->post; elem; elem = elem->cdr) post_len++;
|
|
|
|
/* Optimization: if we know the target is an array, skip deconstruct */
|
|
if (known_array_len >= 0) {
|
|
/* Use target directly as array register */
|
|
arr_reg = target;
|
|
/* Compile-time size check */
|
|
if (pat_arr->rest == 0) {
|
|
/* No rest: exact length match required */
|
|
if (known_array_len != pre_len) {
|
|
/* Size mismatch - always fail */
|
|
tmp = genjmp(s, OP_JMP, *fail_pos);
|
|
*fail_pos = tmp;
|
|
break;
|
|
}
|
|
/* Size matches, no runtime check needed */
|
|
}
|
|
else {
|
|
/* Has rest: minimum length check */
|
|
int min_len = pre_len + post_len;
|
|
if (known_array_len < min_len) {
|
|
/* Size too small - always fail */
|
|
tmp = genjmp(s, OP_JMP, *fail_pos);
|
|
*fail_pos = tmp;
|
|
break;
|
|
}
|
|
/* Size sufficient, no runtime check needed */
|
|
}
|
|
|
|
/* Match pre-rest elements using GETIDX (faster than SEND :[]) */
|
|
i = 0;
|
|
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
|
|
/* Get arr[i] using GETIDX */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
genop_1(s, OP_GETIDX, cursp() - 1); /* R[cursp-1] = R[cursp-1][R[cursp]] */
|
|
/* Element is now at cursp-1 */
|
|
/* Match element pattern (elements are not known arrays) */
|
|
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
|
pop(); /* Clean up element slot */
|
|
}
|
|
|
|
/* Bind rest elements if rest is a variable */
|
|
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
|
|
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
|
|
if (rest_var->name) {
|
|
int var_idx = lv_idx(s, rest_var->name);
|
|
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
|
|
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
|
push();
|
|
gen_int(s, cursp(), pre_len); /* start at cursp */
|
|
push();
|
|
if (post_len > 0) {
|
|
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
|
|
}
|
|
else {
|
|
gen_int(s, cursp(), -1); /* end at cursp */
|
|
}
|
|
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
|
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
|
/* arr at cursp-2, range at cursp-1 */
|
|
pop(); /* cursp now at range position */
|
|
pop(); /* cursp now at arr position */
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
if (var_idx > 0) {
|
|
gen_move(s, var_idx, cursp(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Match post-rest elements using GETIDX */
|
|
i = -post_len;
|
|
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
|
|
/* Get arr[i] using GETIDX (negative index from end) */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
genop_1(s, OP_GETIDX, cursp() - 1);
|
|
/* Match element pattern */
|
|
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
|
pop(); /* Clean up element slot */
|
|
}
|
|
/* No arr_reg to pop since we used target directly */
|
|
}
|
|
else {
|
|
/* General case: need to call deconstruct and check size at runtime */
|
|
arr_reg = cursp();
|
|
|
|
/* Call deconstruct on target */
|
|
gen_move(s, cursp(), target, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, arr_reg, sym_idx(s, MRB_SYM(deconstruct)), 0);
|
|
|
|
/* Check length constraints */
|
|
if (pat_arr->rest == 0) {
|
|
/* No rest: exact length match */
|
|
/* Generate: arr.size == pre_len using EQ opcode */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM(size)), 0);
|
|
gen_int(s, cursp(), pre_len);
|
|
/* EQ: R[a] = R[a] == R[a+1]; size at cursp-1, pre_len at cursp */
|
|
genop_1(s, OP_EQ, cursp() - 1);
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp() - 1, *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
pop();
|
|
}
|
|
else {
|
|
/* Has rest: minimum length check */
|
|
int min_len = pre_len + post_len;
|
|
if (min_len > 0) {
|
|
/* Generate: arr.size >= min_len using GE opcode */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM(size)), 0);
|
|
gen_int(s, cursp(), min_len);
|
|
/* GE: R[a] = R[a] >= R[a+1]; size at cursp-1, min_len at cursp */
|
|
genop_1(s, OP_GE, cursp() - 1);
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp() - 1, *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
pop();
|
|
}
|
|
}
|
|
|
|
/* Match pre-rest elements */
|
|
i = 0;
|
|
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
|
|
/* Get arr[i] */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
push(); /* Preserve element result for codegen_pattern */
|
|
/* Match element pattern */
|
|
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
|
pop(); /* Clean up element slot */
|
|
}
|
|
|
|
/* Bind rest elements if rest is a variable */
|
|
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
|
|
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
|
|
if (rest_var->name) {
|
|
int var_idx = lv_idx(s, rest_var->name);
|
|
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
|
|
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
|
push();
|
|
gen_int(s, cursp(), pre_len); /* start at cursp */
|
|
push();
|
|
if (post_len > 0) {
|
|
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
|
|
}
|
|
else {
|
|
gen_int(s, cursp(), -1); /* end at cursp */
|
|
}
|
|
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
|
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
|
/* arr at cursp-2, range at cursp-1 */
|
|
pop(); /* cursp now at range position */
|
|
pop(); /* cursp now at arr position */
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
if (var_idx > 0) {
|
|
gen_move(s, var_idx, cursp(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Match post-rest elements */
|
|
i = -post_len;
|
|
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
|
|
/* Get arr[i] (negative index from end) */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
push(); /* Preserve element result for codegen_pattern */
|
|
/* Match element pattern */
|
|
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
|
pop(); /* Clean up element slot */
|
|
}
|
|
|
|
pop(); /* Pop arr_reg */
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_FIND:
|
|
{
|
|
/* Find pattern: [*pre, elem1, elem2, ..., *post]
|
|
* Searches for elems anywhere in the array.
|
|
*
|
|
* Stack layout:
|
|
* arr_reg: deconstructed array (stable)
|
|
* idx_reg: current search index (stable)
|
|
*
|
|
* Loop bound is recomputed each iteration since OP_SEND clobbers registers.
|
|
*/
|
|
struct mrb_ast_pat_find_node *pat_find = pat_find_node(pattern);
|
|
int elems_len = 0;
|
|
node *elem;
|
|
int arr_reg = cursp();
|
|
int idx_reg;
|
|
uint32_t loop_start, match_fail, loop_end;
|
|
|
|
/* Count middle elements */
|
|
for (elem = pat_find->elems; elem; elem = elem->cdr) elems_len++;
|
|
|
|
/* Call deconstruct on target */
|
|
gen_move(s, cursp(), target, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, arr_reg, sym_idx(s, MRB_SYM(deconstruct)), 0);
|
|
|
|
/* Check minimum length: arr.size >= elems_len */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM(size)), 0);
|
|
gen_int(s, cursp(), elems_len);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(ge)), 1);
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
|
|
/* Initialize index to 0 */
|
|
idx_reg = cursp();
|
|
gen_int(s, idx_reg, 0);
|
|
push();
|
|
|
|
/* Loop: try matching at each position */
|
|
loop_start = s->pc;
|
|
match_fail = JMPLINK_START;
|
|
|
|
/* Check if idx <= arr.size - elems_len (i.e., idx < arr.size - elems_len + 1) */
|
|
/* Compute: arr.size - elems_len */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM(size)), 0);
|
|
gen_int(s, cursp(), elems_len);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(sub)), 1);
|
|
/* Now cursp() has (size - elems_len), compare: idx <= (size - elems_len) */
|
|
gen_move(s, cursp() + 1, idx_reg, 0);
|
|
push();
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(ge)), 1);
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
|
|
/* Try to match each middle element at idx+offset */
|
|
int offset = 0;
|
|
for (elem = pat_find->elems; elem; elem = elem->cdr, offset++) {
|
|
/* Get arr[idx + offset] */
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
if (offset == 0) {
|
|
gen_move(s, cursp(), idx_reg, 0);
|
|
}
|
|
else {
|
|
gen_move(s, cursp(), idx_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), offset);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(add)), 1);
|
|
}
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
push(); /* Preserve element result for codegen_pattern */
|
|
/* Match element pattern - on fail, try next index */
|
|
codegen_pattern(s, elem->car, cursp() - 1, &match_fail, -1);
|
|
pop(); /* Clean up element slot */
|
|
}
|
|
|
|
/* All elements matched - bind pre and post if named */
|
|
if (pat_find->pre && pat_find->pre != (node*)-1) {
|
|
struct mrb_ast_pat_var_node *pre_var = pat_var_node(pat_find->pre);
|
|
if (pre_var->name) {
|
|
int var_idx = lv_idx(s, pre_var->name);
|
|
/* pre = arr[0...idx] (exclusive range) */
|
|
/* Following the NODE_PAT_ARRAY pattern exactly */
|
|
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
|
push();
|
|
gen_int(s, cursp(), 0); /* start=0 at cursp */
|
|
push();
|
|
gen_move(s, cursp(), idx_reg, 0); /* end=idx at cursp */
|
|
/* start at cursp-1, end at cursp; create exclusive range at cursp-1 */
|
|
genop_1(s, OP_RANGE_EXC, cursp() - 1);
|
|
/* arr at cursp-2, range at cursp-1 */
|
|
pop(); /* cursp now at range position */
|
|
pop(); /* cursp now at arr position */
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
if (var_idx > 0) {
|
|
gen_move(s, var_idx, cursp(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pat_find->post && pat_find->post != (node*)-1) {
|
|
struct mrb_ast_pat_var_node *post_var = pat_var_node(pat_find->post);
|
|
if (post_var->name) {
|
|
int var_idx = lv_idx(s, post_var->name);
|
|
/* post = arr[(idx+elems_len)..-1] (inclusive range) */
|
|
/* Following the NODE_PAT_ARRAY pattern exactly */
|
|
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
|
push();
|
|
/* Compute idx + elems_len for start index */
|
|
gen_move(s, cursp(), idx_reg, 0); /* idx at cursp */
|
|
push();
|
|
gen_int(s, cursp(), elems_len); /* elems_len at cursp */
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(add)), 1);
|
|
/* start index (idx+elems_len) now at cursp */
|
|
push();
|
|
gen_int(s, cursp(), -1); /* end=-1 at cursp */
|
|
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
|
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
|
/* arr at cursp-2, range at cursp-1 */
|
|
pop(); /* cursp now at range position */
|
|
pop(); /* cursp now at arr position */
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
if (var_idx > 0) {
|
|
gen_move(s, var_idx, cursp(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Jump to success (end of find pattern) */
|
|
loop_end = genjmp(s, OP_JMP, JMPLINK_START);
|
|
|
|
/* Match failed - increment index and try again */
|
|
dispatch_linked(s, match_fail);
|
|
gen_move(s, cursp(), idx_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), 1);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(add)), 1);
|
|
gen_move(s, idx_reg, cursp(), 0);
|
|
genjmp(s, OP_JMP, loop_start);
|
|
|
|
/* Success exit point */
|
|
dispatch(s, loop_end);
|
|
|
|
pop(); /* idx_reg */
|
|
pop(); /* arr_reg */
|
|
}
|
|
break;
|
|
|
|
case NODE_PAT_HASH:
|
|
{
|
|
struct mrb_ast_pat_hash_node *pat_hash = pat_hash_node(pattern);
|
|
int hash_reg = cursp();
|
|
node *pair;
|
|
int num_keys = 0;
|
|
|
|
/* Count keys */
|
|
for (pair = pat_hash->pairs; pair; pair = pair->cdr) num_keys++;
|
|
|
|
/* Call deconstruct_keys.
|
|
* Pass nil when all keys are needed (rest or exact match).
|
|
* Pass keys array only for partial match (optimization for custom classes). */
|
|
gen_move(s, cursp(), target, 0);
|
|
push();
|
|
if (pat_hash->rest == NULL && num_keys > 0) {
|
|
/* Partial match: pass keys array */
|
|
gen_pat_keys_ary(s, pat_hash->pairs, num_keys);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
genop_3(s, OP_SEND, hash_reg, sym_idx(s, MRB_SYM(deconstruct_keys)), 1);
|
|
pop();
|
|
|
|
/* Check all keys exist and get values via __pat_values */
|
|
if (num_keys > 0) {
|
|
int vals_reg = cursp();
|
|
gen_move(s, vals_reg, hash_reg, 0);
|
|
push();
|
|
gen_pat_keys_ary(s, pat_hash->pairs, num_keys);
|
|
genop_3(s, OP_SEND, vals_reg, sym_idx(s, MRB_SYM(__pat_values)), 1);
|
|
pop(); /* keys_ary */
|
|
/* vals_reg = values array or false; fail if false */
|
|
tmp = genjmp2(s, OP_JMPNOT, vals_reg, *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
|
|
/* Match each value against its pattern */
|
|
int i = 0;
|
|
for (pair = pat_hash->pairs; pair; pair = pair->cdr, i++) {
|
|
node *pat = pair->car->cdr;
|
|
|
|
gen_move(s, cursp(), vals_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
push(); push(); pop(); pop(); pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM(aref)), 1);
|
|
push();
|
|
|
|
codegen_pattern(s, pat, cursp() - 1, fail_pos, -1);
|
|
pop();
|
|
}
|
|
pop(); /* vals_reg */
|
|
}
|
|
|
|
/* Handle rest pattern */
|
|
if (pat_hash->rest == (node*)-1 || (num_keys == 0 && pat_hash->rest == NULL)) {
|
|
/* **nil or empty {}: exact match - verify hash.size == num_keys */
|
|
gen_move(s, cursp(), hash_reg, 0);
|
|
push();
|
|
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM(size)), 0);
|
|
gen_int(s, cursp(), num_keys);
|
|
genop_1(s, OP_EQ, cursp() - 1);
|
|
tmp = genjmp2(s, OP_JMPNOT, cursp() - 1, *fail_pos, 1);
|
|
*fail_pos = tmp;
|
|
pop();
|
|
}
|
|
else if (pat_hash->rest && pat_hash->rest != (node*)-2) {
|
|
/* **var: capture remaining keys via hash.__except(keys_array) */
|
|
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_hash->rest);
|
|
if (rest_var->name) {
|
|
int var_idx = lv_idx(s, rest_var->name);
|
|
int recv = cursp();
|
|
gen_move(s, recv, hash_reg, 0);
|
|
push();
|
|
if (num_keys > 0) {
|
|
gen_pat_keys_ary(s, pat_hash->pairs, num_keys);
|
|
genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM(__except)), 1);
|
|
pop();
|
|
}
|
|
else {
|
|
genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM(dup)), 0);
|
|
}
|
|
if (var_idx > 0) {
|
|
gen_move(s, var_idx, recv, 1);
|
|
}
|
|
pop();
|
|
}
|
|
}
|
|
|
|
pop(); /* hash_reg */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
raise_error(s, "unsupported pattern type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Definition node codegen functions */
|
|
|
|
static void
|
|
codegen_def(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_def_node *def_n = def_node(varnode);
|
|
int sym = sym_idx(s, def_n->name);
|
|
|
|
/* Call lambda_body directly with individual parameters */
|
|
/* For NODE_DEF, args should contain the full locals structure from defn_setup */
|
|
int idx = lambda_body(s, def_n->locals, def_n->args, def_n->body, 0);
|
|
|
|
if (idx <= 0xff) {
|
|
/* TDEF fusion: TCLASS + METHOD + DEF -> TDEF */
|
|
genop_3(s, OP_TDEF, cursp(), sym, idx);
|
|
}
|
|
else {
|
|
genop_1(s, OP_TCLASS, cursp());
|
|
push();
|
|
genop_2(s, OP_METHOD, cursp(), idx);
|
|
push(); pop();
|
|
pop();
|
|
genop_2(s, OP_DEF, cursp(), sym);
|
|
}
|
|
if (val) push();
|
|
}
|
|
|
|
/* Helper function for generating class/module/singleton class body */
|
|
/* Forward declaration */
|
|
static mrb_bool is_empty_stmts(node *stmt_node);
|
|
|
|
static void
|
|
gen_class_body(codegen_scope *s, node *body, int val)
|
|
{
|
|
int idx;
|
|
|
|
if (body && body->cdr) {
|
|
/* Extract locals and body from the cons structure: (locals . body) */
|
|
node *locals = body->car;
|
|
node *body_stmts = body->cdr;
|
|
|
|
/* Check for empty body case */
|
|
if (is_empty_stmts(body_stmts)) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
else {
|
|
/* Generate proper scope with locals and body */
|
|
idx = scope_body(s, locals, body_stmts, val);
|
|
genop_2(s, OP_EXEC, cursp(), idx);
|
|
}
|
|
}
|
|
else {
|
|
/* No body - load nil */
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
}
|
|
|
|
/* Helper function for generating namespace/parent for class/module */
|
|
static void
|
|
gen_namespace(codegen_scope *s, node *name)
|
|
{
|
|
if (name->car == (node*)0) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
else if (name->car == (node*)1) {
|
|
genop_1(s, OP_OCLASS, cursp());
|
|
push();
|
|
}
|
|
else {
|
|
codegen(s, name->car, VAL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_class(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_class_node *class_n = class_node(varnode);
|
|
node *name = class_n->name;
|
|
node *superclass = class_n->superclass;
|
|
node *body = class_n->body;
|
|
int idx;
|
|
|
|
/* Handle class namespace */
|
|
gen_namespace(s, name);
|
|
|
|
/* Handle superclass */
|
|
if (superclass) {
|
|
codegen(s, superclass, VAL);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
|
|
pop(); pop();
|
|
|
|
/* Create class with name symbol */
|
|
idx = sym_idx(s, node_to_sym(name->cdr));
|
|
genop_2(s, OP_CLASS, cursp(), idx);
|
|
|
|
/* Generate class body */
|
|
gen_class_body(s, body, val);
|
|
|
|
if (val) {
|
|
push();
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_module(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_module_node *module_n = module_node(varnode);
|
|
node *name = module_n->name;
|
|
node *body = module_n->body;
|
|
int idx;
|
|
|
|
/* Handle module namespace */
|
|
gen_namespace(s, name);
|
|
pop();
|
|
|
|
/* Create module with name symbol */
|
|
idx = sym_idx(s, node_to_sym(name->cdr));
|
|
genop_2(s, OP_MODULE, cursp(), idx);
|
|
|
|
/* Generate module body */
|
|
gen_class_body(s, body, val);
|
|
|
|
if (val) {
|
|
push();
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_sclass(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_sclass_node *sclass_n = sclass_node(varnode);
|
|
node *obj = sclass_n->obj;
|
|
node *body = sclass_n->body;
|
|
|
|
/* Generate code for the singleton object */
|
|
codegen(s, obj, VAL);
|
|
pop();
|
|
|
|
/* Enter singleton class scope */
|
|
genop_1(s, OP_SCLASS, cursp());
|
|
|
|
/* Generate singleton class body */
|
|
gen_class_body(s, body, val);
|
|
|
|
if (val) {
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* Variable-sized assignment codegen functions */
|
|
static void
|
|
codegen_asgn(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_asgn_node *asgn_n = asgn_node(varnode);
|
|
node *lhs = asgn_n->lhs;
|
|
node *rhs = asgn_n->rhs;
|
|
|
|
gen_assignment(s, lhs, rhs, 0, val);
|
|
}
|
|
|
|
static void
|
|
codegen_masgn(codegen_scope *s, node *varnode, node *rhs, int sp, int val)
|
|
{
|
|
struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)varnode;
|
|
|
|
/* If called from codegen_variable_node context, use the embedded rhs */
|
|
if (!rhs && sp == 0) {
|
|
rhs = masgn_n->rhs;
|
|
sp = 0; /* Use register 0 as base for standalone assignment */
|
|
}
|
|
|
|
int len = 0, n = 0, post = 0;
|
|
node *t = rhs ? rhs : masgn_n->rhs, *p;
|
|
int rhs_reg = sp;
|
|
|
|
if (!val && t && node_type(t) == NODE_ARRAY) {
|
|
struct mrb_ast_array_node *an = array_node(t);
|
|
if (an->elements && nosplat(an->elements)) {
|
|
/* fixed rhs */
|
|
t = an->elements;
|
|
|
|
/* Optimization: direct generation for simple cases */
|
|
/* When all lhs are local vars and all rhs are simple literals, */
|
|
/* generate directly into target registers (no temporaries) */
|
|
if (masgn_n->pre && !masgn_n->rest && !masgn_n->post) {
|
|
int regs[16]; /* support up to 16 variables */
|
|
node *lhs = masgn_n->pre;
|
|
node *rhs_elem = t;
|
|
int rhs_count = 0, lhs_count = 0;
|
|
mrb_bool all_simple = TRUE;
|
|
|
|
/* Count lhs variables */
|
|
while (lhs && lhs_count < 16) {
|
|
lhs_count++;
|
|
lhs = lhs->cdr;
|
|
}
|
|
|
|
/* Count and check rhs are all simple literals */
|
|
while (rhs_elem && rhs_count < 16) {
|
|
if (!is_simple_literal(rhs_elem->car)) {
|
|
all_simple = FALSE;
|
|
break;
|
|
}
|
|
rhs_count++;
|
|
rhs_elem = rhs_elem->cdr;
|
|
}
|
|
/* Only apply when lhs and rhs counts match exactly */
|
|
lhs = masgn_n->pre;
|
|
if (all_simple && lhs_count > 0 && lhs_count == rhs_count &&
|
|
all_lvar_pre(s, lhs, regs, lhs_count)) {
|
|
/* Direct generation: generate literals into target registers */
|
|
rhs_elem = t;
|
|
for (int i = 0; i < lhs_count; i++) {
|
|
gen_literal_to_reg(s, rhs_elem->car, regs[i]);
|
|
rhs_elem = rhs_elem->cdr;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
rhs_reg = cursp(); /* Save register where values will be pushed */
|
|
while (t) {
|
|
codegen(s, t->car, VAL);
|
|
len++;
|
|
t = t->cdr;
|
|
}
|
|
if (masgn_n->pre) { /* pre */
|
|
t = masgn_n->pre;
|
|
n = 0;
|
|
while (t) {
|
|
if (n < len) {
|
|
gen_assignment(s, t->car, NULL, rhs_reg+n, NOVAL);
|
|
n++;
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, rhs_reg+n);
|
|
gen_assignment(s, t->car, NULL, rhs_reg+n, NOVAL);
|
|
}
|
|
t = t->cdr;
|
|
}
|
|
}
|
|
/* Count post variables */
|
|
if (masgn_n->post) {
|
|
p = masgn_n->post;
|
|
while (p) {
|
|
post++;
|
|
p = p->cdr;
|
|
}
|
|
}
|
|
/* Handle rest variable */
|
|
if (masgn_n->rest && (intptr_t)masgn_n->rest != -1) {
|
|
int rn;
|
|
|
|
if (len < post + n) {
|
|
rn = 0;
|
|
}
|
|
else {
|
|
rn = len - post - n;
|
|
}
|
|
if (cursp() == rhs_reg+n) {
|
|
genop_2(s, OP_ARRAY, cursp(), rn);
|
|
}
|
|
else {
|
|
genop_3(s, OP_ARRAY2, cursp(), rhs_reg+n, rn);
|
|
}
|
|
gen_assignment(s, masgn_n->rest, NULL, cursp(), NOVAL);
|
|
n += rn;
|
|
}
|
|
/* Handle post variables */
|
|
if (masgn_n->post) {
|
|
t = masgn_n->post;
|
|
while (t) {
|
|
if (n<len) {
|
|
gen_assignment(s, t->car, NULL, rhs_reg+n, NOVAL);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
gen_assignment(s, t->car, NULL, cursp(), NOVAL);
|
|
}
|
|
t = t->cdr;
|
|
n++;
|
|
}
|
|
}
|
|
pop_n(len);
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
/* variable rhs - implement gen_massignment logic directly for variable-sized nodes */
|
|
|
|
/* Check if this is parameter destructuring (called from lambda_body) */
|
|
if (!rhs && sp > 0) {
|
|
/* Parameter destructuring: value is already in register sp */
|
|
rhs_reg = sp;
|
|
}
|
|
else if (t) {
|
|
codegen(s, t, VAL);
|
|
rhs_reg = cursp() - 1; /* rhs is now at cursp()-1 */
|
|
}
|
|
else {
|
|
/* No rhs and no sp value - should not happen in normal cases */
|
|
return;
|
|
}
|
|
|
|
/* Handle the lhs structure directly */
|
|
n = 0;
|
|
post = 0;
|
|
|
|
if (masgn_n->pre) { /* pre */
|
|
node *pre = masgn_n->pre;
|
|
n = 0;
|
|
while (pre) {
|
|
int sp = cursp();
|
|
genop_3(s, OP_AREF, sp, rhs_reg, n);
|
|
push();
|
|
gen_assignment(s, pre->car, NULL, sp, NOVAL);
|
|
pop();
|
|
n++;
|
|
pre = pre->cdr;
|
|
}
|
|
}
|
|
|
|
/* Count post variables */
|
|
if (masgn_n->post) {
|
|
node *p = masgn_n->post;
|
|
while (p) {
|
|
post++;
|
|
p = p->cdr;
|
|
}
|
|
}
|
|
|
|
/* Only generate APOST if there's rest or post variables */
|
|
if ((masgn_n->rest && (intptr_t)masgn_n->rest != -1) || masgn_n->post) {
|
|
gen_move(s, cursp(), rhs_reg, val);
|
|
push_n(post+1);
|
|
pop_n(post+1);
|
|
genop_3(s, OP_APOST, cursp(), n, post);
|
|
int nn = 1;
|
|
if (masgn_n->rest && (intptr_t)masgn_n->rest != -1) { /* rest */
|
|
gen_assignment(s, masgn_n->rest, NULL, cursp(), NOVAL);
|
|
}
|
|
if (masgn_n->post) {
|
|
node *post_part = masgn_n->post;
|
|
while (post_part) {
|
|
gen_assignment(s, post_part->car, NULL, cursp()+nn, NOVAL);
|
|
post_part = post_part->cdr;
|
|
nn++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!val && t) {
|
|
pop(); /* pop the rhs value */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_op_asgn(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_op_asgn_node *op_asgn_n = op_asgn_node(varnode);
|
|
node *lhs = op_asgn_n->lhs;
|
|
node *rhs = op_asgn_n->rhs;
|
|
mrb_sym sym = op_asgn_n->op;
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(s->mrb, sym, &len);
|
|
int vsp = -1;
|
|
|
|
/* Handle ||= and &&= operators */
|
|
if (len == 2 &&
|
|
((name[0] == '|' && name[1] == '|') ||
|
|
(name[0] == '&' && name[1] == '&'))) {
|
|
uint32_t pos;
|
|
enum node_type lhs_type = node_type(lhs);
|
|
|
|
/* For ||= on class variables and constants, wrap read in exception handling */
|
|
if (name[0] == '|' && (lhs_type == NODE_CVAR || lhs_type == NODE_CONST)) {
|
|
int catch_entry, begin, end;
|
|
int noexc, exc;
|
|
struct loopinfo *lp;
|
|
|
|
lp = loop_push(s, LOOP_BEGIN);
|
|
lp->pc0 = new_label(s);
|
|
catch_entry = catch_handler_new(s);
|
|
begin = s->pc;
|
|
exc = cursp();
|
|
codegen(s, lhs, VAL);
|
|
end = s->pc;
|
|
noexc = genjmp_0(s, OP_JMP);
|
|
lp->type = LOOP_RESCUE;
|
|
catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc);
|
|
genop_1(s, OP_EXCEPT, exc);
|
|
genop_1(s, OP_LOADFALSE, exc);
|
|
dispatch(s, noexc);
|
|
loop_pop(s, NOVAL);
|
|
}
|
|
else {
|
|
/* Generate code to get current value of LHS */
|
|
codegen(s, lhs, VAL);
|
|
}
|
|
|
|
pop();
|
|
if (val) {
|
|
if (vsp >= 0) {
|
|
gen_move(s, vsp, cursp(), 1);
|
|
}
|
|
pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val);
|
|
}
|
|
else {
|
|
pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val);
|
|
}
|
|
codegen(s, rhs, VAL);
|
|
pop();
|
|
if (val && vsp >= 0) {
|
|
gen_move(s, vsp, cursp(), 1);
|
|
}
|
|
gen_assignment(s, lhs, NULL, cursp(), val);
|
|
dispatch(s, pos);
|
|
return;
|
|
}
|
|
|
|
/* For other operators, generate: lhs = lhs op rhs */
|
|
codegen(s, lhs, VAL);
|
|
codegen(s, rhs, VAL);
|
|
push(); pop();
|
|
pop(); pop();
|
|
|
|
/* Apply the operator */
|
|
if (len == 1 && name[0] == '+') {
|
|
gen_addsub(s, OP_ADD, cursp());
|
|
}
|
|
else if (len == 1 && name[0] == '-') {
|
|
gen_addsub(s, OP_SUB, cursp());
|
|
}
|
|
else if (len == 1 && name[0] == '*') {
|
|
genop_1(s, OP_MUL, cursp());
|
|
}
|
|
else if (len == 1 && name[0] == '/') {
|
|
genop_1(s, OP_DIV, cursp());
|
|
}
|
|
else if (len == 1 && name[0] == '<') {
|
|
genop_1(s, OP_LT, cursp());
|
|
}
|
|
else if (len == 2 && name[0] == '<' && name[1] == '=') {
|
|
genop_1(s, OP_LE, cursp());
|
|
}
|
|
else if (len == 1 && name[0] == '>') {
|
|
genop_1(s, OP_GT, cursp());
|
|
}
|
|
else if (len == 2 && name[0] == '>' && name[1] == '=') {
|
|
genop_1(s, OP_GE, cursp());
|
|
}
|
|
else {
|
|
int idx = sym_idx(s, sym);
|
|
genop_3(s, OP_SEND, cursp(), idx, 1);
|
|
}
|
|
|
|
/* Assign the result back to LHS */
|
|
gen_assignment(s, lhs, NULL, cursp(), val);
|
|
}
|
|
|
|
/* Variable-sized expression codegen functions */
|
|
static void
|
|
codegen_and(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_and_node *and_n = (struct mrb_ast_and_node*)varnode;
|
|
node *left = and_n->left;
|
|
node *right = and_n->right;
|
|
uint32_t pos;
|
|
|
|
if (true_always(left)) {
|
|
codegen(s, right, val);
|
|
return;
|
|
}
|
|
if (false_always(left)) {
|
|
codegen(s, left, val);
|
|
return;
|
|
}
|
|
codegen(s, left, VAL);
|
|
pop();
|
|
pos = genjmp2_0(s, OP_JMPNOT, cursp(), val);
|
|
codegen(s, right, val);
|
|
dispatch(s, pos);
|
|
}
|
|
|
|
static void
|
|
codegen_or(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_or_node *or_n = (struct mrb_ast_or_node*)varnode;
|
|
node *left = or_n->left;
|
|
node *right = or_n->right;
|
|
uint32_t pos;
|
|
|
|
if (true_always(left)) {
|
|
codegen(s, left, val);
|
|
return;
|
|
}
|
|
if (false_always(left)) {
|
|
codegen(s, right, val);
|
|
return;
|
|
}
|
|
codegen(s, left, VAL);
|
|
pop();
|
|
pos = genjmp2_0(s, OP_JMPIF, cursp(), val);
|
|
codegen(s, right, val);
|
|
dispatch(s, pos);
|
|
}
|
|
|
|
static void
|
|
codegen_return(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_return_node *return_n = return_node(varnode);
|
|
node *args = return_n->args;
|
|
|
|
if (args) {
|
|
gen_retval(s, args);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
if (s->loop) {
|
|
gen_return(s, OP_RETURN_BLK, cursp());
|
|
}
|
|
else {
|
|
gen_return(s, OP_RETURN, cursp());
|
|
}
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_yield(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_yield_node *yield_n = yield_node(varnode);
|
|
node *args = yield_n->args;
|
|
codegen_scope *s2 = s;
|
|
int lv = 0, ainfo = -1;
|
|
int n = 0, nk = 0, sendv = 0;
|
|
|
|
while (!s2->mscope) {
|
|
lv++;
|
|
s2 = s2->prev;
|
|
if (!s2) break;
|
|
}
|
|
if (s2) {
|
|
ainfo = (int)s2->ainfo;
|
|
}
|
|
if (ainfo < 0) codegen_error(s, "invalid yield (SyntaxError)");
|
|
if (lv > 0xf) codegen_error(s, "too deep nesting");
|
|
push();
|
|
if (args) {
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args;
|
|
if (callargs->regular_args) {
|
|
n = gen_values(s, callargs->regular_args, VAL, 14);
|
|
if (n < 0) {
|
|
n = sendv = 1;
|
|
push();
|
|
}
|
|
}
|
|
if (callargs->keyword_args) {
|
|
nk = gen_hash(s, callargs->keyword_args, VAL, 14);
|
|
if (nk < 0) {
|
|
nk = 15;
|
|
}
|
|
}
|
|
}
|
|
push();pop(); /* space for a block */
|
|
pop_n(n + (nk == 15 ? 1 : nk * 2) + 1);
|
|
genop_2S(s, OP_BLKPUSH, cursp(), (ainfo<<4)|(lv & 0xf));
|
|
if (sendv) n = CALL_MAXARGS;
|
|
if (nk == 0 && n < 15) {
|
|
/* fast path: direct block call without method dispatch */
|
|
genop_2(s, OP_BLKCALL, cursp(), n);
|
|
}
|
|
else {
|
|
/* fallback: use SEND for keyword args or splat */
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM(call)), n|(nk<<4));
|
|
}
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_super(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_super_node *super_n = super_node(varnode);
|
|
node *tree = super_n->args;
|
|
|
|
codegen_scope *s2 = s;
|
|
int lv = 0;
|
|
int n = 0, nk = 0, st = 0;
|
|
|
|
push();
|
|
while (!s2->mscope) {
|
|
lv++;
|
|
s2 = s2->prev;
|
|
if (!s2) break;
|
|
}
|
|
if (tree) {
|
|
/* Handle callargs structure - direct casting like new_args() */
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree;
|
|
|
|
/* Regular arguments */
|
|
if (callargs->regular_args) {
|
|
st = n = gen_values(s, callargs->regular_args, VAL, 14);
|
|
if (n < 0) {
|
|
st = 1; n = 15;
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* Keyword arguments */
|
|
if (callargs->keyword_args) {
|
|
nk = gen_hash(s, callargs->keyword_args, VAL, 14);
|
|
if (nk < 0) {st++; nk = 15;}
|
|
else st += nk*2;
|
|
n |= nk<<4;
|
|
}
|
|
|
|
/* Block arguments */
|
|
if (callargs->block_arg) {
|
|
codegen(s, callargs->block_arg, VAL);
|
|
}
|
|
else if (s2) gen_blkmove(s, s2->ainfo, lv);
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
}
|
|
else {
|
|
if (s2) gen_blkmove(s, s2->ainfo, lv);
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
}
|
|
st++;
|
|
pop_n(st+1);
|
|
genop_2(s, OP_SUPER, cursp(), n);
|
|
if (val) push();
|
|
}
|
|
|
|
/* Variable-sized literal node generation functions */
|
|
static void
|
|
codegen_str(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_str_node *str_n = str_node(varnode);
|
|
node *list = str_n->list;
|
|
|
|
/* Use common cons list string codegen */
|
|
gen_string(s, list, val);
|
|
}
|
|
|
|
static void
|
|
codegen_range(codegen_scope *s, node *varnode, int val, mrb_bool exclusive)
|
|
{
|
|
node *left = dot2_node(varnode)->left;
|
|
node *right = dot2_node(varnode)->right;
|
|
|
|
codegen(s, left, val);
|
|
codegen(s, right, val);
|
|
if (!val) return;
|
|
pop(); pop();
|
|
genop_1(s, exclusive ? OP_RANGE_EXC : OP_RANGE_INC, cursp());
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_float(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_float_node *float_n = (struct mrb_ast_float_node*)varnode;
|
|
const char *value = float_n->value;
|
|
double f;
|
|
|
|
mrb_read_float(value, NULL, &f);
|
|
int off = new_lit_float(s, (mrb_float)f);
|
|
|
|
gen_load_op2(s, OP_LOADL, off, val);
|
|
}
|
|
|
|
/* Variable-sized simple node generation functions */
|
|
static void
|
|
codegen_self(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
/* Use traditional self codegen logic */
|
|
gen_load_op1(s, OP_LOADSELF, val);
|
|
}
|
|
|
|
static void
|
|
codegen_nil(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
/* Use traditional nil codegen logic */
|
|
gen_load_op1(s, OP_LOADNIL, val);
|
|
}
|
|
|
|
static void
|
|
codegen_true(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
/* Generate OP_LOADTRUE instruction for true literal */
|
|
gen_load_op1(s, OP_LOADTRUE, val);
|
|
}
|
|
|
|
static void
|
|
codegen_false(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
/* Generate OP_LOADFALSE instruction for false literal */
|
|
gen_load_op1(s, OP_LOADFALSE, val);
|
|
}
|
|
|
|
static void
|
|
codegen_const(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_const_node *const_n = const_node(varnode);
|
|
mrb_sym symbol = const_n->symbol;
|
|
|
|
int i = sym_idx(s, symbol);
|
|
genop_2(s, OP_GETCONST, cursp(), i);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_rescue(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_rescue_node *rescue = rescue_node(varnode);
|
|
node *body = rescue->body;
|
|
node *rescue_clauses = rescue->rescue_clauses;
|
|
node *else_clause = rescue->else_clause;
|
|
|
|
int noexc;
|
|
uint32_t exend, pos1, pos2, tmp;
|
|
struct loopinfo *lp;
|
|
int catch_entry, begin, end;
|
|
|
|
if (body == NULL) return;
|
|
lp = loop_push(s, LOOP_BEGIN);
|
|
lp->pc0 = new_label(s);
|
|
catch_entry = catch_handler_new(s);
|
|
begin = s->pc;
|
|
codegen(s, body, VAL);
|
|
pop();
|
|
lp->type = LOOP_RESCUE;
|
|
end = s->pc;
|
|
noexc = genjmp_0(s, OP_JMP);
|
|
catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc);
|
|
exend = JMPLINK_START;
|
|
pos1 = JMPLINK_START;
|
|
if (rescue_clauses) {
|
|
node *n2 = rescue_clauses;
|
|
int exc = cursp();
|
|
|
|
genop_1(s, OP_EXCEPT, exc);
|
|
push();
|
|
while (n2) {
|
|
node *n3 = n2->car;
|
|
node *n4 = n3->car;
|
|
|
|
dispatch(s, pos1);
|
|
pos2 = JMPLINK_START;
|
|
do {
|
|
if (n4 && n4->car && is_splat_node(n4->car)) {
|
|
codegen(s, n4->car, VAL);
|
|
gen_move(s, cursp(), exc, 0);
|
|
push_n(2); pop_n(2); /* space for one arg and a block */
|
|
pop();
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM(__case_eqq)), 1);
|
|
}
|
|
else {
|
|
if (n4) {
|
|
codegen(s, n4->car, VAL);
|
|
}
|
|
else {
|
|
genop_2(s, OP_GETCONST, cursp(), sym_idx(s, MRB_SYM(StandardError)));
|
|
push();
|
|
}
|
|
pop();
|
|
genop_2(s, OP_RESCUE, exc, cursp());
|
|
}
|
|
tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, val);
|
|
pos2 = tmp;
|
|
if (n4) {
|
|
n4 = n4->cdr;
|
|
}
|
|
} while (n4);
|
|
pos1 = genjmp_0(s, OP_JMP);
|
|
dispatch_linked(s, pos2);
|
|
|
|
pop();
|
|
if (n3->cdr->car) {
|
|
gen_assignment(s, n3->cdr->car, NULL, exc, NOVAL);
|
|
}
|
|
if (n3->cdr->cdr->car) {
|
|
codegen(s, n3->cdr->cdr->car, val);
|
|
if (val) pop();
|
|
}
|
|
tmp = genjmp(s, OP_JMP, exend);
|
|
exend = tmp;
|
|
n2 = n2->cdr;
|
|
push();
|
|
}
|
|
if (pos1 != JMPLINK_START) {
|
|
dispatch(s, pos1);
|
|
genop_1(s, OP_RAISEIF, exc);
|
|
}
|
|
}
|
|
pop();
|
|
dispatch(s, noexc);
|
|
if (else_clause) {
|
|
codegen(s, else_clause, val);
|
|
}
|
|
else if (val) {
|
|
push();
|
|
}
|
|
dispatch_linked(s, exend);
|
|
loop_pop(s, NOVAL);
|
|
}
|
|
|
|
static void
|
|
codegen_block(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
if (!val) return;
|
|
|
|
struct mrb_ast_block_node *n = block_node(varnode);
|
|
|
|
/* Call lambda_body directly with individual parameters */
|
|
int idx = lambda_body(s, n->locals, n->args, n->body, 1);
|
|
genop_2(s, OP_BLOCK, cursp(), idx);
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_break(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_break_node *n = (struct mrb_ast_break_node*)varnode;
|
|
loop_break(s, n->value);
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_next(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_next_node *n = (struct mrb_ast_next_node*)varnode;
|
|
if (!s->loop) {
|
|
raise_error(s, "unexpected next");
|
|
}
|
|
else if (s->loop->type == LOOP_NORMAL) {
|
|
codegen(s, n->value, NOVAL);
|
|
genjmp(s, OP_JMPUW, s->loop->pc0);
|
|
}
|
|
else {
|
|
if (n->value) {
|
|
codegen(s, n->value, VAL);
|
|
pop();
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
gen_return(s, OP_RETURN, cursp());
|
|
}
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_redo(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
for (const struct loopinfo *lp = s->loop; ; lp = lp->prev) {
|
|
if (!lp) {
|
|
raise_error(s, "unexpected redo");
|
|
break;
|
|
}
|
|
if (lp->type != LOOP_BEGIN && lp->type != LOOP_RESCUE) {
|
|
genjmp(s, OP_JMPUW, lp->pc1);
|
|
break;
|
|
}
|
|
}
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_retry(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
const struct loopinfo *lp = s->loop;
|
|
|
|
while (lp && lp->type != LOOP_RESCUE) {
|
|
lp = lp->prev;
|
|
}
|
|
if (!lp) {
|
|
raise_error(s, "unexpected retry");
|
|
}
|
|
else {
|
|
genjmp(s, OP_JMPUW, lp->pc0);
|
|
}
|
|
if (!val) return;
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_xstr(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_xstr_node *n = xstr_node(varnode);
|
|
node *list = n->list;
|
|
int sym;
|
|
|
|
/* Always execute backtick command for side effects, even in NOVAL mode */
|
|
push();
|
|
/* Generate string using common function */
|
|
gen_string(s, list, VAL);
|
|
|
|
push(); /* for block */
|
|
pop_n(3);
|
|
sym = sym_idx(s, MRB_OPSYM(tick)); /* ` */
|
|
genop_3(s, OP_SSEND, cursp(), sym, 1);
|
|
|
|
if (val) {
|
|
push(); /* Keep result on stack if needed */
|
|
}
|
|
/* If val=0, the result is discarded but the method was still called */
|
|
}
|
|
|
|
static void
|
|
codegen_regx(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_regx_node *n = regx_node(varnode);
|
|
|
|
if (val) {
|
|
int sym = sym_idx(s, mrb_intern_lit(s->mrb, REGEXP_CLASS));
|
|
int argc = 1;
|
|
int off;
|
|
|
|
genop_1(s, OP_OCLASS, cursp());
|
|
genop_2(s, OP_GETMCNST, cursp(), sym);
|
|
push();
|
|
|
|
/* Generate regex pattern using common cons list function */
|
|
gen_string(s, n->list, VAL);
|
|
|
|
/* Add flags and/or encoding if present */
|
|
if ((n->flags && *n->flags) || (n->encoding && *n->encoding)) {
|
|
/* Add flags (or nil if not present but encoding is) */
|
|
if (n->flags && *n->flags) {
|
|
off = new_lit_cstr(s, n->flags);
|
|
genop_2(s, OP_STRING, cursp(), off);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
push();
|
|
argc++;
|
|
|
|
/* Add encoding if present */
|
|
if (n->encoding && *n->encoding) {
|
|
off = new_lit_cstr(s, n->encoding);
|
|
genop_2(s, OP_STRING, cursp(), off);
|
|
push();
|
|
argc++;
|
|
}
|
|
}
|
|
|
|
push(); /* space for a block */
|
|
pop_n(argc+2);
|
|
sym = sym_idx(s, MRB_SYM(compile));
|
|
genop_3(s, OP_SEND, cursp(), sym, argc);
|
|
push();
|
|
}
|
|
else {
|
|
/* NOVAL case: still need to evaluate expressions for side effects */
|
|
gen_string(s, n->list, NOVAL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_heredoc(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_heredoc_node *n = heredoc_node(varnode);
|
|
// Process heredoc doc field as cons list string
|
|
gen_string(s, n->info.doc, val);
|
|
}
|
|
|
|
static void
|
|
codegen_dsym(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_str_node *n = dsym_node(varnode);
|
|
// Generate the list content, then intern to symbol
|
|
gen_string(s, n->list, val);
|
|
if (val) {
|
|
gen_intern(s);
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_nth_ref(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_nth_ref_node *n = (struct mrb_ast_nth_ref_node*)varnode;
|
|
mrb_state *mrb = s->mrb;
|
|
mrb_value str;
|
|
int sym;
|
|
|
|
str = mrb_format(mrb, "$%d", n->nth);
|
|
sym = sym_idx(s, mrb_intern_str(mrb, str));
|
|
gen_load_op2(s, OP_GETGV, sym, val);
|
|
}
|
|
|
|
static void
|
|
codegen_back_ref(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_back_ref_node *n = (struct mrb_ast_back_ref_node*)varnode;
|
|
char buf[] = {'$', (char)n->type};
|
|
int sym = sym_idx(s, mrb_intern(s->mrb, buf, sizeof(buf)));
|
|
gen_load_op2(s, OP_GETGV, sym, val);
|
|
}
|
|
|
|
static void
|
|
codegen_nvar(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
if (!val) return;
|
|
struct mrb_ast_nvar_node *n = (struct mrb_ast_nvar_node*)varnode;
|
|
|
|
gen_move(s, cursp(), n->num, val);
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_dvar(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_dvar_node *n = (struct mrb_ast_dvar_node*)varnode;
|
|
// DVAR nodes are not currently used in mruby, but provide basic implementation
|
|
if (val) {
|
|
gen_lvar(s, n->name, val);
|
|
}
|
|
}
|
|
|
|
/* Unary operator codegen functions */
|
|
static void
|
|
codegen_not(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_not_node *n = (struct mrb_ast_not_node*)varnode;
|
|
// NOT nodes are rarely used - generate method call to !
|
|
if (val) {
|
|
codegen(s, n->operand, TRUE);
|
|
pop();
|
|
mrb_sym sym = sym_idx(s, mrb_intern_lit(s->mrb, "!"));
|
|
genop_3(s, OP_SEND, cursp(), sym, 0);
|
|
push();
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_negate(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_negate_node *n = (struct mrb_ast_negate_node*)varnode;
|
|
node *tree = n->operand;
|
|
|
|
/* Check if the operand is a variable-sized node */
|
|
enum node_type vnt = node_type(tree);
|
|
switch (vnt) {
|
|
#ifndef MRB_NO_FLOAT
|
|
case NODE_FLOAT:
|
|
if (val) {
|
|
struct mrb_ast_float_node *float_n = (struct mrb_ast_float_node*)tree;
|
|
const char *value = float_n->value;
|
|
double f;
|
|
|
|
mrb_read_float(value, NULL, &f);
|
|
int off = new_lit_float(s, (mrb_float)-f);
|
|
|
|
gen_load_lit(s, off);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case NODE_INT:
|
|
if (val) {
|
|
int32_t value = int_node(tree)->value;
|
|
if (value == INT32_MIN) {
|
|
/* -INT32_MIN overflows, use bigint */
|
|
int off = new_litbint(s, "2147483648", -10);
|
|
genop_2(s, OP_LOADL, cursp(), off);
|
|
}
|
|
else {
|
|
gen_int(s, cursp(), -value);
|
|
}
|
|
push();
|
|
}
|
|
break;
|
|
|
|
case NODE_BIGINT:
|
|
if (val) {
|
|
char *str = bigint_node(tree)->string;
|
|
int base = bigint_node(tree)->base;
|
|
/* Negate base to indicate negative number */
|
|
int off = new_litbint(s, str, -base);
|
|
genop_2(s, OP_LOADL, cursp(), off);
|
|
push();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
codegen(s, tree, VAL);
|
|
pop();
|
|
push_n(2);pop_n(2); /* space for receiver&block */
|
|
mrb_sym minus = MRB_OPSYM(minus);
|
|
if (!gen_uniop(s, minus, cursp())) {
|
|
genop_3(s, OP_SEND, cursp(), sym_idx(s, minus), 0);
|
|
}
|
|
if (val) push();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_colon2(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_colon2_node *n = (struct mrb_ast_colon2_node*)varnode;
|
|
// Generate COLON2 (::) access manually
|
|
int sym = sym_idx(s, n->name);
|
|
codegen(s, n->base, VAL);
|
|
pop();
|
|
genop_2(s, OP_GETMCNST, cursp(), sym);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_colon3(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_colon3_node *n = (struct mrb_ast_colon3_node*)varnode;
|
|
int sym = sym_idx(s, n->name);
|
|
genop_1(s, OP_OCLASS, cursp());
|
|
genop_2(s, OP_GETMCNST, cursp(), sym);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_defined(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
// DEFINED nodes are rarely used - generate basic implementation
|
|
(void)varnode; // suppress unused warning
|
|
if (val) {
|
|
// For now, just return nil (defined? is complex to implement correctly)
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_zsuper(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
/* NODE_ZSUPER now uses mrb_ast_super_node, which may have args */
|
|
struct mrb_ast_super_node *zsuper_n = super_node(varnode);
|
|
node *tree = zsuper_n->args; /* May be NULL or args added by call_with_block */
|
|
|
|
codegen_scope *s2 = s;
|
|
int lv = 0;
|
|
uint16_t ainfo = 0;
|
|
int n = CALL_MAXARGS;
|
|
int sp = cursp();
|
|
mrb_bool has_block_arg = FALSE;
|
|
|
|
push(); /* room for receiver */
|
|
int argary_pos = cursp();
|
|
while (!s2->mscope) {
|
|
lv++;
|
|
s2 = s2->prev;
|
|
if (!s2) break;
|
|
}
|
|
if (s2 && s2->ainfo > 0) {
|
|
ainfo = s2->ainfo;
|
|
has_block_arg = (ainfo >> 13) & 0x1;
|
|
}
|
|
if (lv > 0xf) codegen_error(s, "too deep nesting");
|
|
if (ainfo > 0) {
|
|
genop_2S(s, OP_ARGARY, argary_pos, (ainfo<<4)|(lv & 0xf));
|
|
push(); push(); push(); /* ARGARY pushes 3 values at most */
|
|
pop(); pop(); pop();
|
|
/* keyword arguments */
|
|
if (ainfo & 0x1) {
|
|
n |= CALL_MAXARGS<<4;
|
|
push();
|
|
/* If parent has keywords but no block parameter, ARGARY reads garbage for block */
|
|
if (!has_block_arg) {
|
|
genop_1(s, OP_LOADNIL, argary_pos+2);
|
|
}
|
|
}
|
|
/* block argument - tree here is args, so check for block */
|
|
if (tree) {
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree;
|
|
if (callargs->block_arg) {
|
|
push();
|
|
codegen(s, callargs->block_arg, VAL);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* block argument */
|
|
if (tree) {
|
|
struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree;
|
|
if (callargs->block_arg) {
|
|
codegen(s, callargs->block_arg, VAL);
|
|
}
|
|
}
|
|
else if (s2) {
|
|
gen_blkmove(s, 0, lv);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
n = 0;
|
|
}
|
|
s->sp = sp;
|
|
genop_2(s, OP_SUPER, cursp(), n);
|
|
if (val) push();
|
|
}
|
|
|
|
static void
|
|
codegen_lambda(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
if (!val) return;
|
|
|
|
struct mrb_ast_lambda_node *n = lambda_node(varnode);
|
|
|
|
/* Call lambda_body directly with individual parameters */
|
|
int idx = lambda_body(s, n->locals, n->args, n->body, 1);
|
|
genop_2(s, OP_LAMBDA, cursp(), idx);
|
|
push();
|
|
}
|
|
|
|
static void
|
|
codegen_words(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_words_node *n = words_node(varnode);
|
|
gen_literal_array(s, n->args, FALSE, val);
|
|
}
|
|
|
|
static void
|
|
codegen_symbols(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_symbols_node *n = symbols_node(varnode);
|
|
gen_literal_array(s, n->args, TRUE, val);
|
|
}
|
|
|
|
static void
|
|
codegen_splat(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_splat_node *n = splat_node(varnode);
|
|
// Generate code for the splat value directly
|
|
codegen(s, n->value, val);
|
|
}
|
|
|
|
static void
|
|
codegen_block_arg(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_block_arg_node *n = block_arg_node(varnode);
|
|
|
|
if (!n->value) {
|
|
int idx = lv_idx(s, MRB_OPSYM(and));
|
|
|
|
if (idx == 0) {
|
|
gen_getupvar(s, cursp(), MRB_OPSYM(and));
|
|
}
|
|
else {
|
|
gen_move(s, cursp(), idx, val);
|
|
}
|
|
if (val) push();
|
|
}
|
|
else {
|
|
codegen(s, n->value, val);
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_scope_node(codegen_scope *s, const node *varnode, int val)
|
|
{
|
|
struct mrb_ast_scope_node *scope = scope_node(varnode);
|
|
|
|
/* Pass locals and body directly to scope_body() */
|
|
scope_body(s, scope->locals, scope->body, val);
|
|
}
|
|
|
|
static void
|
|
codegen_begin(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_begin_node *begin = begin_node(varnode);
|
|
node *body = begin->body;
|
|
|
|
codegen(s, body, val);
|
|
}
|
|
|
|
static void
|
|
codegen_ensure(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_ensure_node *ensure = ensure_node(varnode);
|
|
node *body = ensure->body;
|
|
node *ensure_clause = ensure->ensure_clause;
|
|
|
|
if (!ensure_clause || !is_empty_stmts(ensure_clause)) {
|
|
int catch_entry, begin, end, target;
|
|
int idx;
|
|
|
|
catch_entry = catch_handler_new(s);
|
|
begin = s->pc;
|
|
codegen(s, body, val);
|
|
end = target = s->pc;
|
|
push();
|
|
idx = cursp();
|
|
genop_1(s, OP_EXCEPT, idx);
|
|
push();
|
|
codegen(s, ensure_clause, NOVAL);
|
|
pop();
|
|
genop_1(s, OP_RAISEIF, idx);
|
|
pop();
|
|
catch_handler_set(s, catch_entry, MRB_CATCH_ENSURE, begin, end, target);
|
|
}
|
|
else { /* empty ensure ignored */
|
|
codegen(s, body, val);
|
|
}
|
|
}
|
|
|
|
static void
|
|
codegen_stmts(codegen_scope *s, node *varnode, int val)
|
|
{
|
|
struct mrb_ast_stmts_node *stmts = stmts_node(varnode);
|
|
node *tree = stmts_node(stmts)->stmts;
|
|
|
|
if (val && !tree) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
while (tree) {
|
|
codegen(s, tree->car, tree->cdr ? NOVAL : val);
|
|
tree = tree->cdr;
|
|
}
|
|
}
|
|
|
|
static mrb_bool
|
|
is_empty_stmts(node *stmt_node)
|
|
{
|
|
if (!stmt_node) return TRUE;
|
|
|
|
if (node_type(stmt_node) == NODE_STMTS) {
|
|
/* Variable-sized NODE_STMTS with internal cons-list */
|
|
struct mrb_ast_stmts_node *stmts = (struct mrb_ast_stmts_node*)stmt_node;
|
|
return stmts->stmts == NULL;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Declaration codegen functions */
|
|
|
|
static void
|
|
codegen_alias(codegen_scope *s, const node *varnode, int val)
|
|
{
|
|
struct mrb_ast_alias_node *alias = alias_node(varnode);
|
|
|
|
int a = sym_idx(s, alias->new_name);
|
|
int b = sym_idx(s, alias->old_name);
|
|
|
|
genop_2(s, OP_ALIAS, a, b);
|
|
gen_load_nil(s, val);
|
|
}
|
|
|
|
static void
|
|
codegen_undef(codegen_scope *s, const node *varnode, int val)
|
|
{
|
|
struct mrb_ast_undef_node *undef = undef_node(varnode);
|
|
node *t = undef->syms;
|
|
|
|
while (t) {
|
|
int symbol = sym_idx(s, node_to_sym(t->car));
|
|
genop_1(s, OP_UNDEF, symbol);
|
|
t = t->cdr;
|
|
}
|
|
gen_load_nil(s, val);
|
|
}
|
|
|
|
static void
|
|
codegen_sdef(codegen_scope *s, const node *varnode, int val)
|
|
{
|
|
struct mrb_ast_sdef_node *sdef = sdef_node(varnode);
|
|
node *recv = sdef->obj;
|
|
int sym = sym_idx(s, sdef->name);
|
|
|
|
/* Call lambda_body directly with individual parameters */
|
|
/* For NODE_SDEF, args should contain the full locals structure from defs_setup */
|
|
int idx = lambda_body(s, sdef->locals, sdef->args, sdef->body, 0);
|
|
|
|
codegen(s, recv, VAL);
|
|
pop();
|
|
if (idx <= 0xff) {
|
|
/* SDEF fusion: SCLASS + METHOD + DEF -> SDEF */
|
|
genop_3(s, OP_SDEF, cursp(), sym, idx);
|
|
}
|
|
else {
|
|
genop_1(s, OP_SCLASS, cursp());
|
|
push();
|
|
genop_2(s, OP_METHOD, cursp(), idx);
|
|
push(); pop();
|
|
pop();
|
|
genop_2(s, OP_DEF, cursp(), sym);
|
|
}
|
|
if (val) push();
|
|
}
|
|
|
|
|
|
static void
|
|
codegen(codegen_scope *s, node *tree, int val)
|
|
{
|
|
int rlev = s->rlev;
|
|
|
|
if (!tree) {
|
|
if (val) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
push();
|
|
}
|
|
return;
|
|
}
|
|
|
|
s->rlev++;
|
|
if (s->rlev > MRB_CODEGEN_LEVEL_MAX) {
|
|
codegen_error(s, "too complex expression");
|
|
}
|
|
|
|
/* Check if this is a variable-sized node */
|
|
/* For variable-sized nodes, get filename/lineno from the variable node header */
|
|
struct mrb_ast_var_header *var_head = get_var_header(tree);
|
|
|
|
if (s->irep && s->filename_index != var_head->filename_index) {
|
|
mrb_sym fname = mrb_parser_get_filename(s->parser, s->filename_index);
|
|
const char *filename = mrb_sym_name_len(s->mrb, fname, NULL);
|
|
|
|
if (filename) {
|
|
mrb_debug_info_append_file(s->mrb, s->irep->debug_info,
|
|
filename, s->lines, s->debug_start_pos, s->pc);
|
|
}
|
|
s->debug_start_pos = s->pc;
|
|
s->filename_index = var_head->filename_index;
|
|
s->filename_sym = mrb_parser_get_filename(s->parser, var_head->filename_index);
|
|
}
|
|
s->lineno = var_head->lineno;
|
|
|
|
/* Process variable-sized node directly */
|
|
enum node_type var_type = (enum node_type)var_head->node_type;
|
|
|
|
switch (var_type) {
|
|
case NODE_INT:
|
|
if (val) {
|
|
gen_int(s, cursp(), int_node(tree)->value);
|
|
push();
|
|
}
|
|
break;
|
|
|
|
case NODE_BIGINT:
|
|
if (val) {
|
|
char *str = bigint_node(tree)->string;
|
|
int base = bigint_node(tree)->base;
|
|
int off = new_litbint(s, str, base);
|
|
genop_2(s, OP_LOADL, cursp(), off);
|
|
push();
|
|
}
|
|
break;
|
|
|
|
case NODE_SYM:
|
|
{
|
|
int i = sym_idx(s, sym_node(tree)->symbol);
|
|
gen_load_op2(s, OP_LOADSYM, i, val);
|
|
}
|
|
break;
|
|
|
|
case NODE_LVAR:
|
|
gen_lvar(s, var_node(tree)->symbol, val);
|
|
break;
|
|
|
|
case NODE_GVAR:
|
|
gen_xvar(s, var_node(tree)->symbol, val, OP_GETGV);
|
|
break;
|
|
|
|
case NODE_IVAR:
|
|
gen_xvar(s, var_node(tree)->symbol, val, OP_GETIV);
|
|
break;
|
|
|
|
case NODE_CVAR:
|
|
gen_xvar(s, var_node(tree)->symbol, val, OP_GETCV);
|
|
break;
|
|
|
|
case NODE_CALL:
|
|
codegen_call(s, tree, val);
|
|
break;
|
|
|
|
case NODE_ARRAY:
|
|
codegen_array(s, tree, val);
|
|
break;
|
|
|
|
case NODE_HASH:
|
|
codegen_hash(s, tree, val);
|
|
break;
|
|
|
|
case NODE_IF:
|
|
codegen_if(s, tree, val);
|
|
break;
|
|
|
|
case NODE_WHILE:
|
|
codegen_loop(s, tree, val, FALSE);
|
|
break;
|
|
|
|
case NODE_UNTIL:
|
|
codegen_loop(s, tree, val, TRUE);
|
|
break;
|
|
|
|
case NODE_FOR:
|
|
codegen_for(s, tree, val);
|
|
break;
|
|
|
|
case NODE_CASE:
|
|
codegen_case(s, tree, val);
|
|
break;
|
|
|
|
case NODE_CASE_MATCH:
|
|
codegen_case_match(s, tree, val);
|
|
break;
|
|
|
|
case NODE_MATCH_PAT:
|
|
{
|
|
/* One-line pattern matching: expr in pattern / expr => pattern */
|
|
struct mrb_ast_match_pat_node *mp = match_pat_node(tree);
|
|
int head;
|
|
int known_array_len = -1;
|
|
uint32_t fail_pos = JMPLINK_START;
|
|
|
|
/* Optimize: for simple variable pattern, generate value directly into variable */
|
|
if (node_type(mp->pattern) == NODE_PAT_VAR) {
|
|
struct mrb_ast_pat_var_node *pat_var = pat_var_node(mp->pattern);
|
|
if (pat_var->name) {
|
|
int idx = lv_idx(s, pat_var->name);
|
|
if (idx > 0) {
|
|
codegen(s, mp->value, VAL);
|
|
pop();
|
|
gen_move(s, idx, cursp(), 0); /* peephole optimizes LOADI+MOVE */
|
|
goto match_pat_push_result;
|
|
}
|
|
}
|
|
/* Wildcard pattern - just evaluate value for side effects */
|
|
codegen(s, mp->value, NOVAL);
|
|
match_pat_push_result:
|
|
if (val) {
|
|
/* 'in' pattern returns true, '=>' pattern returns nil */
|
|
if (mp->raise_on_fail) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADTRUE, cursp());
|
|
push();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Optimize: array literal => array pattern with matching sizes */
|
|
if (node_type(mp->value) == NODE_ARRAY &&
|
|
node_type(mp->pattern) == NODE_PAT_ARRAY) {
|
|
struct mrb_ast_pat_array_node *pat = pat_array_node(mp->pattern);
|
|
/* Only optimize for exact match (no rest, no post) */
|
|
if (pat->rest == 0 && pat->post == NULL) {
|
|
/* Count array elements (bail if splat present) and pattern pre elements */
|
|
int arr_len = array_literal_known_len(mp->value);
|
|
int pat_len = 0;
|
|
node *e;
|
|
for (e = pat->pre; e; e = e->cdr) pat_len++;
|
|
if (arr_len >= 0 && arr_len == pat_len) {
|
|
/* Sizes match - skip deconstruct and size check */
|
|
int arr_reg = cursp();
|
|
int i = 0;
|
|
codegen(s, mp->value, VAL); /* Generate array */
|
|
/* Extract elements directly with GETIDX */
|
|
for (e = pat->pre; e; e = e->cdr, i++) {
|
|
gen_move(s, cursp(), arr_reg, 0);
|
|
push();
|
|
gen_int(s, cursp(), i);
|
|
push();
|
|
genop_1(s, OP_GETIDX, cursp() - 2); /* R[a] = R[a][R[a+1]] */
|
|
pop();
|
|
/* Match element pattern (element is now at cursp()-1) */
|
|
codegen_pattern(s, e->car, cursp() - 1, &fail_pos, -1);
|
|
pop(); /* clean up array copy slot */
|
|
}
|
|
pop(); /* pop array */
|
|
if (fail_pos != JMPLINK_START) {
|
|
goto pattern_fail_handling;
|
|
}
|
|
/* Pattern always matches - push result if needed */
|
|
if (val) {
|
|
if (mp->raise_on_fail) {
|
|
gen_load_nil(s, 1); /* '=>' pattern returns nil */
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADTRUE, cursp()); /* 'in' pattern returns true */
|
|
push();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
head = cursp();
|
|
|
|
/* Check if value is array literal for optimization */
|
|
known_array_len = array_literal_known_len(mp->value);
|
|
|
|
/* Evaluate the value */
|
|
codegen(s, mp->value, VAL);
|
|
|
|
/* Generate pattern matching code */
|
|
codegen_pattern(s, mp->pattern, head, &fail_pos, known_array_len);
|
|
|
|
pattern_fail_handling:
|
|
if (fail_pos != JMPLINK_START) {
|
|
/* Pattern can fail - generate failure handling code */
|
|
uint32_t match_pos;
|
|
int saved_sp = cursp(); /* save stack pointer before branching */
|
|
|
|
/* Success path: pattern matched */
|
|
pop(); /* pop the value */
|
|
if (val) {
|
|
/* 'in' pattern returns true, '=>' pattern returns nil */
|
|
if (mp->raise_on_fail) {
|
|
gen_load_nil(s, 1);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADTRUE, cursp());
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* Optimize: single JMPNOT can be replaced with MATCHERR for raise_on_fail */
|
|
/* Conditions: (1) single entry in fail_pos chain,
|
|
* (2) JMPNOT is immediately before current position (no code between), and
|
|
* (3) the instruction is actually JMPNOT (not JMP from undefined pinned var) */
|
|
if ((int32_t)(fail_pos + 2) + (int16_t)PEEK_S(s->iseq+fail_pos) == 0 &&
|
|
fail_pos + 2 == s->pc &&
|
|
s->iseq[fail_pos - 2] == OP_JMPNOT) {
|
|
if (mp->raise_on_fail) {
|
|
/* Replace JMPNOT(BS,4bytes) with MATCHERR(B,2bytes)+NOP+NOP;
|
|
* keep the same size so that any jump targeting s->pc stays valid */
|
|
s->iseq[fail_pos - 2] = OP_MATCHERR;
|
|
/* fail_pos-1 already holds the register operand */
|
|
s->iseq[fail_pos] = OP_NOP;
|
|
s->iseq[fail_pos + 1] = OP_NOP;
|
|
s->sp = saved_sp - 1;
|
|
if (val) push();
|
|
break; /* Pattern matching complete */
|
|
}
|
|
/* Single failure point with 'in' pattern - invert JMPNOT to JMPIF */
|
|
s->iseq[fail_pos - 2] = OP_JMPIF;
|
|
match_pos = fail_pos;
|
|
}
|
|
else {
|
|
/* Multiple failure points - need JMP to skip error handling */
|
|
match_pos = genjmp(s, OP_JMP, JMPLINK_START);
|
|
dispatch_linked(s, fail_pos);
|
|
}
|
|
|
|
/* Failure path: restore stack pointer (value still on stack at runtime) */
|
|
s->sp = saved_sp;
|
|
pop(); /* pop the value */
|
|
if (mp->raise_on_fail) {
|
|
/* expr => pattern: raise NoMatchingPatternError */
|
|
genop_1(s, OP_LOADFALSE, cursp()); /* Load false for MATCHERR */
|
|
genop_1(s, OP_MATCHERR, cursp());
|
|
}
|
|
else {
|
|
/* expr in pattern: return false */
|
|
if (val) {
|
|
genop_1(s, OP_LOADFALSE, cursp());
|
|
push();
|
|
}
|
|
}
|
|
|
|
/* End of pattern matching */
|
|
dispatch(s, match_pos);
|
|
/* Restore sp to match success path value */
|
|
s->sp = saved_sp - 1;
|
|
if (val) push();
|
|
}
|
|
else {
|
|
/* Pattern always matches - pop value and push result if needed */
|
|
pop(); /* pop the value */
|
|
if (val) {
|
|
if (mp->raise_on_fail) {
|
|
gen_load_nil(s, 1); /* '=>' pattern returns nil */
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADTRUE, cursp()); /* 'in' pattern returns true */
|
|
push();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NODE_DEF:
|
|
codegen_def(s, tree, val);
|
|
break;
|
|
|
|
case NODE_CLASS:
|
|
codegen_class(s, tree, val);
|
|
break;
|
|
|
|
case NODE_MODULE:
|
|
codegen_module(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SCLASS:
|
|
codegen_sclass(s, tree, val);
|
|
break;
|
|
|
|
case NODE_ASGN:
|
|
codegen_asgn(s, tree, val);
|
|
break;
|
|
|
|
case NODE_MASGN:
|
|
codegen_masgn(s, tree, NULL, 0, val);
|
|
break;
|
|
|
|
case NODE_MARG:
|
|
/* Parameter destructuring should be handled inline by lambda_body */
|
|
/* This case should not be reached in normal execution */
|
|
break;
|
|
|
|
case NODE_OP_ASGN:
|
|
codegen_op_asgn(s, tree, val);
|
|
break;
|
|
|
|
case NODE_AND:
|
|
codegen_and(s, tree, val);
|
|
break;
|
|
|
|
case NODE_OR:
|
|
codegen_or(s, tree, val);
|
|
break;
|
|
|
|
case NODE_RETURN:
|
|
codegen_return(s, tree, val);
|
|
break;
|
|
|
|
case NODE_YIELD:
|
|
codegen_yield(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SUPER:
|
|
codegen_super(s, tree, val);
|
|
break;
|
|
|
|
case NODE_STR:
|
|
codegen_str(s, tree, val);
|
|
break;
|
|
|
|
case NODE_DOT2:
|
|
codegen_range(s, tree, val, FALSE);
|
|
break;
|
|
|
|
case NODE_DOT3:
|
|
codegen_range(s, tree, val, TRUE);
|
|
break;
|
|
|
|
case NODE_FLOAT:
|
|
codegen_float(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SELF:
|
|
codegen_self(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NIL:
|
|
codegen_nil(s, tree, val);
|
|
break;
|
|
|
|
case NODE_TRUE:
|
|
codegen_true(s, tree, val);
|
|
break;
|
|
|
|
case NODE_FALSE:
|
|
codegen_false(s, tree, val);
|
|
break;
|
|
|
|
case NODE_CONST:
|
|
codegen_const(s, tree, val);
|
|
break;
|
|
|
|
case NODE_RESCUE:
|
|
codegen_rescue(s, tree, val);
|
|
break;
|
|
|
|
case NODE_BLOCK:
|
|
codegen_block(s, tree, val);
|
|
break;
|
|
|
|
case NODE_BREAK:
|
|
codegen_break(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NEXT:
|
|
codegen_next(s, tree, val);
|
|
break;
|
|
|
|
case NODE_REDO:
|
|
codegen_redo(s, tree, val);
|
|
break;
|
|
|
|
case NODE_RETRY:
|
|
codegen_retry(s, tree, val);
|
|
break;
|
|
|
|
case NODE_WHILE_MOD:
|
|
codegen_loop_mod(s, tree, val, FALSE);
|
|
break;
|
|
|
|
case NODE_UNTIL_MOD:
|
|
codegen_loop_mod(s, tree, val, TRUE);
|
|
break;
|
|
|
|
case NODE_XSTR:
|
|
codegen_xstr(s, tree, val);
|
|
break;
|
|
|
|
case NODE_REGX:
|
|
codegen_regx(s, tree, val);
|
|
break;
|
|
|
|
case NODE_HEREDOC:
|
|
codegen_heredoc(s, tree, val);
|
|
break;
|
|
|
|
case NODE_DSYM:
|
|
codegen_dsym(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NTH_REF:
|
|
codegen_nth_ref(s, tree, val);
|
|
break;
|
|
|
|
case NODE_BACK_REF:
|
|
codegen_back_ref(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NVAR:
|
|
codegen_nvar(s, tree, val);
|
|
break;
|
|
|
|
case NODE_DVAR:
|
|
codegen_dvar(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NOT:
|
|
codegen_not(s, tree, val);
|
|
break;
|
|
|
|
case NODE_NEGATE:
|
|
codegen_negate(s, tree, val);
|
|
break;
|
|
|
|
case NODE_COLON2:
|
|
codegen_colon2(s, tree, val);
|
|
break;
|
|
|
|
case NODE_COLON3:
|
|
codegen_colon3(s, tree, val);
|
|
break;
|
|
|
|
case NODE_DEFINED:
|
|
codegen_defined(s, tree, val);
|
|
break;
|
|
|
|
case NODE_ZSUPER:
|
|
codegen_zsuper(s, tree, val);
|
|
break;
|
|
|
|
case NODE_LAMBDA:
|
|
codegen_lambda(s, tree, val);
|
|
break;
|
|
|
|
case NODE_WORDS:
|
|
codegen_words(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SYMBOLS:
|
|
codegen_symbols(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SPLAT:
|
|
codegen_splat(s, tree, val);
|
|
break;
|
|
|
|
case NODE_BLOCK_ARG:
|
|
codegen_block_arg(s, tree, val);
|
|
break;
|
|
|
|
case NODE_SCOPE:
|
|
codegen_scope_node(s, tree, val);
|
|
break;
|
|
|
|
case NODE_BEGIN:
|
|
codegen_begin(s, tree, val);
|
|
break;
|
|
|
|
case NODE_ENSURE:
|
|
codegen_ensure(s, tree, val);
|
|
break;
|
|
|
|
case NODE_STMTS:
|
|
codegen_stmts(s, tree, val);
|
|
break;
|
|
|
|
case NODE_ALIAS:
|
|
codegen_alias(s, tree, val);
|
|
break;
|
|
|
|
case NODE_UNDEF:
|
|
codegen_undef(s, tree, val);
|
|
break;
|
|
|
|
case NODE_POSTEXE:
|
|
{
|
|
struct mrb_ast_postexe_node *postexe = postexe_node(tree);
|
|
codegen(s, postexe->body, NOVAL);
|
|
}
|
|
break;
|
|
|
|
case NODE_SDEF:
|
|
codegen_sdef(s, tree, val);
|
|
break;
|
|
|
|
default:
|
|
/* Unhandled variable-sized node type - should not occur with current AST */
|
|
break;
|
|
}
|
|
s->rlev = rlev;
|
|
}
|
|
|
|
static void
|
|
scope_add_irep(codegen_scope *s)
|
|
{
|
|
mrb_irep *irep;
|
|
codegen_scope *prev = s->prev;
|
|
|
|
if (prev->irep == NULL) {
|
|
irep = mrb_add_irep(s->mrb);
|
|
prev->irep = s->irep = irep;
|
|
return;
|
|
}
|
|
else {
|
|
if (prev->irep->rlen == UINT16_MAX) {
|
|
codegen_error(s, "too many nested blocks/methods");
|
|
}
|
|
s->irep = irep = mrb_add_irep(s->mrb);
|
|
if (prev->irep->rlen == prev->rcapa) {
|
|
prev->rcapa *= 2;
|
|
prev->reps = (mrb_irep**)mrbc_realloc(prev->reps, sizeof(mrb_irep*)*prev->rcapa);
|
|
}
|
|
prev->reps[prev->irep->rlen] = irep;
|
|
prev->irep->rlen++;
|
|
}
|
|
}
|
|
|
|
static codegen_scope*
|
|
scope_new(mrb_state *mrb, codegen_scope *prev, node *nlv)
|
|
{
|
|
static const codegen_scope codegen_scope_zero = { 0 };
|
|
mempool *pool = mempool_open();
|
|
codegen_scope *s = (codegen_scope*)mempool_alloc(pool, sizeof(codegen_scope));
|
|
|
|
if (!s) {
|
|
if (prev)
|
|
codegen_error(prev, "unexpected scope");
|
|
return NULL;
|
|
}
|
|
*s = codegen_scope_zero;
|
|
s->mrb = mrb;
|
|
s->mpool = pool;
|
|
if (!prev) return s;
|
|
s->prev = prev;
|
|
s->ainfo = 0;
|
|
s->mscope = 0;
|
|
|
|
scope_add_irep(s);
|
|
|
|
s->rcapa = 8;
|
|
s->reps = (mrb_irep**)mrbc_malloc(sizeof(mrb_irep*)*s->rcapa);
|
|
|
|
s->icapa = 1024;
|
|
s->iseq = (mrb_code*)mrbc_malloc(sizeof(mrb_code)*s->icapa);
|
|
|
|
s->pcapa = 32;
|
|
s->pool = (mrb_irep_pool*)mrbc_malloc(sizeof(mrb_irep_pool)*s->pcapa);
|
|
|
|
s->scapa = 256;
|
|
s->syms = (mrb_sym*)mrbc_malloc(sizeof(mrb_sym)*s->scapa);
|
|
|
|
s->lv = nlv;
|
|
s->sp += (nlv ? node_len(nlv) : 0) + 1; /* add self */
|
|
s->nlocals = s->nregs = s->sp;
|
|
if (nlv) {
|
|
mrb_sym *lv;
|
|
node *n = nlv;
|
|
size_t i = 0;
|
|
|
|
s->irep->lv = lv = (mrb_sym*)mrbc_malloc(sizeof(mrb_sym)*(s->nlocals-1));
|
|
for (i=0, n=nlv; n; i++,n=n->cdr) {
|
|
lv[i] = lv_name(n);
|
|
}
|
|
mrb_assert(i + 1 == s->nlocals);
|
|
}
|
|
s->ai = mrb_gc_arena_save(mrb);
|
|
|
|
s->filename_sym = prev->filename_sym;
|
|
if (s->filename_sym) {
|
|
s->lines = (uint16_t*)mrbc_malloc(sizeof(short)*s->icapa);
|
|
}
|
|
s->lineno = prev->lineno;
|
|
|
|
/* debug setting */
|
|
s->debug_start_pos = 0;
|
|
if (s->filename_sym) {
|
|
mrb_debug_info_alloc(mrb, s->irep);
|
|
}
|
|
else {
|
|
s->irep->debug_info = NULL;
|
|
}
|
|
s->parser = prev->parser;
|
|
s->filename_index = prev->filename_index;
|
|
|
|
s->rlev = prev->rlev+1;
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
scope_finish(codegen_scope *s)
|
|
{
|
|
mrb_state *mrb = s->mrb;
|
|
mrb_irep *irep = s->irep;
|
|
|
|
if (s->nlocals > 0xff) {
|
|
codegen_error(s, "too many local variables");
|
|
}
|
|
irep->flags = 0;
|
|
if (s->iseq) {
|
|
size_t catchsize = sizeof(struct mrb_irep_catch_handler) * irep->clen;
|
|
irep->iseq = (const mrb_code*)mrbc_realloc(s->iseq, sizeof(mrb_code)*s->pc + catchsize);
|
|
irep->ilen = s->pc;
|
|
if (irep->clen > 0) {
|
|
memcpy((void*)(irep->iseq + irep->ilen), s->catch_table, catchsize);
|
|
}
|
|
}
|
|
else {
|
|
irep->clen = 0;
|
|
}
|
|
mrbc_free(s->catch_table);
|
|
s->catch_table = NULL;
|
|
irep->pool = (const mrb_irep_pool*)mrbc_realloc(s->pool, sizeof(mrb_irep_pool)*irep->plen);
|
|
irep->syms = (const mrb_sym*)mrbc_realloc(s->syms, sizeof(mrb_sym)*irep->slen);
|
|
irep->reps = (const mrb_irep**)mrbc_realloc(s->reps, sizeof(mrb_irep*)*irep->rlen);
|
|
if (s->filename_sym) {
|
|
mrb_sym fname = mrb_parser_get_filename(s->parser, s->filename_index);
|
|
const char *filename = mrb_sym_name_len(s->mrb, fname, NULL);
|
|
|
|
mrb_debug_info_append_file(s->mrb, s->irep->debug_info,
|
|
filename, s->lines, s->debug_start_pos, s->pc);
|
|
}
|
|
mrbc_free(s->lines);
|
|
|
|
irep->nlocals = s->nlocals;
|
|
irep->nregs = s->nregs;
|
|
|
|
mrb_gc_arena_restore(mrb, s->ai);
|
|
mempool_close(s->mpool);
|
|
}
|
|
|
|
static struct loopinfo*
|
|
loop_push(codegen_scope *s, enum looptype t)
|
|
{
|
|
struct loopinfo *p = (struct loopinfo*)codegen_palloc(s, sizeof(struct loopinfo));
|
|
|
|
p->type = t;
|
|
p->pc0 = p->pc1 = p->pc2 = JMPLINK_START;
|
|
p->prev = s->loop;
|
|
p->reg = cursp();
|
|
s->loop = p;
|
|
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
loop_break(codegen_scope *s, node *tree)
|
|
{
|
|
if (!s->loop) {
|
|
codegen(s, tree, NOVAL);
|
|
raise_error(s, "unexpected break");
|
|
}
|
|
else {
|
|
struct loopinfo *loop;
|
|
|
|
loop = s->loop;
|
|
if (tree) {
|
|
if (loop->reg < 0) {
|
|
codegen(s, tree, NOVAL);
|
|
}
|
|
else {
|
|
gen_retval(s, tree);
|
|
}
|
|
}
|
|
while (loop) {
|
|
if (loop->type == LOOP_BEGIN) {
|
|
loop = loop->prev;
|
|
}
|
|
else if (loop->type == LOOP_RESCUE) {
|
|
loop = loop->prev;
|
|
}
|
|
else{
|
|
break;
|
|
}
|
|
}
|
|
if (!loop) {
|
|
raise_error(s, "unexpected break");
|
|
return;
|
|
}
|
|
|
|
if (loop->type == LOOP_NORMAL) {
|
|
int tmp;
|
|
|
|
if (loop->reg >= 0) {
|
|
if (tree) {
|
|
gen_move(s, loop->reg, cursp(), 0);
|
|
}
|
|
else {
|
|
genop_1(s, OP_LOADNIL, loop->reg);
|
|
}
|
|
}
|
|
tmp = genjmp(s, OP_JMPUW, loop->pc2);
|
|
loop->pc2 = tmp;
|
|
}
|
|
else {
|
|
if (!tree) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
gen_return(s, OP_BREAK, cursp());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
loop_pop(codegen_scope *s, int val)
|
|
{
|
|
if (val) {
|
|
genop_1(s, OP_LOADNIL, cursp());
|
|
}
|
|
dispatch_linked(s, s->loop->pc2);
|
|
s->loop = s->loop->prev;
|
|
if (val) push();
|
|
}
|
|
|
|
static int
|
|
catch_handler_new(codegen_scope *s)
|
|
{
|
|
size_t newsize = sizeof(struct mrb_irep_catch_handler) * (s->irep->clen + 1);
|
|
s->catch_table = (struct mrb_irep_catch_handler*)mrbc_realloc((void*)s->catch_table, newsize);
|
|
return s->irep->clen++;
|
|
}
|
|
|
|
static void
|
|
catch_handler_set(codegen_scope *s, int ent, enum mrb_catch_type type, uint32_t begin, uint32_t end, uint32_t target)
|
|
{
|
|
struct mrb_irep_catch_handler *e;
|
|
|
|
mrb_assert(ent >= 0 && ent < s->irep->clen);
|
|
|
|
e = &s->catch_table[ent];
|
|
uint8_to_bin(type, &e->type);
|
|
mrb_irep_catch_handler_pack(begin, e->begin);
|
|
mrb_irep_catch_handler_pack(end, e->end);
|
|
mrb_irep_catch_handler_pack(target, e->target);
|
|
}
|
|
|
|
static struct RProc*
|
|
generate_code(mrb_state *mrb, parser_state *p, int val)
|
|
{
|
|
codegen_scope *scope = scope_new(mrb, 0, 0);
|
|
struct mrb_jmpbuf *prev_jmp = mrb->jmp;
|
|
struct mrb_jmpbuf jmpbuf;
|
|
struct RProc *proc;
|
|
|
|
mrb->jmp = &jmpbuf;
|
|
|
|
scope->mrb = mrb;
|
|
scope->parser = p;
|
|
scope->filename_sym = p->filename_sym;
|
|
scope->filename_index = p->current_filename_index;
|
|
|
|
MRB_TRY(mrb->jmp) {
|
|
/* prepare irep */
|
|
codegen(scope, p->tree, val);
|
|
proc = mrb_proc_new(mrb, scope->irep);
|
|
mrb_irep_decref(mrb, scope->irep);
|
|
mempool_close(scope->mpool);
|
|
proc->c = NULL;
|
|
if (mrb->c->cibase && mrb->c->cibase->proc == proc->upper) {
|
|
proc->upper = NULL;
|
|
}
|
|
mrb->jmp = prev_jmp;
|
|
return proc;
|
|
}
|
|
MRB_CATCH(mrb->jmp) {
|
|
mrb_irep_decref(mrb, scope->irep);
|
|
mempool_close(scope->mpool);
|
|
mrb->jmp = prev_jmp;
|
|
return NULL;
|
|
}
|
|
MRB_END_EXC(mrb->jmp);
|
|
}
|
|
|
|
|
|
MRB_API struct RProc*
|
|
mrb_generate_code(mrb_state *mrb, parser_state *p)
|
|
{
|
|
return generate_code(mrb, p, p->no_return_value ? NOVAL : VAL);
|
|
}
|