Files
mruby-mruby/mrbgems/mruby-compiler/core/codegen.c
T
Yukihiro "Matz" Matsumoto dccd66f9ef Support Ruby3.0 keyword arguments.
The Difference

Since Ruby1.9, the keyword arguments were emulated by Ruby using the hash
object at the bottom of the arguments. But we have gradually moved toward
keyword arguments separated from normal (positinal) arguments.

At the same time, we value compatibility, so that Ruby3.0 keyword
arguments are somewhat compromise. Basically, keyword arguments are
separated from positional arguments, except when the method does not
take any formal keyword arguments, given keyword arguments (packed
in the hash object) are considered as the last argument.

And we also allow non symbol keys in the keyword arguments. In that
case, those keys are just passed in the `**` hash (or raise
`ArgumentError` for unknown keys).

The Instruction Changes

We have changed `OP_SEND` instruction. `OP_SEND` instruction used to
take 3 operands, the register, the symbol, the number of (positional)
arguments. The meaning of the third operand has been changed. It is now
considered as `n|(nk<<4)`, where `n` is the number of positional
arguments, and `nk` is the number of keyword arguments, both occupies
4 bits in the operand.

The number `15` in both `n` and `nk` means variable sized arguments are
packed in the object. Positional arguments will be packed in the array,
and keyword arguments will be packed in the hash object. That means
arguments more than 14 values are always packed in the object.

Arguments information for other instructions (`OP_SENDB` and `OP_SUPER`)
are also changed. It works as the third operand of `OP_SEND`. the
difference between `OP_SEND` and `OP_SENDB` is just trivial. It assigns
`nil` to the block hidden arguments (right after arguments).

The instruction `OP_SENDV` and `OP_SENDVB` are removed. Those
instructions are replaced by `OP_SEND` and `OP_SENDB` respectively with
the `15` (variable sized) argument information.

Calling Convention

When calling a method, the stack elements shall be in the order of the
receiver of the method, positional arguments, keyword arguments and the
block argument. If the number of positional or keyword arugument (`n` or
`nk`) is zero, corresponding arguments will be empty. So when `n=0` and
`nk=0` the stack layout (from bottom to top) will be:

+-----------------------+
| recv | block (or nil) |
+-----------------------+

The last elements `block` should be explicitly filled before `OP_SEND`
or assigned to `nil` by `OP_SENDB` internally. In other words, the
following have exactly same behavior:

OP_SENDB clears `block` implicitly:

```
OP_SENDB reg sym 0
```

OP_SEND clears `block` implicitly:

```
OP_LOADNIL  R2
OP_SEND     R2 sym 0
```

When calling a method with only positional arguments (n=0..14) without
keyword arguments, the stack layout will be like following:

+--------------------------------------------+
| recv | arg1 | ... | arg_n | block (or nil) |
+--------------------------------------------+

When calling a method with arguments packed in the array (n=15) which
means argument splat (*) is used in the actual arguments, or more than
14 arguments are passed the stack layout will be like following:

+-------------------------------+
| recv | array | block (or nil) |
+-------------------------------+

The number of the actual arguments is determined by the length of the
argument array.

When keyword arguments are given (nk>0), keyword arguments are passed
between positional arguments and the block argument. For example, when
we pass one positional argument `1` and one keyword argument `a: 2`,
the stack layout will be like:

+------------------------------------+
| recv | 1 | :a | 2 | block (or nil) |
+------------------------------------+

Note that keyword arguments consume `2*nk` elements in the stack when
`nk=0..14` (unpacked).

When calling a method with keyword arguments packed in the hash object
(nk=15) which means keyword argument splat (**) is used or more than
14 keyword arguments in the actual arguments, the stack layout will
be like:

+------------------------------+
| recv | hash | block (or nil) |
+------------------------------+

Note for mruby/c

When mruby/c authors try to support new keyword arguments, they need
to handle the new meaning of the argument information operand. If they
choose not to support keyword arguments in mruby/c, it just raise
error when `nk` (taken by `(c>>4)&0xf`) is not zero. And combine
`OP_SENDV` behavior with `OP_SEND` when `n` is `15`.

If they want to support keyword arguments seriously, contact me at
<matz@ruby.or.jp> or `@yukihiro_matz`. I can help you.
2021-10-12 20:16:36 +09:00

3876 lines
86 KiB
C

/*
** codegen.c - mruby code generator
**
** See Copyright Notice in mruby.h
*/
#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 <mruby/presym.h>
#include "node.h"
#include <mruby/opcode.h>
#include <mruby/re.h>
#include <mruby/throw.h>
#include <ctype.h>
#include <string.h>
#ifndef MRB_CODEGEN_LEVEL_MAX
#define MRB_CODEGEN_LEVEL_MAX 1024
#endif
#define MAXARG_S (1<<16)
typedef mrb_ast_node node;
typedef struct mrb_parser_state parser_state;
enum looptype {
LOOP_NORMAL,
LOOP_BLOCK,
LOOP_FOR,
LOOP_BEGIN,
LOOP_RESCUE,
};
struct loopinfo {
enum looptype type;
uint32_t pc0; /* `next` destination */
uint32_t pc1; /* `redo` destination */
uint32_t pc2; /* `break` destination */
int reg; /* destination register */
struct loopinfo *prev;
};
typedef struct scope {
mrb_state *mrb;
mrb_pool *mpool;
struct scope *prev;
node *lv;
uint16_t sp;
uint32_t pc;
uint32_t lastpc;
uint32_t lastlabel;
size_t ainfo:15;
mrb_bool mscope:1;
struct loopinfo *loop;
mrb_sym filename_sym;
uint16_t lineno;
mrb_code *iseq;
uint16_t *lines;
uint32_t icapa;
mrb_irep *irep;
mrb_pool_value *pool;
mrb_sym *syms;
mrb_irep **reps;
struct mrb_irep_catch_handler *catch_table;
uint32_t pcapa, scapa, rcapa;
uint16_t nlocals;
uint16_t nregs;
int ai;
int debug_start_pos;
uint16_t filename_index;
parser_state* parser;
int rlev; /* recursion levels */
} 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_assignment(codegen_scope *s, node *tree, int sp, int val);
static void gen_vmassignment(codegen_scope *s, node *tree, int rhs, int val);
static void codegen(codegen_scope *s, node *tree, int val);
static void raise_error(codegen_scope *s, const char *msg);
static void
codegen_error(codegen_scope *s, const char *message)
{
if (!s) return;
while (s->prev) {
codegen_scope *tmp = s->prev;
if (s->irep) {
mrb_free(s->mrb, s->iseq);
for (int i=0; i<s->irep->plen; i++) {
mrb_pool_value *pv = &s->pool[i];
if ((pv->tt & 0x3) == IREP_TT_STR || pv->tt == IREP_TT_BIGINT) {
mrb_free(s->mrb, (void*)pv->u.str);
}
}
mrb_free(s->mrb, s->pool);
mrb_free(s->mrb, s->syms);
mrb_free(s->mrb, 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]);
}
mrb_free(s->mrb, s->reps);
}
mrb_free(s->mrb, s->lines);
}
mrb_pool_close(s->mpool);
s = tmp;
}
#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
MRB_THROW(s->mrb->jmp);
}
static void*
codegen_palloc(codegen_scope *s, size_t len)
{
void *p = mrb_pool_alloc(s->mpool, len);
if (!p) codegen_error(s, "pool memory allocation");
return p;
}
static void*
codegen_realloc(codegen_scope *s, void *p, size_t len)
{
p = mrb_realloc_simple(s->mrb, p, len);
if (!p && len > 0) codegen_error(s, "mrb_realloc");
return p;
}
static int
new_label(codegen_scope *s)
{
return s->lastlabel = s->pc;
}
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 *)codegen_realloc(s, s->iseq, sizeof(mrb_code)*s->icapa);
if (s->lines) {
s->lines = (uint16_t*)codegen_realloc(s, 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;
}
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);
}
static void
gen_B(codegen_scope *s, uint8_t i)
{
emit_B(s, s->pc, i);
s->pc++;
}
static void
gen_S(codegen_scope *s, uint16_t i)
{
emit_S(s, s->pc, i);
s->pc += 2;
}
static void
genop_0(codegen_scope *s, mrb_code i)
{
s->lastpc = s->pc;
gen_B(s, i);
}
static void
genop_1(codegen_scope *s, mrb_code i, uint16_t a)
{
s->lastpc = s->pc;
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);
}
}
static void
genop_2(codegen_scope *s, mrb_code i, uint16_t a, uint16_t b)
{
s->lastpc = s->pc;
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);
}
}
static void
genop_3(codegen_scope *s, mrb_code i, uint16_t a, uint16_t b, uint8_t c)
{
genop_2(s, i, a, b);
gen_B(s, c);
}
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);
}
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);
}
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);
}
#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;
}
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
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;
while (i<pc) {
uint8_t insn = i[0];
prev_pc = i;
switch (insn) {
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[insn];
break;
}
}
return prev_pc;
}
#define pc_addr(s) &((s)->iseq[(s)->pc])
#define addr_pc(s, addr) (uint32_t)((addr) - s->iseq)
#define rewind_pc(s) s->pc = s->lastpc
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]);
}
static mrb_bool
no_peephole(codegen_scope *s)
{
return no_optimize(s) || s->lastlabel == s->pc || s->pc == 0 || s->pc == s->lastpc;
}
#define JMPLINK_START UINT32_MAX
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);
}
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)
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_LOADF:
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_LOADT: case OP_LOADI: 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) {
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);
static void
gen_move(codegen_scope *s, uint16_t dst, uint16_t src, int nopeep)
{
if (nopeep || no_peephole(s)) goto normal;
else {
struct mrb_insn_data data = mrb_last_insn(s);
switch (data.insn) {
case OP_MOVE:
if (dst == src) return; /* remove useless MOVE */
if (data.b == dst && data.a == src) /* skip swapping MOVE */
return;
goto normal;
case OP_LOADNIL: case OP_LOADSELF: case OP_LOADT: case OP_LOADF:
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) goto normal;
rewind_pc(s);
genop_1(s, data.insn, dst);
return;
case OP_HASH: case OP_ARRAY:
if (data.b != 0) goto normal;
/* fall through */
case OP_LOADI: 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) goto normal;
rewind_pc(s);
genop_2(s, data.insn, dst, data.b);
return;
case OP_LOADI16:
if (data.a != src || data.a < s->nlocals) goto normal;
rewind_pc(s);
genop_2S(s, data.insn, dst, data.b);
return;
case OP_LOADI32:
if (data.a != src || data.a < s->nlocals) goto normal;
else {
uint32_t i = (uint32_t)data.b<<16|data.c;
rewind_pc(s);
genop_2SS(s, data.insn, dst, i);
}
return;
case OP_AREF:
case OP_GETUPVAR:
if (data.a != src || data.a < s->nlocals) goto normal;
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) goto normal;
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) goto normal;
s->pc = addr_pc(s, data0.addr);
if (addr_pc(s, data0.addr) != s->lastlabel) {
/* constant folding */
data0 = mrb_decode_insn(mrb_prev_pc(s, data0.addr));
mrb_int n;
if (data0.a == dst && get_int_operand(s, &data0, &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, data0.addr);
gen_int(s, dst, n);
return;
}
}
}
}
genop_2(s, data.insn, dst, data.b);
return;
default:
break;
}
}
normal:
genop_2(s, OP_MOVE, dst, src);
return;
}
static int search_upvar(codegen_scope *s, mrb_sym id, int *idx);
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);
}
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);
}
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_RETURN) {
genop_1(s, op, src);
}
}
}
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_LOADI:
case OP_LOADI16:
*n = data->b;
return TRUE;
case OP_LOADI32:
*n = (mrb_int)((uint32_t)data->b<<16)+data->c;
return TRUE;
case OP_LOADL:
{
mrb_pool_value *pv = &s->pool[data->b];
if (pv->tt == IREP_TT_INT32) {
*n = (mrb_int)pv->u.i32;
}
#ifdef MRB_INT64
else if (pv->tt == IREP_TT_INT64) {
*n = (mrb_int)pv->u.i64;
}
#endif
else {
return FALSE;
}
}
return TRUE;
default:
return FALSE;
}
}
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 */
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)) {
/* OP_ADDI/OP_SUBI takes upto 16bits */
if (n > INT16_MAX) goto normal;
rewind_pc(s);
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);
}
}
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) || n == 0) {
goto normal;
}
if (op == OP_MUL) {
if (mrb_int_mul_overflow(n0, n, &n)) goto normal;
}
else { /* OP_DIV */
if (n0 == MRB_INT_MIN && n == -1) goto normal;
n = 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);
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_2(s->mrb, aref)) {
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_2(s->mrb, lshift)) {
if (!mrb_num_shift(s->mrb, n0, n, &n)) return FALSE;
}
else if (op == MRB_OPSYM_2(s->mrb, rshift)) {
if (n == MRB_INT_MIN) return FALSE;
if (!mrb_num_shift(s->mrb, n0, -n, &n)) return FALSE;
}
else if (op == MRB_OPSYM_2(s->mrb, 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_2(s->mrb, and)) {
n = n0 & n;
}
else if (op == MRB_OPSYM_2(s->mrb, or)) {
n = n0 | n;
}
else if (op == MRB_OPSYM_2(s->mrb, xor)) {
n = n0 ^ n;
}
else {
return FALSE;
}
s->pc = addr_pc(s, data0.addr);
gen_int(s, dst, n);
return TRUE;
}
}
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;
}
static void
dispatch_linked(codegen_scope *s, uint32_t pos)
{
if (pos==JMPLINK_START) return;
for (;;) {
pos = dispatch(s, pos);
if (pos==0) break;
}
}
#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;
}
#define push() push_n_(s,1)
#define push_n(n) push_n_(s,n)
#define pop() pop_n_(s,1)
#define pop_n(n) pop_n_(s,n)
#define cursp() (s->sp)
static int
new_litbn(codegen_scope *s, const char *p, int base, mrb_bool neg)
{
int i;
size_t plen;
mrb_pool_value *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;
}
if (s->irep->plen == s->pcapa) {
s->pcapa *= 2;
s->pool = (mrb_pool_value*)codegen_realloc(s, s->pool, sizeof(mrb_pool_value)*s->pcapa);
}
pv = &s->pool[s->irep->plen];
i = s->irep->plen++;
{
char *buf;
pv->tt = IREP_TT_BIGINT;
buf = (char*)codegen_realloc(s, NULL, plen+3);
buf[0] = (char)plen;
buf[1] = base;
if (neg) buf[1] = 0x80;
memcpy(buf+2, p, plen);
buf[plen+2] = '\0';
pv->u.str = buf;
}
return i;
}
static int
new_lit(codegen_scope *s, mrb_value val)
{
int i;
mrb_pool_value *pv;
switch (mrb_type(val)) {
case MRB_TT_STRING:
for (i=0; i<s->irep->plen; i++) {
mrb_int len;
pv = &s->pool[i];
if (pv->tt & IREP_TT_NFLAG) continue;
len = pv->tt>>2;
if (RSTRING_LEN(val) != len) continue;
if (memcmp(pv->u.str, RSTRING_PTR(val), len) == 0)
return i;
}
break;
#ifndef MRB_NO_FLOAT
case MRB_TT_FLOAT:
for (i=0; i<s->irep->plen; i++) {
mrb_float f1, f2;
pv = &s->pool[i];
if (pv->tt != IREP_TT_FLOAT) continue;
pv = &s->pool[i];
f1 = pv->u.f;
f2 = mrb_float(val);
if (f1 == f2 && !signbit(f1) == !signbit(f2)) return i;
}
break;
#endif
case MRB_TT_INTEGER:
for (i=0; i<s->irep->plen; i++) {
mrb_int v = mrb_integer(val);
pv = &s->pool[i];
if (pv->tt == IREP_TT_INT32) {
if (v == pv->u.i32) return i;
}
#ifdef MRB_64BIT
else if (pv->tt == IREP_TT_INT64) {
if (v == pv->u.i64) return i;
}
continue;
#endif
}
break;
default:
/* should not happen */
return 0;
}
if (s->irep->plen == s->pcapa) {
s->pcapa *= 2;
s->pool = (mrb_pool_value*)codegen_realloc(s, s->pool, sizeof(mrb_pool_value)*s->pcapa);
}
pv = &s->pool[s->irep->plen];
i = s->irep->plen++;
switch (mrb_type(val)) {
case MRB_TT_STRING:
if (RSTR_NOFREE_P(RSTRING(val))) {
pv->tt = (uint32_t)(RSTRING_LEN(val)<<2) | IREP_TT_SSTR;
pv->u.str = RSTRING_PTR(val);
}
else {
char *p;
mrb_int len = RSTRING_LEN(val);
pv->tt = (uint32_t)(len<<2) | IREP_TT_STR;
p = (char*)codegen_realloc(s, NULL, len+1);
memcpy(p, RSTRING_PTR(val), len);
p[len] = '\0';
pv->u.str = p;
}
break;
#ifndef MRB_NO_FLOAT
case MRB_TT_FLOAT:
pv->tt = IREP_TT_FLOAT;
pv->u.f = mrb_float(val);
break;
#endif
case MRB_TT_INTEGER:
#ifdef MRB_INT64
pv->tt = IREP_TT_INT64;
pv->u.i64 = mrb_integer(val);
#else
pv->tt = IREP_TT_INT32;
pv->u.i32 = mrb_integer(val);
#endif
break;
default:
/* should not happen */
break;
}
return i;
}
static int
new_sym(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*)codegen_realloc(s, s->syms, sizeof(mrb_sym)*s->scapa);
}
s->syms[s->irep->slen] = sym;
return s->irep->slen++;
}
static void
gen_setxv(codegen_scope *s, uint8_t op, uint16_t dst, mrb_sym sym, int val)
{
int idx = new_sym(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);
}
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_LOADI, 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(s, mrb_int_value(s->mrb, i)));
}
}
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_2(s->mrb, plus)) {
/* unary plus does nothing */
}
else if (sym == MRB_OPSYM_2(s->mrb, minus)) {
if (n == MRB_INT_MIN) return FALSE;
n = -n;
}
else if (sym == MRB_OPSYM_2(s->mrb, neg)) {
n = ~n;
}
else {
return FALSE;
}
s->pc = addr_pc(s, data.addr);
gen_int(s, dst, n);
return TRUE;
}
static int
node_len(node *tree)
{
int n = 0;
while (tree) {
n++;
tree = tree->cdr;
}
return n;
}
#define nint(x) ((int)(intptr_t)(x))
#define nchar(x) ((char)(intptr_t)(x))
#define nsym(x) ((mrb_sym)(intptr_t)(x))
#define lv_name(lv) nsym((lv)->car)
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 ++;
}
codegen_error(s, "Can't found local variables");
return -1; /* not reached */
}
static void
for_body(codegen_scope *s, node *tree)
{
codegen_scope *prev = s;
int idx;
struct loopinfo *lp;
node *n2;
/* generate receiver */
codegen(s, tree->cdr->car, VAL);
/* generate loop-block */
s = scope_new(s->mrb, s, NULL);
push(); /* push for a block parameter */
/* generate loop variable */
n2 = tree->car;
genop_W(s, OP_ENTER, 0x40000);
if (n2->car && !n2->car->cdr && !n2->cdr) {
gen_assignment(s, n2->car->car, 1, NOVAL);
}
else {
gen_vmassignment(s, n2, 1, VAL);
}
/* construct loop */
lp = loop_push(s, LOOP_FOR);
lp->pc1 = new_label(s);
/* loop body */
codegen(s, tree->cdr->cdr->car, 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 = new_sym(s, MRB_SYM_2(s->mrb, each));
genop_3(s, OP_SENDB, cursp(), idx, 0);
}
static int
lambda_body(codegen_scope *s, node *tree, int blk)
{
codegen_scope *parent = s;
s = scope_new(s->mrb, s, tree->car);
s->mscope = !blk;
if (blk) {
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
lp->pc0 = new_label(s);
}
tree = tree->cdr;
if (tree->car == NULL) {
genop_W(s, OP_ENTER, 0);
s->ainfo = 0;
}
else {
mrb_aspec a;
int ma, oa, ra, pa, ka, kd, ba, i;
uint32_t pos;
node *opt;
node *margs, *pargs;
node *tail;
/* mandatory arguments */
ma = node_len(tree->car->car);
margs = tree->car->car;
tail = tree->car->cdr->cdr->cdr->cdr;
/* optional arguments */
oa = node_len(tree->car->cdr->car);
/* rest argument? */
ra = tree->car->cdr->cdr->car ? 1 : 0;
/* mandatory arguments after rest argument */
pa = node_len(tree->car->cdr->cdr->cdr->car);
pargs = tree->car->cdr->cdr->cdr->car;
/* keyword arguments */
ka = tail? node_len(tail->cdr->car) : 0;
/* keyword dictionary? */
kd = tail && tail->cdr->cdr->car? 1 : 0;
/* block argument? */
ba = tail && tail->cdr->cdr->cdr->car ? 1 : 0;
if (ma > 0x1f || oa > 0x1f || pa > 0x1f || ka > 0x1f) {
codegen_error(s, "too many formal arguments");
}
a = 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);
s->ainfo = (((ma+oa) & 0x3f) << 7) /* (12bits = 5:1:5:1) */
| ((ra & 0x1) << 6)
| ((pa & 0x1f) << 1)
| ((ka | kd) != 0 ? 0x01 : 0x00);
genop_W(s, OP_ENTER, a);
/* generate jump table for optional arguments initializer */
pos = new_label(s);
for (i=0; i<oa; i++) {
new_label(s);
genjmp_0(s, OP_JMP);
}
if (oa > 0) {
genjmp_0(s, OP_JMP);
}
opt = tree->car->cdr->car;
i = 0;
while (opt) {
int idx;
mrb_sym id = nsym(opt->car->car);
dispatch(s, pos+i*3+1);
codegen(s, opt->car->cdr, VAL);
pop();
idx = lv_idx(s, id);
if (idx > 0) {
gen_move(s, idx, cursp(), 0);
}
else {
gen_getupvar(s, cursp(), id);
}
i++;
opt = opt->cdr;
}
if (oa > 0) {
dispatch(s, pos+i*3+1);
}
/* keyword arguments */
if (tail) {
node *kwds = tail->cdr->car;
int kwrest = 0;
if (tail->cdr->cdr->car) {
kwrest = 1;
}
mrb_assert(nint(tail->car) == NODE_ARGS_TAIL);
mrb_assert(node_len(tail) == 4);
while (kwds) {
int jmpif_key_p, jmp_def_set = -1;
node *kwd = kwds->car, *def_arg = kwd->cdr->cdr->car;
mrb_sym kwd_sym = nsym(kwd->cdr->car);
mrb_assert(nint(kwd->car) == NODE_KW_ARG);
if (def_arg) {
int idx;
genop_2(s, OP_KEY_P, lv_idx(s, kwd_sym), new_sym(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), new_sym(s, kwd_sym));
if (jmp_def_set != -1) {
dispatch(s, jmp_def_set);
}
i++;
kwds = kwds->cdr;
}
if (tail->cdr->car && !kwrest) {
genop_0(s, OP_KEYEND);
}
}
/* argument destructuring */
if (margs) {
node *n = margs;
pos = 1;
while (n) {
if (nint(n->car->car) == NODE_MASGN) {
gen_vmassignment(s, n->car->cdr->car, pos, NOVAL);
}
pos++;
n = n->cdr;
}
}
if (pargs) {
node *n = margs;
pos = ma+oa+ra+1;
while (n) {
if (nint(n->car->car) == NODE_MASGN) {
gen_vmassignment(s, n->car->cdr->car, pos, NOVAL);
}
pos++;
n = n->cdr;
}
}
}
codegen(s, tree->cdr->car, VAL);
pop();
if (s->pc > 0) {
gen_return(s, OP_RETURN, cursp());
}
if (blk) {
loop_pop(s, NOVAL);
}
scope_finish(s);
return parent->irep->rlen - 1;
}
static int
scope_body(codegen_scope *s, node *tree, int val)
{
codegen_scope *scope = scope_new(s->mrb, s, tree->car);
codegen(scope, tree->cdr, VAL);
gen_return(scope, OP_RETURN, scope->sp-1);
if (!s->iseq) {
genop_0(scope, OP_STOP);
}
scope_finish(scope);
if (!s->irep) {
/* should not happen */
return 0;
}
return s->irep->rlen - 1;
}
static mrb_bool
nosplat(node *t)
{
while (t) {
if (nint(t->car->car) == NODE_SPLAT) return FALSE;
t = t->cdr;
}
return TRUE;
}
static mrb_sym
attrsym(codegen_scope *s, mrb_sym a)
{
const char *name;
mrb_int len;
char *name2;
name = mrb_sym_name_len(s->mrb, a, &len);
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);
}
#define CALL_MAXARGS 15
#define GEN_LIT_ARY_MAX 64
#define GEN_VAL_STACK_MAX 99
static int
gen_values(codegen_scope *s, node *t, int val, int extra, 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 = nint(t->car->car) == NODE_SPLAT;
if (is_splat || n+extra >= limit-1 || 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 */
}
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;
while (tree) {
if (nint(tree->car->car->car) == NODE_KW_REST_ARGS) {
if (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 (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;
}
}
if (update) {
if (len > 0) {
pop_n(len*2+1);
genop_2(s, OP_HASHADD, cursp(), len);
push();
}
return -1; /* variable length */
}
if (update) return -1;
return len;
}
static void
gen_call(codegen_scope *s, node *tree, mrb_sym name, int sp, int val, int safe)
{
mrb_sym sym = name ? name : nsym(tree->cdr->car);
int skip = 0;
int n = 0, nk = 0, st = 0, noop = 0, blk = 0;
codegen(s, tree->car, VAL); /* receiver */
if (safe) {
int recv = cursp()-1;
gen_move(s, cursp(), recv, 1);
skip = genjmp2_0(s, OP_JMPNIL, cursp(), val);
}
tree = tree->cdr->cdr->car;
if (tree) {
if (tree->car) { /* positional arguments */
st = n = gen_values(s, tree->car, VAL, sp?1:0, 14);
if (n < 0) { /* variable length */
st = 1; /* one stack element */
noop = 1; /* not operator */
n = 15;
push();
}
}
if (tree->cdr->car) { /* keyword arguments */
noop = 1;
nk = gen_hash(s, tree->cdr->car->cdr, VAL, 14);
if (nk < 0) {st++; nk = 15;}
else st += 2*nk;
}
}
if (sp) { /* last argument pushed (attr=, []=) */
if (n == CALL_MAXARGS) {
gen_move(s, cursp(), sp, 0);
pop();
genop_2(s, OP_ARYPUSH, cursp(), 1);
push();
}
else {
gen_move(s, cursp(), sp, 0);
push();
n++; st++;
}
}
if (tree && tree->cdr && tree->cdr->cdr) {
codegen(s, tree->cdr->cdr, VAL);
pop();
noop = 1;
blk = 1;
}
push();pop();
pop_n(st+1);
if (!noop && sym == MRB_OPSYM_2(s->mrb, add) && n == 1) {
gen_addsub(s, OP_ADD, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, sub) && n == 1) {
gen_addsub(s, OP_SUB, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, mul) && n == 1) {
gen_muldiv(s, OP_MUL, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, div) && n == 1) {
gen_muldiv(s, OP_DIV, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, lt) && n == 1) {
genop_1(s, OP_LT, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, le) && n == 1) {
genop_1(s, OP_LE, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, gt) && n == 1) {
genop_1(s, OP_GT, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, ge) && n == 1) {
genop_1(s, OP_GE, cursp());
}
else if (!noop && sym == MRB_OPSYM_2(s->mrb, eq) && n == 1) {
genop_1(s, OP_EQ, 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 {
genop_3(s, blk ? OP_SENDB : OP_SEND, cursp(), new_sym(s, sym), n|(nk<<4));
}
if (safe) {
dispatch(s, skip);
}
if (val) {
push();
}
}
static void
gen_assignment(codegen_scope *s, node *tree, int sp, int val)
{
int idx;
int type = nint(tree->car);
tree = tree->cdr;
switch (type) {
case NODE_GVAR:
gen_setxv(s, OP_SETGV, sp, nsym(tree), val);
break;
case NODE_ARG:
case NODE_LVAR:
idx = lv_idx(s, nsym(tree));
if (idx > 0) {
if (idx != sp) {
gen_move(s, idx, sp, val);
}
break;
}
else { /* upvar */
gen_setupvar(s, sp, nsym(tree));
}
break;
case NODE_NVAR:
idx = nint(tree);
codegen_error(s, "Can't assign to numbered parameter");
break;
case NODE_IVAR:
gen_setxv(s, OP_SETIV, sp, nsym(tree), val);
break;
case NODE_CVAR:
gen_setxv(s, OP_SETCV, sp, nsym(tree), val);
break;
case NODE_CONST:
gen_setxv(s, OP_SETCONST, sp, nsym(tree), val);
break;
case NODE_COLON2:
gen_move(s, cursp(), sp, 0);
push();
codegen(s, tree->car, VAL);
pop_n(2);
idx = new_sym(s, nsym(tree->cdr));
genop_2(s, OP_SETMCNST, sp, idx);
break;
case NODE_CALL:
case NODE_SCALL:
push();
gen_call(s, tree, attrsym(s, nsym(tree->cdr->car)), sp, NOVAL,
type == NODE_SCALL);
pop();
if (val) {
gen_move(s, cursp(), sp, 0);
}
break;
case NODE_MASGN:
gen_vmassignment(s, tree->car, sp, val);
break;
/* splat without assignment */
case NODE_NIL:
break;
default:
codegen_error(s, "unknown lhs");
break;
}
if (val) push();
}
static void
gen_vmassignment(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, 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, cursp(), NOVAL);
}
if (t->cdr && t->cdr->car) {
t = t->cdr->car;
while (t) {
gen_assignment(s, t->car, 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 i = 0, j = 0, gen = 0;
while (tree) {
switch (nint(tree->car->car)) {
case NODE_STR:
if ((tree->cdr == NULL) && (nint(tree->car->cdr->cdr) == 0))
break;
/* fall through */
case NODE_BEGIN:
codegen(s, tree->car, VAL);
++j;
break;
case NODE_LITERAL_DELIM:
if (j > 0) {
j = 0;
++i;
if (sym)
gen_intern(s);
}
break;
}
while (j >= 2) {
pop(); pop();
genop_1(s, OP_STRCAT, cursp());
push();
j--;
}
if (i > GEN_LIT_ARY_MAX) {
pop_n(i);
if (gen) {
pop();
genop_2(s, OP_ARYPUSH, cursp(), i);
}
else {
genop_2(s, OP_ARRAY, cursp(), i);
gen = 1;
}
push();
i = 0;
}
tree = tree->cdr;
}
if (j > 0) {
++i;
if (sym)
gen_intern(s);
}
pop_n(i);
if (gen) {
pop();
genop_2(s, OP_ARYPUSH, cursp(), i);
}
else {
genop_2(s, OP_ARRAY, cursp(), i);
}
push();
}
else {
while (tree) {
switch (nint(tree->car->car)) {
case NODE_BEGIN: case NODE_BLOCK:
codegen(s, tree->car, NOVAL);
}
tree = tree->cdr;
}
}
}
static void
raise_error(codegen_scope *s, const char *msg)
{
int idx = new_lit(s, mrb_str_new_cstr(s->mrb, msg));
genop_1(s, OP_ERR, idx);
}
static mrb_int
readint(codegen_scope *s, const char *p, int base, mrb_bool neg, mrb_bool *overflow)
{
const char *e = p + strlen(p);
mrb_int result = 0;
mrb_assert(base >= 2 && base <= 16);
if (*p == '+') p++;
while (p < e) {
int n;
char c = *p;
switch (c) {
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
n = c - '0'; break;
case '8': case '9':
n = c - '0'; break;
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
n = c - 'a' + 10; break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
n = c - 'A' + 10; break;
default:
codegen_error(s, "malformed readint input");
*overflow = TRUE;
/* not reached */
return result;
}
if (mrb_int_mul_overflow(result, base, &result)) {
overflow:
*overflow = TRUE;
return 0;
}
mrb_uint tmp = ((mrb_uint)result)+n;
if (neg && tmp == (mrb_uint)MRB_INT_MAX+1) {
*overflow = FALSE;
return MRB_INT_MIN;
}
if (tmp > MRB_INT_MAX) goto overflow;
result = (mrb_int)tmp;
p++;
}
*overflow = FALSE;
if (neg) return -result;
return result;
}
static void
gen_retval(codegen_scope *s, node *tree)
{
if (nint(tree->car) == NODE_SPLAT) {
codegen(s, tree, VAL);
pop();
genop_1(s, OP_ARYDUP, cursp());
}
else {
codegen(s, tree, VAL);
pop();
}
}
static mrb_bool
true_always(node *tree)
{
switch (nint(tree->car)) {
case NODE_TRUE:
case NODE_INT:
case NODE_STR:
case NODE_SYM:
return TRUE;
default:
return FALSE;
}
}
static mrb_bool
false_always(node *tree)
{
switch (nint(tree->car)) {
case NODE_FALSE:
case NODE_NIL:
return TRUE;
default:
return FALSE;
}
}
static void
gen_blkmove(codegen_scope *s, int 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
codegen(codegen_scope *s, node *tree, int val)
{
int nt;
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");
}
if (s->irep && s->filename_index != tree->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);
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 = tree->filename_index;
s->filename_sym = mrb_parser_get_filename(s->parser, tree->filename_index);
}
nt = nint(tree->car);
s->lineno = tree->lineno;
tree = tree->cdr;
switch (nt) {
case NODE_BEGIN:
if (val && !tree) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
while (tree) {
codegen(s, tree->car, tree->cdr ? NOVAL : val);
tree = tree->cdr;
}
break;
case NODE_RESCUE:
{
int noexc;
uint32_t exend, pos1, pos2, tmp;
struct loopinfo *lp;
int catch_entry, begin, end;
if (tree->car == NULL) goto exit;
lp = loop_push(s, LOOP_BEGIN);
lp->pc0 = new_label(s);
catch_entry = catch_handler_new(s);
begin = s->pc;
codegen(s, tree->car, 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);
tree = tree->cdr;
exend = JMPLINK_START;
pos1 = JMPLINK_START;
if (tree->car) {
node *n2 = tree->car;
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 && nint(n4->car->car) == NODE_SPLAT) {
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(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1);
}
else {
if (n4) {
codegen(s, n4->car, VAL);
}
else {
genop_2(s, OP_GETCONST, cursp(), new_sym(s, MRB_SYM_2(s->mrb, 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, 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();
tree = tree->cdr;
dispatch(s, noexc);
if (tree->car) {
codegen(s, tree->car, val);
}
else if (val) {
push();
}
dispatch_linked(s, exend);
loop_pop(s, NOVAL);
}
break;
case NODE_ENSURE:
if (!tree->cdr || !tree->cdr->cdr ||
(nint(tree->cdr->cdr->car) == NODE_BEGIN &&
tree->cdr->cdr->cdr)) {
int catch_entry, begin, end, target;
int idx;
catch_entry = catch_handler_new(s);
begin = s->pc;
codegen(s, tree->car, val);
end = target = s->pc;
push();
idx = cursp();
genop_1(s, OP_EXCEPT, idx);
push();
codegen(s, tree->cdr->cdr, 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, tree->car, val);
}
break;
case NODE_LAMBDA:
if (val) {
int idx = lambda_body(s, tree, 1);
genop_2(s, OP_LAMBDA, cursp(), idx);
push();
}
break;
case NODE_BLOCK:
if (val) {
int idx = lambda_body(s, tree, 1);
genop_2(s, OP_BLOCK, cursp(), idx);
push();
}
break;
case NODE_IF:
{
uint32_t pos1, pos2;
mrb_bool nil_p = FALSE;
node *elsepart = tree->cdr->cdr->car;
if (!tree->car) {
codegen(s, elsepart, val);
goto exit;
}
if (true_always(tree->car)) {
codegen(s, tree->cdr->car, val);
goto exit;
}
if (false_always(tree->car)) {
codegen(s, elsepart, val);
goto exit;
}
if (nint(tree->car->car) == NODE_CALL) {
node *n = tree->car->cdr;
mrb_sym mid = nsym(n->cdr->car);
mrb_sym sym_nil_p = MRB_SYM_Q_2(s->mrb, nil);
if (mid == sym_nil_p && n->cdr->cdr->car == NULL) {
nil_p = TRUE;
codegen(s, n->car, VAL);
}
}
if (!nil_p) {
codegen(s, tree->car, VAL);
}
pop();
if (val || tree->cdr->car) {
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, tree->cdr->car, val);
if (val) pop();
if (elsepart || val) {
pos2 = genjmp_0(s, OP_JMP);
dispatch(s, pos1);
codegen(s, elsepart, val);
dispatch(s, pos2);
}
else {
dispatch(s, pos1);
}
}
else { /* empty then-part */
if (elsepart) {
if (nil_p) {
pos1 = genjmp2_0(s, OP_JMPNIL, cursp(), val);
}
else {
pos1 = genjmp2_0(s, OP_JMPIF, cursp(), val);
}
codegen(s, elsepart, val);
dispatch(s, pos1);
}
else if (val && !nil_p) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
}
}
break;
case NODE_AND:
{
uint32_t pos;
if (true_always(tree->car)) {
codegen(s, tree->cdr, val);
goto exit;
}
if (false_always(tree->car)) {
codegen(s, tree->car, val);
goto exit;
}
codegen(s, tree->car, VAL);
pop();
pos = genjmp2_0(s, OP_JMPNOT, cursp(), val);
codegen(s, tree->cdr, val);
dispatch(s, pos);
}
break;
case NODE_OR:
{
uint32_t pos;
if (true_always(tree->car)) {
codegen(s, tree->car, val);
goto exit;
}
if (false_always(tree->car)) {
codegen(s, tree->cdr, val);
goto exit;
}
codegen(s, tree->car, VAL);
pop();
pos = genjmp2_0(s, OP_JMPIF, cursp(), val);
codegen(s, tree->cdr, val);
dispatch(s, pos);
}
break;
case NODE_WHILE:
case NODE_UNTIL:
{
if (true_always(tree->car)) {
if (nt == NODE_UNTIL) {
if (val) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
goto exit;
}
}
else if (false_always(tree->car)) {
if (nt == NODE_WHILE) {
if (val) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
goto exit;
}
}
uint32_t pos = JMPLINK_START;
struct loopinfo *lp = loop_push(s, LOOP_NORMAL);
if (!val) lp->reg = -1;
lp->pc0 = new_label(s);
codegen(s, tree->car, VAL);
pop();
if (nt == NODE_WHILE) {
pos = genjmp2_0(s, OP_JMPNOT, cursp(), NOVAL);
}
else {
pos = genjmp2_0(s, OP_JMPIF, cursp(), NOVAL);
}
lp->pc1 = new_label(s);
codegen(s, tree->cdr, NOVAL);
genjmp(s, OP_JMP, lp->pc0);
dispatch(s, pos);
loop_pop(s, val);
}
break;
case NODE_FOR:
for_body(s, tree);
if (val) push();
break;
case NODE_CASE:
{
int head = 0;
uint32_t pos1, pos2, pos3, tmp;
node *n;
pos3 = JMPLINK_START;
if (tree->car) {
head = cursp();
codegen(s, tree->car, VAL);
}
tree = tree->cdr;
while (tree) {
n = tree->car->car;
pos1 = pos2 = JMPLINK_START;
while (n) {
codegen(s, n->car, VAL);
if (head) {
gen_move(s, cursp(), head, 0);
push(); push(); pop(); pop(); pop();
if (nint(n->car->car) == NODE_SPLAT) {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1);
}
else {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1);
}
}
else {
pop();
}
tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, NOVAL);
pos2 = tmp;
n = n->cdr;
}
if (tree->car->car) {
pos1 = genjmp_0(s, OP_JMP);
dispatch_linked(s, pos2);
}
codegen(s, tree->car->cdr, val);
if (val) pop();
tmp = genjmp(s, OP_JMP, pos3);
pos3 = tmp;
dispatch(s, pos1);
tree = tree->cdr;
}
if (val) {
uint32_t pos = cursp();
genop_1(s, OP_LOADNIL, cursp());
if (pos3 != JMPLINK_START) dispatch_linked(s, pos3);
if (head) pop();
if (cursp() != pos) {
gen_move(s, cursp(), pos, 0);
}
push();
}
else {
if (pos3 != JMPLINK_START) {
dispatch_linked(s, pos3);
}
if (head) {
pop();
}
}
}
break;
case NODE_SCOPE:
scope_body(s, tree, NOVAL);
break;
case NODE_FCALL:
case NODE_CALL:
gen_call(s, tree, 0, 0, val, 0);
break;
case NODE_SCALL:
gen_call(s, tree, 0, 0, val, 1);
break;
case NODE_DOT2:
codegen(s, tree->car, val);
codegen(s, tree->cdr, val);
if (val) {
pop(); pop();
genop_1(s, OP_RANGE_INC, cursp());
push();
}
break;
case NODE_DOT3:
codegen(s, tree->car, val);
codegen(s, tree->cdr, val);
if (val) {
pop(); pop();
genop_1(s, OP_RANGE_EXC, cursp());
push();
}
break;
case NODE_COLON2:
{
int sym = new_sym(s, nsym(tree->cdr));
codegen(s, tree->car, VAL);
pop();
genop_2(s, OP_GETMCNST, cursp(), sym);
if (val) push();
}
break;
case NODE_COLON3:
{
int sym = new_sym(s, nsym(tree));
genop_1(s, OP_OCLASS, cursp());
genop_2(s, OP_GETMCNST, cursp(), sym);
if (val) push();
}
break;
case NODE_ARRAY:
{
int n;
n = gen_values(s, tree, val, 0, 0);
if (val) {
if (n >= 0) {
pop_n(n);
genop_2(s, OP_ARRAY, cursp(), n);
}
push();
}
}
break;
case NODE_HASH:
case NODE_KW_HASH:
{
int nk = gen_hash(s, tree, val, GEN_LIT_ARY_MAX);
if (val && nk >= 0) {
pop_n(nk*2);
genop_2(s, OP_HASH, cursp(), nk);
push();
}
}
break;
case NODE_SPLAT:
codegen(s, tree, val);
break;
case NODE_ASGN:
codegen(s, tree->cdr, VAL);
pop();
gen_assignment(s, tree->car, cursp(), val);
break;
case NODE_MASGN:
{
int len = 0, n = 0, post = 0;
node *t = tree->cdr, *p;
int rhs = cursp();
if (nint(t->car) == NODE_ARRAY && t->cdr && nosplat(t->cdr)) {
/* fixed rhs */
t = t->cdr;
while (t) {
codegen(s, t->car, VAL);
len++;
t = t->cdr;
}
tree = tree->car;
if (tree->car) { /* pre */
t = tree->car;
n = 0;
while (t) {
if (n < len) {
gen_assignment(s, t->car, rhs+n, NOVAL);
n++;
}
else {
genop_1(s, OP_LOADNIL, rhs+n);
gen_assignment(s, t->car, rhs+n, NOVAL);
}
t = t->cdr;
}
}
t = tree->cdr;
if (t) {
if (t->cdr) { /* post count */
p = t->cdr->car;
while (p) {
post++;
p = p->cdr;
}
}
if (t->car) { /* rest (len - pre - post) */
int rn;
if (len < post + n) {
rn = 0;
}
else {
rn = len - post - n;
}
genop_3(s, OP_ARRAY2, cursp(), rhs+n, rn);
gen_assignment(s, t->car, cursp(), NOVAL);
n += rn;
}
if (t->cdr && t->cdr->car) {
t = t->cdr->car;
while (n<len) {
gen_assignment(s, t->car, rhs+n, NOVAL);
t = t->cdr;
n++;
}
}
}
pop_n(len);
if (val) {
genop_2(s, OP_ARRAY, rhs, len);
push();
}
}
else {
/* variable rhs */
codegen(s, t, VAL);
gen_vmassignment(s, tree->car, rhs, val);
if (!val) {
pop();
}
}
}
break;
case NODE_OP_ASGN:
{
mrb_sym sym = nsym(tree->cdr->car);
mrb_int len;
const char *name = mrb_sym_name_len(s->mrb, sym, &len);
int idx, callargs = -1, vsp = -1;
if ((len == 2 && name[0] == '|' && name[1] == '|') &&
(nint(tree->car->car) == NODE_CONST ||
nint(tree->car->car) == NODE_CVAR)) {
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, tree->car, 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_LOADF, exc);
dispatch(s, noexc);
loop_pop(s, NOVAL);
}
else if (nint(tree->car->car) == NODE_CALL) {
node *n = tree->car->cdr;
int base, i, nargs = 0;
callargs = 0;
if (val) {
vsp = cursp();
push();
}
codegen(s, n->car, VAL); /* receiver */
idx = new_sym(s, nsym(n->cdr->car));
base = cursp()-1;
if (n->cdr->cdr->car) {
nargs = gen_values(s, n->cdr->cdr->car->car, VAL, 1, 14);
if (nargs >= 0) {
callargs = nargs;
}
else { /* varargs */
push();
nargs = 1;
callargs = CALL_MAXARGS;
}
}
/* copy receiver and arguments */
gen_move(s, cursp(), base, 1);
for (i=0; i<nargs; i++) {
gen_move(s, cursp()+i+1, base+i+1, 1);
}
push_n(nargs+2);pop_n(nargs+2); /* space for receiver, arguments and a block */
genop_3(s, OP_SEND, cursp(), idx, callargs);
push();
}
else {
codegen(s, tree->car, VAL);
}
if (len == 2 &&
((name[0] == '|' && name[1] == '|') ||
(name[0] == '&' && name[1] == '&'))) {
uint32_t pos;
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, tree->cdr->cdr->car, VAL);
pop();
if (val && vsp >= 0) {
gen_move(s, vsp, cursp(), 1);
}
if (nint(tree->car->car) == NODE_CALL) {
if (callargs == CALL_MAXARGS) {
pop();
genop_2(s, OP_ARYPUSH, cursp(), 1);
}
else {
pop_n(callargs);
callargs++;
}
pop();
idx = new_sym(s, attrsym(s, nsym(tree->car->cdr->cdr->car)));
genop_3(s, OP_SEND, cursp(), idx, callargs);
}
else {
gen_assignment(s, tree->car, cursp(), val);
}
dispatch(s, pos);
goto exit;
}
codegen(s, tree->cdr->cdr->car, VAL);
push(); pop();
pop(); pop();
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 {
idx = new_sym(s, sym);
genop_3(s, OP_SEND, cursp(), idx, 1);
}
if (callargs < 0) {
gen_assignment(s, tree->car, cursp(), val);
}
else {
if (val && vsp >= 0) {
gen_move(s, vsp, cursp(), 0);
}
if (callargs == CALL_MAXARGS) {
pop();
genop_2(s, OP_ARYPUSH, cursp(), 1);
}
else {
pop_n(callargs);
callargs++;
}
pop();
idx = new_sym(s, attrsym(s,nsym(tree->car->cdr->cdr->car)));
genop_3(s, OP_SEND, cursp(), idx, callargs);
}
}
break;
case NODE_SUPER:
{
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) {
node *args = tree->car;
if (args) {
st = n = gen_values(s, args, VAL, 0, 14);
if (n < 0) {
st = 1; n = 15;
push();
}
}
/* keyword arguments */
if ((s2->ainfo & 0x1) && tree->cdr->car) {
nk = gen_hash(s, tree->cdr->car->cdr, VAL, 14);
if (nk < 0) {st++; nk = 15;}
else st += nk;
n |= 15<<4;
}
/* block arguments */
if (tree->cdr->cdr) {
codegen(s, tree->cdr->cdr, VAL);
}
else {
gen_blkmove(s, s2->ainfo, lv);
}
st++;
}
else {
gen_blkmove(s, s2->ainfo, lv);
st++;
}
pop_n(st+1);
genop_2(s, OP_SUPER, cursp(), n);
if (val) push();
}
break;
case NODE_ZSUPER:
{
codegen_scope *s2 = s;
int lv = 0, ainfo = 0;
int n = CALL_MAXARGS;
int sp = cursp();
push(); /* room for receiver */
while (!s2->mscope) {
lv++;
s2 = s2->prev;
if (!s2) break;
}
if (s2 && s2->ainfo > 0) {
ainfo = s2->ainfo;
}
genop_2S(s, OP_ARGARY, cursp(), (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();
}
/* block argument */
if (tree && tree->cdr && tree->cdr->cdr) {
push();
codegen(s, tree->cdr->cdr, VAL);
}
s->sp = sp;
genop_2(s, OP_SUPER, cursp(), n);
if (val) push();
}
break;
case NODE_RETURN:
if (tree) {
gen_retval(s, tree);
}
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) push();
break;
case NODE_YIELD:
{
codegen_scope *s2 = s;
int lv = 0, ainfo = -1;
int n = 0, sendv = 0;
while (!s2->mscope) {
lv++;
s2 = s2->prev;
if (!s2) break;
}
if (s2) {
ainfo = s2->ainfo;
}
if (ainfo < 0) codegen_error(s, "invalid yield (SyntaxError)");
push();
if (tree) {
n = gen_values(s, tree, VAL, 0, 14);
if (n < 0) {
n = sendv = 1;
push();
}
}
push();pop(); /* space for a block */
pop_n(n+1);
genop_2S(s, OP_BLKPUSH, cursp(), (ainfo<<4)|(lv & 0xf));
if (sendv) n = CALL_MAXARGS;
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, call)), n);
if (val) push();
}
break;
case NODE_BREAK:
loop_break(s, tree);
if (val) push();
break;
case NODE_NEXT:
if (!s->loop) {
raise_error(s, "unexpected next");
}
else if (s->loop->type == LOOP_NORMAL) {
codegen(s, tree, NOVAL);
genjmp(s, OP_JMPUW, s->loop->pc0);
}
else {
if (tree) {
codegen(s, tree, VAL);
pop();
}
else {
genop_1(s, OP_LOADNIL, cursp());
}
gen_return(s, OP_RETURN, cursp());
}
if (val) push();
break;
case NODE_REDO:
if (!s->loop || s->loop->type == LOOP_BEGIN || s->loop->type == LOOP_RESCUE) {
raise_error(s, "unexpected redo");
}
else {
genjmp(s, OP_JMPUW, s->loop->pc1);
}
if (val) push();
break;
case NODE_RETRY:
{
const char *msg = "unexpected retry";
const struct loopinfo *lp = s->loop;
while (lp && lp->type != LOOP_RESCUE) {
lp = lp->prev;
}
if (!lp) {
raise_error(s, msg);
}
else {
genjmp(s, OP_JMPUW, lp->pc0);
}
if (val) push();
}
break;
case NODE_LVAR:
if (val) {
int idx = lv_idx(s, nsym(tree));
if (idx > 0) {
gen_move(s, cursp(), idx, val);
}
else {
gen_getupvar(s, cursp(), nsym(tree));
}
push();
}
break;
case NODE_NVAR:
if (val) {
int idx = nint(tree);
gen_move(s, cursp(), idx, val);
push();
}
break;
case NODE_GVAR:
{
int sym = new_sym(s, nsym(tree));
genop_2(s, OP_GETGV, cursp(), sym);
if (val) push();
}
break;
case NODE_IVAR:
{
int sym = new_sym(s, nsym(tree));
genop_2(s, OP_GETIV, cursp(), sym);
if (val) push();
}
break;
case NODE_CVAR:
{
int sym = new_sym(s, nsym(tree));
genop_2(s, OP_GETCV, cursp(), sym);
if (val) push();
}
break;
case NODE_CONST:
{
int sym = new_sym(s, nsym(tree));
genop_2(s, OP_GETCONST, cursp(), sym);
if (val) push();
}
break;
case NODE_BACK_REF:
if (val) {
char buf[] = {'$', nchar(tree)};
int sym = new_sym(s, mrb_intern(s->mrb, buf, sizeof(buf)));
genop_2(s, OP_GETGV, cursp(), sym);
push();
}
break;
case NODE_NTH_REF:
if (val) {
mrb_state *mrb = s->mrb;
mrb_value str;
int sym;
str = mrb_format(mrb, "$%d", nint(tree));
sym = new_sym(s, mrb_intern_str(mrb, str));
genop_2(s, OP_GETGV, cursp(), sym);
push();
}
break;
case NODE_ARG:
/* should not happen */
break;
case NODE_BLOCK_ARG:
codegen(s, tree, val);
break;
case NODE_INT:
if (val) {
char *p = (char*)tree->car;
int base = nint(tree->cdr->car);
mrb_int i;
mrb_bool overflow;
i = readint(s, p, base, FALSE, &overflow);
if (overflow) {
int off = new_litbn(s, p, base, FALSE);
genop_2(s, OP_LOADL, cursp(), off);
}
else {
gen_int(s, cursp(), i);
}
push();
}
break;
#ifndef MRB_NO_FLOAT
case NODE_FLOAT:
if (val) {
char *p = (char*)tree;
mrb_float f = mrb_float_read(p, NULL);
int off = new_lit(s, mrb_float_value(s->mrb, f));
genop_2(s, OP_LOADL, cursp(), off);
push();
}
break;
#endif
case NODE_NEGATE:
{
nt = nint(tree->car);
switch (nt) {
#ifndef MRB_NO_FLOAT
case NODE_FLOAT:
if (val) {
char *p = (char*)tree->cdr;
mrb_float f = mrb_float_read(p, NULL);
int off = new_lit(s, mrb_float_value(s->mrb, -f));
genop_2(s, OP_LOADL, cursp(), off);
push();
}
break;
#endif
case NODE_INT:
if (val) {
char *p = (char*)tree->cdr->car;
int base = nint(tree->cdr->cdr->car);
mrb_int i;
mrb_bool overflow;
i = readint(s, p, base, TRUE, &overflow);
if (overflow) {
int off = new_litbn(s, p, base, TRUE);
genop_2(s, OP_LOADL, cursp(), off);
}
else {
gen_int(s, cursp(), i);
}
push();
}
break;
default:
if (val) {
codegen(s, tree, VAL);
pop();
push_n(2);pop_n(2); /* space for receiver&block */
mrb_sym minus = MRB_OPSYM_2(s->mrb, minus);
if (!gen_uniop(s, minus, cursp())) {
genop_3(s, OP_SEND, cursp(), new_sym(s, minus), 0);
}
push();
}
else {
codegen(s, tree, NOVAL);
}
break;
}
}
break;
case NODE_STR:
if (val) {
char *p = (char*)tree->car;
size_t len = (intptr_t)tree->cdr;
int ai = mrb_gc_arena_save(s->mrb);
int off = new_lit(s, mrb_str_new(s->mrb, p, len));
mrb_gc_arena_restore(s->mrb, ai);
genop_2(s, OP_STRING, cursp(), off);
push();
}
break;
case NODE_HEREDOC:
tree = ((struct mrb_parser_heredoc_info *)tree)->doc;
/* fall through */
case NODE_DSTR:
if (val) {
node *n = tree;
if (!n) {
genop_1(s, OP_LOADNIL, cursp());
push();
break;
}
codegen(s, n->car, VAL);
n = n->cdr;
while (n) {
codegen(s, n->car, VAL);
pop(); pop();
genop_1(s, OP_STRCAT, cursp());
push();
n = n->cdr;
}
}
else {
node *n = tree;
while (n) {
if (nint(n->car->car) != NODE_STR) {
codegen(s, n->car, NOVAL);
}
n = n->cdr;
}
}
break;
case NODE_WORDS:
gen_literal_array(s, tree, FALSE, val);
break;
case NODE_SYMBOLS:
gen_literal_array(s, tree, TRUE, val);
break;
case NODE_DXSTR:
{
node *n;
int ai = mrb_gc_arena_save(s->mrb);
int sym = new_sym(s, MRB_SYM_2(s->mrb, Kernel));
genop_1(s, OP_LOADSELF, cursp());
push();
codegen(s, tree->car, VAL);
n = tree->cdr;
while (n) {
if (nint(n->car->car) == NODE_XSTR) {
n->car->car = (struct mrb_ast_node*)(intptr_t)NODE_STR;
mrb_assert(!n->cdr); /* must be the end */
}
codegen(s, n->car, VAL);
pop(); pop();
genop_1(s, OP_STRCAT, cursp());
push();
n = n->cdr;
}
push(); /* for block */
pop_n(3);
sym = new_sym(s, MRB_OPSYM_2(s->mrb, tick)); /* ` */
genop_3(s, OP_SEND, cursp(), sym, 1);
if (val) push();
mrb_gc_arena_restore(s->mrb, ai);
}
break;
case NODE_XSTR:
{
char *p = (char*)tree->car;
size_t len = (intptr_t)tree->cdr;
int ai = mrb_gc_arena_save(s->mrb);
int off = new_lit(s, mrb_str_new(s->mrb, p, len));
int sym;
genop_1(s, OP_LOADSELF, cursp());
push();
genop_2(s, OP_STRING, cursp(), off);
push(); push();
pop_n(3);
sym = new_sym(s, MRB_OPSYM_2(s->mrb, tick)); /* ` */
genop_3(s, OP_SEND, cursp(), sym, 1);
if (val) push();
mrb_gc_arena_restore(s->mrb, ai);
}
break;
case NODE_REGX:
if (val) {
char *p1 = (char*)tree->car;
char *p2 = (char*)tree->cdr->car;
char *p3 = (char*)tree->cdr->cdr;
int ai = mrb_gc_arena_save(s->mrb);
int sym = new_sym(s, mrb_intern_lit(s->mrb, REGEXP_CLASS));
int off = new_lit(s, mrb_str_new_cstr(s->mrb, p1));
int argc = 1;
genop_1(s, OP_OCLASS, cursp());
genop_2(s, OP_GETMCNST, cursp(), sym);
push();
genop_2(s, OP_STRING, cursp(), off);
push();
if (p2 || p3) {
if (p2) { /* opt */
off = new_lit(s, mrb_str_new_cstr(s->mrb, p2));
genop_2(s, OP_STRING, cursp(), off);
}
else {
genop_1(s, OP_LOADNIL, cursp());
}
push();
argc++;
if (p3) { /* enc */
off = new_lit(s, mrb_str_new(s->mrb, p3, 1));
genop_2(s, OP_STRING, cursp(), off);
push();
argc++;
}
}
push(); /* space for a block */
pop_n(argc+2);
sym = new_sym(s, MRB_SYM_2(s->mrb, compile));
genop_3(s, OP_SEND, cursp(), sym, argc);
mrb_gc_arena_restore(s->mrb, ai);
push();
}
break;
case NODE_DREGX:
if (val) {
node *n = tree->car;
int ai = mrb_gc_arena_save(s->mrb);
int sym = new_sym(s, mrb_intern_lit(s->mrb, REGEXP_CLASS));
int argc = 1;
int off;
char *p;
genop_1(s, OP_OCLASS, cursp());
genop_2(s, OP_GETMCNST, cursp(), sym);
push();
codegen(s, n->car, VAL);
n = n->cdr;
while (n) {
codegen(s, n->car, VAL);
pop(); pop();
genop_1(s, OP_STRCAT, cursp());
push();
n = n->cdr;
}
n = tree->cdr->cdr;
if (n->car) { /* tail */
p = (char*)n->car;
off = new_lit(s, mrb_str_new_cstr(s->mrb, p));
codegen(s, tree->car, VAL);
genop_2(s, OP_STRING, cursp(), off);
pop();
genop_1(s, OP_STRCAT, cursp());
push();
}
if (n->cdr->car) { /* opt */
char *p2 = (char*)n->cdr->car;
off = new_lit(s, mrb_str_new_cstr(s->mrb, p2));
genop_2(s, OP_STRING, cursp(), off);
push();
argc++;
}
if (n->cdr->cdr) { /* enc */
char *p2 = (char*)n->cdr->cdr;
off = new_lit(s, mrb_str_new_cstr(s->mrb, p2));
genop_2(s, OP_STRING, cursp(), off);
push();
argc++;
}
push(); /* space for a block */
pop_n(argc+2);
sym = new_sym(s, MRB_SYM_2(s->mrb, compile));
genop_3(s, OP_SEND, cursp(), sym, argc);
mrb_gc_arena_restore(s->mrb, ai);
push();
}
else {
node *n = tree->car;
while (n) {
if (nint(n->car->car) != NODE_STR) {
codegen(s, n->car, NOVAL);
}
n = n->cdr;
}
}
break;
case NODE_SYM:
if (val) {
int sym = new_sym(s, nsym(tree));
genop_2(s, OP_LOADSYM, cursp(), sym);
push();
}
break;
case NODE_DSYM:
codegen(s, tree, val);
if (val) {
gen_intern(s);
}
break;
case NODE_SELF:
if (val) {
genop_1(s, OP_LOADSELF, cursp());
push();
}
break;
case NODE_NIL:
if (val) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
break;
case NODE_TRUE:
if (val) {
genop_1(s, OP_LOADT, cursp());
push();
}
break;
case NODE_FALSE:
if (val) {
genop_1(s, OP_LOADF, cursp());
push();
}
break;
case NODE_ALIAS:
{
int a = new_sym(s, nsym(tree->car));
int b = new_sym(s, nsym(tree->cdr));
genop_2(s, OP_ALIAS, a, b);
if (val) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
}
break;
case NODE_UNDEF:
{
node *t = tree;
while (t) {
int symbol = new_sym(s, nsym(t->car));
genop_1(s, OP_UNDEF, symbol);
t = t->cdr;
}
if (val) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
}
break;
case NODE_CLASS:
{
int idx;
node *body;
if (tree->car->car == (node*)0) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
else if (tree->car->car == (node*)1) {
genop_1(s, OP_OCLASS, cursp());
push();
}
else {
codegen(s, tree->car->car, VAL);
}
if (tree->cdr->car) {
codegen(s, tree->cdr->car, VAL);
}
else {
genop_1(s, OP_LOADNIL, cursp());
push();
}
pop(); pop();
idx = new_sym(s, nsym(tree->car->cdr));
genop_2(s, OP_CLASS, cursp(), idx);
body = tree->cdr->cdr->car;
if (nint(body->cdr->car) == NODE_BEGIN && body->cdr->cdr == NULL) {
genop_1(s, OP_LOADNIL, cursp());
}
else {
idx = scope_body(s, body, val);
genop_2(s, OP_EXEC, cursp(), idx);
}
if (val) {
push();
}
}
break;
case NODE_MODULE:
{
int idx;
if (tree->car->car == (node*)0) {
genop_1(s, OP_LOADNIL, cursp());
push();
}
else if (tree->car->car == (node*)1) {
genop_1(s, OP_OCLASS, cursp());
push();
}
else {
codegen(s, tree->car->car, VAL);
}
pop();
idx = new_sym(s, nsym(tree->car->cdr));
genop_2(s, OP_MODULE, cursp(), idx);
if (nint(tree->cdr->car->cdr->car) == NODE_BEGIN &&
tree->cdr->car->cdr->cdr == NULL) {
genop_1(s, OP_LOADNIL, cursp());
}
else {
idx = scope_body(s, tree->cdr->car, val);
genop_2(s, OP_EXEC, cursp(), idx);
}
if (val) {
push();
}
}
break;
case NODE_SCLASS:
{
int idx;
codegen(s, tree->car, VAL);
pop();
genop_1(s, OP_SCLASS, cursp());
if (nint(tree->cdr->car->cdr->car) == NODE_BEGIN &&
tree->cdr->car->cdr->cdr == NULL) {
genop_1(s, OP_LOADNIL, cursp());
}
else {
idx = scope_body(s, tree->cdr->car, val);
genop_2(s, OP_EXEC, cursp(), idx);
}
if (val) {
push();
}
}
break;
case NODE_DEF:
{
int sym = new_sym(s, nsym(tree->car));
int idx = lambda_body(s, tree->cdr, 0);
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();
}
break;
case NODE_SDEF:
{
node *recv = tree->car;
int sym = new_sym(s, nsym(tree->cdr->car));
int idx = lambda_body(s, tree->cdr->cdr, 0);
codegen(s, recv, VAL);
pop();
genop_1(s, OP_SCLASS, cursp());
push();
genop_2(s, OP_METHOD, cursp(), idx);
pop();
genop_2(s, OP_DEF, cursp(), sym);
if (val) push();
}
break;
case NODE_POSTEXE:
codegen(s, tree, NOVAL);
break;
default:
break;
}
exit:
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**)codegen_realloc(s, 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 };
mrb_pool *pool = mrb_pool_open(mrb);
codegen_scope *s = (codegen_scope *)mrb_pool_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 = -1;
s->mscope = 0;
scope_add_irep(s);
s->rcapa = 8;
s->reps = (mrb_irep**)mrb_malloc(mrb, sizeof(mrb_irep*)*s->rcapa);
s->icapa = 1024;
s->iseq = (mrb_code*)mrb_malloc(mrb, sizeof(mrb_code)*s->icapa);
s->pcapa = 32;
s->pool = (mrb_pool_value*)mrb_malloc(mrb, sizeof(mrb_pool_value)*s->pcapa);
s->scapa = 256;
s->syms = (mrb_sym*)mrb_malloc(mrb, sizeof(mrb_sym)*s->scapa);
s->lv = nlv;
s->sp += node_len(nlv)+1; /* add self */
s->nlocals = s->sp;
if (nlv) {
mrb_sym *lv;
node *n = nlv;
size_t i = 0;
s->irep->lv = lv = (mrb_sym*)mrb_malloc(mrb, 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*)mrb_malloc(mrb, 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 *)codegen_realloc(s, 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;
}
mrb_free(s->mrb, s->catch_table);
s->catch_table = NULL;
irep->pool = (const mrb_pool_value*)codegen_realloc(s, s->pool, sizeof(mrb_pool_value)*irep->plen);
irep->syms = (const mrb_sym*)codegen_realloc(s, s->syms, sizeof(mrb_sym)*irep->slen);
irep->reps = (const mrb_irep**)codegen_realloc(s, 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);
}
mrb_free(s->mrb, s->lines);
irep->nlocals = s->nlocals;
irep->nregs = s->nregs;
mrb_gc_arena_restore(mrb, s->ai);
mrb_pool_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 *)codegen_realloc(s, (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);
mrb_pool_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);
mrb_pool_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, VAL);
}
void
mrb_irep_remove_lv(mrb_state *mrb, mrb_irep *irep)
{
int i;
if (irep->flags & MRB_IREP_NO_FREE) return;
if (irep->lv) {
mrb_free(mrb, (void*)irep->lv);
irep->lv = NULL;
}
if (!irep->reps) return;
for (i = 0; i < irep->rlen; ++i) {
mrb_irep_remove_lv(mrb, (mrb_irep*)irep->reps[i]);
}
}