2022-02-15 17:35:09 +01:00

1291 lines
43 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#include "be_vm.h"
#include "be_decoder.h"
#include "be_string.h"
#include "be_strlib.h"
#include "be_class.h"
#include "be_func.h"
#include "be_vector.h"
#include "be_list.h"
#include "be_map.h"
#include "be_module.h"
#include "be_mem.h"
#include "be_var.h"
#include "be_gc.h"
#include "be_exec.h"
#include "be_debug.h"
#include "be_libs.h"
#include <string.h>
#include <math.h>
#define NOT_METHOD BE_NONE
#define vm_error(vm, except, ...) \
be_raise(vm, except, be_pushfstring(vm, __VA_ARGS__))
#define RA() (reg + IGET_RA(ins)) /* Get value of register A */
#define RKB() ((isKB(ins) ? ktab : reg) + KR2idx(IGET_RKB(ins))) /* Get value of register or constant B */
#define RKC() ((isKC(ins) ? ktab : reg) + KR2idx(IGET_RKC(ins))) /* Get value of register or constant C */
#define var2cl(_v) cast(bclosure*, var_toobj(_v)) /* cast var to closure */
#define var2real(_v) (var_isreal(_v) ? (_v)->v.r : (breal)(_v)->v.i) /* get var as real or convert to real if integer */
#define val2bool(v) ((v) ? btrue : bfalse) /* get var as bool (trur if non zero) */
#define ibinop(op, a, b) ((a)->v.i op (b)->v.i) /* apply binary operator to both arguments as integers */
#if BE_USE_DEBUG_HOOK
#define DEBUG_HOOK() \
if (vm->hookmask & BE_HOOK_LINE) { \
do_linehook(vm); \
reg = vm->reg; \
}
#else
#define DEBUG_HOOK()
#endif
#if BE_USE_PERF_COUNTERS
#define COUNTER_HOOK() \
vm->counter_ins++;
#else
#define COUNTER_HOOK()
#endif
#if BE_USE_PERF_COUNTERS
#define VM_HEARTBEAT() \
if ((vm->counter_ins & ((1<<(BE_VM_OBSERVABILITY_SAMPLING - 1))-1) ) == 0) { /* call every 2^BE_VM_OBSERVABILITY_SAMPLING instructions */ \
if (vm->obshook != NULL) \
(*vm->obshook)(vm, BE_OBS_VM_HEARTBEAT, vm->counter_ins); \
}
#else
#define VM_HEARTBEAT()
#endif
#define vm_exec_loop() \
loop: \
DEBUG_HOOK(); \
COUNTER_HOOK(); \
VM_HEARTBEAT(); \
switch (IGET_OP(ins = *vm->ip++))
#if BE_USE_SINGLE_FLOAT
#define mathfunc(func) func##f
#else
#define mathfunc(func) func
#endif
#define opcase(opcode) case OP_##opcode
#define dispatch() goto loop
#define equal_rule(op, iseq) \
bbool res; \
be_assert(!var_isstatic(a)); \
be_assert(!var_isstatic(b)); \
if (var_isint(a) && var_isint(b)) { \
res = ibinop(op, a, b); \
} else if (var_isnumber(a) && var_isnumber(b)) { \
res = var2real(a) op var2real(b); \
} else if (var_isinstance(a) && !var_isnil(b)) { \
res = object_eqop(vm, #op, iseq, a, b); \
} else if (var_primetype(a) == var_primetype(b)) { /* same types */ \
if (var_isnil(a)) { /* nil op nil */ \
res = 1 op 1; \
} else if (var_isbool(a)) { /* bool op bool */ \
res = var_tobool(a) op var_tobool(b); \
} else if (var_isstr(a)) { /* string op string */ \
res = 1 op be_eqstr(a->v.s, b->v.s); \
} else if (var_isclass(a) || var_isfunction(a) || var_iscomptr(a)) { \
res = var_toobj(a) op var_toobj(b); \
} else { \
binop_error(vm, #op, a, b); \
res = bfalse; /* will not be executed */ \
} \
} else { /* different types */ \
res = 1 op 0; \
} \
return res
#define relop_rule(op) \
bbool res; \
if (var_isint(a) && var_isint(b)) { \
res = ibinop(op, a, b); \
} else if (var_isnumber(a) && var_isnumber(b)) { \
res = var2real(a) op var2real(b); \
} else if (var_isstr(a) && var_isstr(b)) { \
bstring *s1 = var_tostr(a), *s2 = var_tostr(b); \
res = be_strcmp(s1, s2) op 0; \
} else if (var_isinstance(a)) { \
binstance *obj = var_toobj(a); \
object_binop(vm, #op, *a, *b); \
check_bool(vm, obj, #op); \
res = var_tobool(vm->top); \
} else { \
binop_error(vm, #op, a, b); \
res = bfalse; /* will not be executed */ \
} \
return res
#define bitwise_block(op) \
bvalue *dst = RA(), *a = RKB(), *b = RKC(); \
if (var_isint(a) && var_isint(b)) { \
var_setint(dst, ibinop(op, a, b)); \
} else if (var_isinstance(a)) { \
ins_binop(vm, #op, ins); \
} else { \
binop_error(vm, #op, a, b); \
}
#define push_native(_vm, _f, _ns, _t) { \
precall(_vm, _f, _ns, _t); \
_vm->cf->status = PRIM_FUNC; \
}
static void prep_closure(bvm *vm, int pos, int argc, int mode);
static void attribute_error(bvm *vm, const char *t, bvalue *b, bvalue *c)
{
const char *attr = var_isstr(c) ? str(var_tostr(c)) : be_vtype2str(c);
vm_error(vm, "attribute_error",
"'%s' value has no %s '%s'", be_vtype2str(b), t, attr);
}
static void binop_error(bvm *vm, const char *op, bvalue *a, bvalue *b)
{
vm_error(vm, "type_error",
"unsupported operand type(s) for %s: '%s' and '%s'",
op, be_vtype2str(a), be_vtype2str(b));
}
static void unop_error(bvm *vm, const char *op, bvalue *a)
{
vm_error(vm, "type_error",
"unsupported operand type(s) for %s: '%s'",
op, be_vtype2str(a));
}
static void call_error(bvm *vm, bvalue *v)
{
vm_error(vm, "type_error",
"'%s' value is not callable", be_vtype2str(v));
}
/* Check that the return value is bool or raise an exception */
/* `obj` and `method` are only passed for error reporting */
static void check_bool(bvm *vm, binstance *obj, const char *method)
{
if (!var_isbool(vm->top)) {
const char *name = str(be_instance_name(obj));
vm_error(vm, "type_error",
"`%s::%s` return value error, the expected type is 'bool'",
strlen(name) ? name : "<anonymous>", method);
}
}
#if BE_USE_DEBUG_HOOK
static void do_linehook(bvm *vm)
{
bcallframe *cf = vm->cf;
bclosure *cl = var_toobj(cf->func);
int pc = cast_int(vm->ip - cl->proto->code);
if (!pc || pc > cf->lineinfo->endpc) {
while (pc > cf->lineinfo->endpc)
cf->lineinfo++;
be_callhook(vm, BE_HOOK_LINE);
} else {
blineinfo *linfo = cf->lineinfo;
blineinfo *base = cl->proto->lineinfo;
while (linfo > base && pc <= linfo[-1].endpc)
linfo--;
if (cf->lineinfo != linfo) {
cf->lineinfo = linfo;
be_callhook(vm, BE_HOOK_LINE);
}
}
}
#endif
/* Prepare the stack for the function/method call */
/* `func` is a pointer to the function/method on the stack, it contains the closure before call and the result after the call */
/* `nstackˋ is the stack depth used by the function (determined by compiler), we add BE_STACK_FREE_MIN as a safety margin */
static void precall(bvm *vm, bvalue *func, int nstack, int mode)
{
bcallframe *cf;
int expan = nstack + BE_STACK_FREE_MIN; /* `expan` is the minimum required space on the stack */
if (vm->stacktop < func + expan) { /* do we have too little space left on the stack? */
size_t fpos = func - vm->stack; /* compute offset of `func` from base stack, in case stack is reallocated and base address changes */
be_stack_expansion(vm, expan); /* expand stack (vector object), warning stack address changes */
func = vm->stack + fpos; /* recompute `func` address with new stack address */
}
be_stack_push(vm, &vm->callstack, NULL); /* push a NULL value on callstack */
cf = be_stack_top(&vm->callstack); /* get address of new callframe at top of callstack */
cf->func = func - mode;
cf->top = vm->top; /* save previous stack top */
cf->reg = vm->reg; /* save previous stack base */
vm->reg = func + 1; /* new stack base is right after function */
vm->top = vm->reg + nstack; /* new stack top is above the registers used by the function, so we don´t mess with them */
vm->cf = cf; /* set new current callframe */
}
/* Prepare call of closure, setting the instruction pointer (ip) */
static void push_closure(bvm *vm, bvalue *func, int nstack, int mode)
{
bclosure *cl = var_toobj(func);
precall(vm, func, nstack, mode);
vm->cf->ip = vm->ip;
vm->cf->status = NONE_FLAG;
vm->ip = cl->proto->code;
#if BE_USE_DEBUG_HOOK
vm->cf->lineinfo = cl->proto->lineinfo;
be_callhook(vm, BE_HOOK_CALL);
#endif
}
static void ret_native(bvm *vm)
{
bcallframe *_cf = vm->cf;
vm->reg = _cf->reg;
vm->top = _cf->top;
be_stack_pop(&vm->callstack);
vm->cf = be_stack_top(&vm->callstack);
}
static bbool obj2bool(bvm *vm, bvalue *var)
{
binstance *obj = var_toobj(var);
bstring *tobool = str_literal(vm, "tobool");
/* get operator method */
// TODO what if `tobool` is static
int type = be_instance_member(vm, obj, tobool, vm->top);
if (type != BE_NONE && type != BE_NIL) {
vm->top[1] = *var; /* move self to argv[0] */
be_dofunc(vm, vm->top, 1); /* call method 'tobool' */
/* check the return value */
check_bool(vm, obj, "tobool");
return var_tobool(vm->top);
}
return btrue;
}
bbool be_value2bool(bvm *vm, bvalue *v)
{
switch (var_basetype(v)) {
case BE_NIL:
return bfalse;
case BE_BOOL:
return var_tobool(v);
case BE_INT:
return val2bool(v->v.i);
case BE_REAL:
return val2bool(v->v.r);
case BE_INSTANCE:
return obj2bool(vm, v);
default:
return btrue;
}
}
static void obj_method(bvm *vm, bvalue *o, bstring *attr, bvalue *dst)
{
binstance *obj = var_toobj(o);
int type = be_instance_member_simple(vm, obj, attr, dst);
if (basetype(type) != BE_FUNCTION) {
vm_error(vm, "attribute_error",
"the '%s' object has no method '%s'",
str(be_instance_name(obj)), str(attr));
}
}
static int obj_attribute(bvm *vm, bvalue *o, bstring *attr, bvalue *dst)
{
binstance *obj = var_toobj(o);
int type = be_instance_member(vm, obj, attr, dst);
if (type == BE_NONE) {
vm_error(vm, "attribute_error",
"the '%s' object has no attribute '%s'",
str(be_instance_name(obj)), str(attr));
}
return type;
}
static int class_attribute(bvm *vm, bvalue *o, bvalue *c, bvalue *dst)
{
bstring *attr = var_tostr(c);
bclass *obj = var_toobj(o);
int type = be_class_member(vm, obj, attr, dst);
if (type == BE_NONE || type == BE_INDEX) {
vm_error(vm, "attribute_error",
"the '%s' class has no static attribute '%s'",
str(obj->name), str(attr));
}
return type;
}
static int module_attribute(bvm *vm, bvalue *o, bvalue *c, bvalue *dst)
{
bstring *attr = var_tostr(c);
bmodule *module = var_toobj(o);
int type = be_module_attr(vm, module, attr, dst);
if (type == BE_NONE) {
vm_error(vm, "attribute_error",
"module '%s' has no member '%s'",
be_module_name(module), str(attr));
}
return type;
}
static bbool object_eqop(bvm *vm,
const char *op, bbool iseq, bvalue *a, bvalue *b)
{
binstance *o = var_toobj(a);
bvalue self = *a, other = *b;
bbool isself = var_isinstance(b) && o == var_toobj(b);
/* first, try to call the overloaded operator of the object */
int type = be_instance_member(vm, o, be_newstr(vm, op), vm->top);
// TODO check that method is not static
if (basetype(type) == BE_FUNCTION) { /* call method */
bvalue *top = vm->top;
top[1] = self; /* move self to argv[0] */
top[2] = other; /* move other to argv[1] */
be_incrtop(vm); /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
be_stackpop(vm, 1);
check_bool(vm, o, op); /* check return value */
return var_tobool(vm->top); /* copy result to dst */
}
/* the default equal operation rule */
return iseq == isself; /* check object self */
}
static void object_binop(bvm *vm, const char *op, bvalue self, bvalue other)
{
bvalue *top = vm->top;
/* get operator method (possible GC) */
obj_method(vm, &self, be_newstr(vm, op), vm->top);
top[1] = self; /* move self to argv[0] */
top[2] = other; /* move other to argv[1] */
be_incrtop(vm); /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
be_stackpop(vm, 1);
}
#define ins_binop(vm, op, ins) { \
object_binop(vm, op, *RKB(), *RKC()); \
reg = vm->reg; \
*RA() = *vm->top; /* copy result to dst */ \
}
static void ins_unop(bvm *vm, const char *op, bvalue self)
{
bvalue *top = vm->top;
/* get operator method (possible GC) */
obj_method(vm, &self, be_newstr(vm, op), vm->top);
top[1] = self; /* move self to argv[0] */
be_dofunc(vm, top, 1); /* call method 'item' */
}
bbool be_vm_iseq(bvm *vm, bvalue *a, bvalue *b)
{
equal_rule(==, btrue);
}
bbool be_vm_isneq(bvm *vm, bvalue *a, bvalue *b)
{
equal_rule(!=, bfalse);
}
bbool be_vm_islt(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(<);
}
bbool be_vm_isle(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(<=);
}
bbool be_vm_isgt(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(>);
}
bbool be_vm_isge(bvm *vm, bvalue *a, bvalue *b)
{
relop_rule(>=);
}
static void make_range(bvm *vm, bvalue lower, bvalue upper)
{
/* get method 'item' (possible GC) */
int idx = be_builtin_find(vm, str_literal(vm, "range"));
bvalue *top = vm->top;
top[0] = *be_global_var(vm, idx);
top[1] = lower; /* move lower to argv[0] */
top[2] = upper; /* move upper to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
vm->top -= 3;
}
static void connect_str(bvm *vm, bstring *a, bvalue *b)
{
bstring *s;
if (var_isstr(b)) {
s = be_strcat(vm, a, var_tostr(b));
var_setstr(vm->top, s);
} else {
*vm->top++ = *b;
be_val2str(vm, -1);
b = vm->top - 1;
s = be_strcat(vm, a, var_tostr(b));
var_setstr(b, s);
vm->top -= 1;
}
}
BERRY_API bvm* be_vm_new(void)
{
bvm *vm = be_os_malloc(sizeof(bvm));
be_assert(vm != NULL);
memset(vm, 0, sizeof(bvm)); /* clear all members */
be_gc_init(vm);
be_string_init(vm);
be_stack_init(vm, &vm->callstack, sizeof(bcallframe));
be_stack_init(vm, &vm->refstack, sizeof(binstance*));
be_stack_init(vm, &vm->exceptstack, sizeof(struct bexecptframe));
be_stack_init(vm, &vm->tracestack, sizeof(bcallsnapshot));
vm->stack = be_malloc(vm, sizeof(bvalue) * BE_STACK_START);
vm->stacktop = vm->stack + BE_STACK_START;
vm->reg = vm->stack;
vm->top = vm->reg;
be_globalvar_init(vm);
be_gc_setpause(vm, 1);
be_loadlibs(vm);
vm->compopt = 0;
vm->obshook = NULL;
vm->ctypefunc = NULL;
#if BE_USE_PERF_COUNTERS
vm->counter_ins = 0;
vm->counter_enter = 0;
vm->counter_call = 0;
vm->counter_get = 0;
vm->counter_set = 0;
vm->counter_try = 0;
vm->counter_exc = 0;
vm->counter_gc_kept = 0;
vm->counter_gc_freed = 0;
#endif
return vm;
}
BERRY_API void be_vm_delete(bvm *vm)
{
be_gc_deleteall(vm);
be_string_deleteall(vm);
be_stack_delete(vm, &vm->callstack);
be_stack_delete(vm, &vm->refstack);
be_stack_delete(vm, &vm->exceptstack);
be_stack_delete(vm, &vm->tracestack);
be_free(vm, vm->stack, (vm->stacktop - vm->stack) * sizeof(bvalue));
be_globalvar_deinit(vm);
#if BE_USE_DEBUG_HOOK
/* free native hook */
if (var_istype(&vm->hook, BE_COMPTR))
be_free(vm, var_toobj(&vm->hook), sizeof(struct bhookblock));
#endif
/* free VM structure */
be_os_free(vm);
}
static void vm_exec(bvm *vm)
{
bclosure *clos;
bvalue *ktab, *reg;
binstruction ins;
vm->cf->status |= BASE_FRAME;
newframe: /* a new call frame */
be_assert(var_isclosure(vm->cf->func));
clos = var_toobj(vm->cf->func); /* `clos` is the current function/closure */
ktab = clos->proto->ktab; /* `ktab` is the current constant table */
reg = vm->reg; /* `reg` is the current stack base for the callframe */
#if BE_USE_PERF_COUNTERS
vm->counter_enter++;
#endif
vm_exec_loop() {
opcase(LDNIL): {
var_setnil(RA());
dispatch();
}
opcase(LDBOOL): {
bvalue *v = RA();
var_setbool(v, IGET_RKB(ins));
if (IGET_RKC(ins)) { /* skip next instruction */
vm->ip += 1;
}
dispatch();
}
opcase(LDINT): {
bvalue *v = RA();
var_setint(v, IGET_sBx(ins));
dispatch();
}
opcase(LDCONST): {
bvalue *dst = RA();
*dst = ktab[IGET_Bx(ins)];
dispatch();
}
opcase(GETGBL): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
*v = *be_global_var(vm, idx);
dispatch();
}
opcase(GETNGBL): { /* get Global by name */
bvalue *v = RA();
bvalue *b = RKB();
if (var_isstr(b)) {
bstring *name = var_tostr(b);
int idx = be_global_find(vm, name);
if (idx > -1) {
*v = *be_global_var(vm, idx);
} else {
vm_error(vm, "attribute_error", "'%s' undeclared", str(name));
}
} else {
vm_error(vm, "internal_error", "global name must be a string");
}
dispatch();
}
opcase(SETNGBL): { /* set Global by name */
bvalue *v = RA();
bvalue *b = RKB();
if (var_isstr(b)) {
bstring *name = var_tostr(b);
int idx = be_global_new(vm, name);
*be_global_var(vm, idx) = *v;
} else {
vm_error(vm, "internal_error", "global name must be a string");
}
dispatch();
}
opcase(SETGBL): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
*be_global_var(vm, idx) = *v;
dispatch();
}
opcase(GETUPV): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
be_assert(*clos->upvals != NULL);
*v = *clos->upvals[idx]->value;
dispatch();
}
opcase(SETUPV): {
bvalue *v = RA();
int idx = IGET_Bx(ins);
be_assert(*clos->upvals != NULL);
*clos->upvals[idx]->value = *v;
dispatch();
}
opcase(MOVE): {
bvalue *dst = RA();
*dst = *RKB();
dispatch();
}
opcase(ADD): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(+, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
union bvaldata x, y; // TASMOTA workaround for ESP32 rev0 bug
x.i = a->v.i;
if (var_isint(a)) { x.r = (breal) x.i; }
y.i = b->v.i;
if (var_isint(b)) { y.r = (breal) y.i; }
// breal x = var2real(a), y = var2real(b);
var_setreal(dst, x.r + y.r);
} else if (var_isstr(a) && var_isstr(b)) { /* strcat */
bstring *s = be_strcat(vm, var_tostr(a), var_tostr(b));
reg = vm->reg;
dst = RA();
var_setstr(dst, s);
} else if (var_isinstance(a)) {
ins_binop(vm, "+", ins);
} else {
binop_error(vm, "+", a, b);
}
dispatch();
}
opcase(SUB): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(-, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
var_setreal(dst, x - y);
} else if (var_isinstance(a)) {
ins_binop(vm, "-", ins);
} else {
binop_error(vm, "-", a, b);
}
dispatch();
}
opcase(MUL): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(*, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
var_setreal(dst, x * y);
} else if (var_isinstance(a)) {
ins_binop(vm, "*", ins);
} else {
binop_error(vm, "*", a, b);
}
dispatch();
}
opcase(DIV): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
bint x = var_toint(a), y = var_toint(b);
if (y == 0) {
vm_error(vm, "divzero_error", "division by zero");
} else {
var_setint(dst, x / y);
}
} else if (var_isnumber(a) && var_isnumber(b)) {
breal x = var2real(a), y = var2real(b);
if (y == cast(breal, 0)) {
vm_error(vm, "divzero_error", "division by zero");
}
var_setreal(dst, x / y);
} else if (var_isinstance(a)) {
ins_binop(vm, "/", ins);
} else {
binop_error(vm, "/", a, b);
}
dispatch();
}
opcase(MOD): {
bvalue *dst = RA(), *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
var_setint(dst, ibinop(%, a, b));
} else if (var_isnumber(a) && var_isnumber(b)) {
var_setreal(dst, mathfunc(fmod)(var_toreal(a), var_toreal(b)));
} else if (var_isinstance(a)) {
ins_binop(vm, "%", ins);
} else {
binop_error(vm, "%", a, b);
}
dispatch();
}
opcase(LT): {
bbool res = be_vm_islt(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(LE): {
bbool res = be_vm_isle(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(EQ): {
bbool res = be_vm_iseq(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(NE): {
bbool res = be_vm_isneq(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(GT): {
bbool res = be_vm_isgt(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(GE): {
bbool res = be_vm_isge(vm, RKB(), RKC());
bvalue *dst;
reg = vm->reg;
dst = RA();
var_setbool(dst, res);
dispatch();
}
opcase(CONNECT): {
bvalue *a = RKB(), *b = RKC();
if (var_isint(a) && var_isint(b)) {
make_range(vm, *RKB(), *RKC());
} else if (var_isstr(a)) {
connect_str(vm, var_tostr(a), b);
} else if (var_isinstance(a)) {
object_binop(vm, "..", *RKB(), *RKC());
} else {
binop_error(vm, "..", RKB(), RKC());
}
reg = vm->reg;
*RA() = *vm->top; /* copy result to R(A) */
dispatch();
}
opcase(AND): {
bitwise_block(&);
dispatch();
}
opcase(OR): {
bitwise_block(|);
dispatch();
}
opcase(XOR): {
bitwise_block(^);
dispatch();
}
opcase(SHL): {
bitwise_block(<<);
dispatch();
}
opcase(SHR): {
bitwise_block(>>);
dispatch();
}
opcase(NEG): {
bvalue *dst = RA(), *a = RKB();
if (var_isint(a)) {
var_setint(dst, -a->v.i);
} else if (var_isreal(a)) {
var_setreal(dst, -a->v.r);
} else if (var_isinstance(a)) {
ins_unop(vm, "-*", *RKB());
reg = vm->reg;
*RA() = *vm->top; /* copy result to dst */
} else {
unop_error(vm, "-", a);
}
dispatch();
}
opcase(FLIP): {
bvalue *dst = RA(), *a = RKB();
if (var_isint(a)) {
var_setint(dst, ~a->v.i);
} else if (var_isinstance(a)) {
ins_unop(vm, "~", *RKB());
reg = vm->reg;
*RA() = *vm->top; /* copy result to dst */
} else {
unop_error(vm, "~", a);
}
dispatch();
}
opcase(JMP): {
vm->ip += IGET_sBx(ins);
dispatch();
}
opcase(JMPT): {
if (be_value2bool(vm, RA())) {
vm->ip += IGET_sBx(ins);
}
dispatch();
}
opcase(JMPF): {
if (!be_value2bool(vm, RA())) {
vm->ip += IGET_sBx(ins);
}
dispatch();
}
opcase(CLOSURE): {
bvalue *dst;
bproto *p = clos->proto->ptab[IGET_Bx(ins)];
bclosure *cl = be_newclosure(vm, p->nupvals);
cl->proto = p;
reg = vm->reg;
dst = RA();
var_setclosure(dst, cl);
be_initupvals(vm, cl);
dispatch();
}
opcase(CLASS): {
bclass *c = var_toobj(ktab + IGET_Bx(ins));
be_class_upvalue_init(vm, c);
dispatch();
}
opcase(GETMBR): {
#if BE_USE_PERF_COUNTERS
vm->counter_get++;
#endif
bvalue result; /* copy result to a temp variable because the stack may be relocated in virtual member calls */
bvalue *b = RKB(), *c = RKC();
if (var_isinstance(b) && var_isstr(c)) {
obj_attribute(vm, b, var_tostr(c), &result);
reg = vm->reg;
} else if (var_isclass(b) && var_isstr(c)) {
class_attribute(vm, b, c, &result);
reg = vm->reg;
} else if (var_ismodule(b) && var_isstr(c)) {
module_attribute(vm, b, c, &result);
reg = vm->reg;
} else {
attribute_error(vm, "attribute", b, c);
result = *RA(); /* avoid gcc warning for uninitialized variable result, this code is never reached */
}
bvalue *a = RA();
*a = result; /* assign the resul to the specified register on the updated stack */
dispatch();
}
opcase(GETMET): {
#if BE_USE_PERF_COUNTERS
vm->counter_get++;
#endif
bvalue result; /* copy result to a temp variable because the stack may be relocated in virtual member calls */
bvalue *b = RKB(), *c = RKC();
if (var_isinstance(b) && var_isstr(c)) {
binstance *obj = var_toobj(b);
int type = obj_attribute(vm, b, var_tostr(c), &result);
reg = vm->reg;
bvalue *a = RA();
*a = result;
if (var_basetype(a) == BE_FUNCTION) {
if ((type & BE_STATIC) || (type == BE_INDEX)) { /* if instance variable then we consider it's non-method */
/* static method, don't bother with the instance */
a[1] = result;
var_settype(a, NOT_METHOD);
} else {
/* this is a real method (i.e. non-static) */
/* check if the object is a superinstance, if so get the lowest possible subclass */
while (obj->sub) {
obj = obj->sub;
}
var_setinstance(&a[1], obj); /* replace superinstance by lowest subinstance */
}
} else {
vm_error(vm, "attribute_error",
"class '%s' has no method '%s'",
str(be_instance_name(obj)), str(var_tostr(c)));
}
} else if (var_isclass(b) && var_isstr(c)) {
class_attribute(vm, b, c, &result);
reg = vm->reg;
bvalue *a = RA();
a[1] = result;
var_settype(a, NOT_METHOD);
} else if (var_ismodule(b) && var_isstr(c)) {
module_attribute(vm, b, c, &result);
reg = vm->reg;
bvalue *a = RA();
a[1] = result;
var_settype(a, NOT_METHOD);
} else {
attribute_error(vm, "method", b, c);
}
dispatch();
}
opcase(SETMBR): {
#if BE_USE_PERF_COUNTERS
vm->counter_set++;
#endif
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(a) && var_isstr(b)) {
binstance *obj = var_toobj(a);
bstring *attr = var_tostr(b);
bvalue result = *c;
if (var_isfunction(&result)) {
var_markstatic(&result);
}
if (!be_instance_setmember(vm, obj, attr, &result)) {
reg = vm->reg;
vm_error(vm, "attribute_error",
"class '%s' cannot assign to attribute '%s'",
str(be_instance_name(obj)), str(attr));
}
reg = vm->reg;
dispatch();
}
if (var_isclass(a) && var_isstr(b)) {
/* if value is a function, we mark it as a static to distinguish from methods */
bclass *obj = var_toobj(a);
bstring *attr = var_tostr(b);
bvalue result = *c;
if (var_isfunction(&result)) {
var_markstatic(&result);
}
if (!be_class_setmember(vm, obj, attr, &result)) {
reg = vm->reg;
vm_error(vm, "attribute_error",
"class '%s' cannot assign to static attribute '%s'",
str(be_class_name(obj)), str(attr));
}
reg = vm->reg;
dispatch();
}
if (var_ismodule(a) && var_isstr(b)) {
bmodule *obj = var_toobj(a);
bstring *attr = var_tostr(b);
if (be_module_setmember(vm, obj, attr, c)) {
reg = vm->reg;
dispatch();
} else {
reg = vm->reg;
// fall through exception below
}
}
attribute_error(vm, "writable attribute", a, b);
dispatch();
}
opcase(GETIDX): {
bvalue *b = RKB(), *c = RKC();
if (var_isinstance(b)) {
bvalue *top = vm->top;
/* get method 'item' */
obj_method(vm, b, str_literal(vm, "item"), vm->top);
top[1] = *b; /* move object to argv[0] */
top[2] = *c; /* move key to argv[1] */
vm->top += 3; /* prevent collection results */
be_dofunc(vm, top, 2); /* call method 'item' */
vm->top -= 3;
reg = vm->reg;
*RA() = *vm->top; /* copy result to R(A) */
} else if (var_isstr(b)) {
bstring *s = be_strindex(vm, var_tostr(b), c);
reg = vm->reg;
var_setstr(RA(), s);
} else {
vm_error(vm, "type_error",
"value '%s' does not support subscriptable",
be_vtype2str(b));
}
dispatch();
}
opcase(SETIDX): {
bvalue *a = RA(), *b = RKB(), *c = RKC();
if (var_isinstance(a)) {
bvalue *top = vm->top;
/* get method 'setitem' */
obj_method(vm, a, str_literal(vm, "setitem"), vm->top);
top[1] = *a; /* move object to argv[0] */
top[2] = *b; /* move key to argv[1] */
top[3] = *c; /* move src to argv[2] */
vm->top += 4;
be_dofunc(vm, top, 3); /* call method 'setitem' */
vm->top -= 4;
reg = vm->reg;
} else {
vm_error(vm, "type_error",
"value '%s' does not support index assignment",
be_vtype2str(a));
}
dispatch();
}
opcase(SETSUPER): {
bvalue *a = RA(), *b = RKB();
if (var_isclass(a) && var_isclass(b)) {
bclass *obj = var_toobj(a);
if (!gc_isconst(obj)) {
be_class_setsuper(obj, var_toobj(b));
} else {
vm_error(vm, "internal_error",
"cannot change superclass of a read-only class");
}
} else {
vm_error(vm, "type_error",
"value '%s' does not support set super",
be_vtype2str(b));
}
dispatch();
}
opcase(CLOSE): {
be_upvals_close(vm, RA());
dispatch();
}
opcase(IMPORT): {
bvalue *b = RKB();
if (var_isstr(b)) {
bstring *name = var_tostr(b);
int res = be_module_load(vm, name);
reg = vm->reg;
switch (res) {
case BE_OK: /* find the module */
be_stackpop(vm, 1);
*RA() = *vm->top;
break;
case BE_EXCEPTION: /* pop the exception value and message */
be_pop(vm, 2);
be_throw(vm, BE_EXCEPTION);
break;
default:
vm_error(vm, "import_error", "module '%s' not found", str(name));
}
} else {
vm_error(vm, "type_error",
"import '%s' does not support import",
be_vtype2str(b));
}
dispatch();
}
opcase(CATCH): {
bvalue *base = RA(), *top = vm->top;
int i = 0, ecnt = IGET_RKB(ins), vcnt = IGET_RKC(ins);
while (i < ecnt && !be_vm_iseq(vm, top, base + i)) {
++i;
}
if (!ecnt || i < ecnt) { /* exception catched */
base = RA(), top = vm->top;
for (i = 0; i < vcnt; ++i) {
*base++ = *top++;
}
vm->ip += 1; /* skip next instruction */
}
dispatch();
}
opcase(RAISE): {
if (IGET_RA(ins) < 2) { /* A==2 means no arguments are passed to RAISE, i.e. rethrow with current exception */
bvalue *top = vm->top;
top[0] = *RKB(); /* push the exception value to top */
if (IGET_RA(ins)) { /* has exception argument? */
top[1] = *RKC(); /* push the exception argument to top + 1 */
} else {
var_setnil(top + 1);
}
be_save_stacktrace(vm);
}
be_throw(vm, BE_EXCEPTION); /* throw / rethrow the exception */
dispatch();
}
opcase(EXBLK): {
#if BE_USE_PERF_COUNTERS
vm->counter_try++;
#endif
if (!IGET_RA(ins)) {
be_except_block_setup(vm);
if (be_setjmp(vm->errjmp->b)) {
bvalue *top = vm->top;
bvalue e1 = top[0];
bvalue e2 = top[1];
be_except_block_resume(vm);
top = vm->top;
top[0] = e1;
top[1] = e2;
goto newframe;
}
reg = vm->reg;
} else {
be_except_block_close(vm, IGET_Bx(ins));
}
dispatch();
}
opcase(CALL): {
#if BE_USE_PERF_COUNTERS
vm->counter_call++;
#endif
bvalue *var = RA(); /* `var` is the register for the call followed by arguments */
int mode = 0, argc = IGET_RKB(ins); /* B contains number of arguments pushed on stack */
recall: /* goto: instantiation class and call constructor */
switch (var_type(var)) {
case NOT_METHOD:
var[0] = var[1];
++var, --argc, mode = 1;
goto recall;
case BE_CLASS:
if (be_class_newobj(vm, var_toobj(var), var - reg, ++argc, mode)) { /* instanciate object and find constructor */
reg = vm->reg + mode; /* constructor found */
mode = 0;
var = RA() + 1; /* to next register */
reg = vm->reg;
goto recall; /* call constructor */
}
break;
case BE_INSTANCE: {
bvalue *v = var + argc++, temp;
/* load the '()' method to `temp' */
obj_method(vm, var, str_literal(vm, "()"), &temp);
for (; v >= var; --v) v[1] = v[0];
*var = temp;
goto recall; /* call '()' method */
}
case BE_CLOSURE: {
prep_closure(vm, var - reg, argc, mode);
reg = vm->reg; /* `reg` has changed, now new base register */
goto newframe; /* continue execution of the closure */
}
case BE_NTVCLOS: {
bntvclos *f = var_toobj(var);
push_native(vm, var, argc, mode);
f->f(vm); /* call C primitive function */
ret_native(vm);
break;
}
case BE_NTVFUNC: {
bntvfunc f = var_tontvfunc(var);
push_native(vm, var, argc, mode);
f(vm); /* call C primitive function */
ret_native(vm);
break;
}
case BE_CTYPE_FUNC: {
if (vm->ctypefunc) {
push_native(vm, var, argc, mode);
const void* args = var_toobj(var);
vm->ctypefunc(vm, args);
ret_native(vm);
} else {
vm_error(vm, "internal_error", "missing ctype_func handler");
}
break;
}
case BE_MODULE: {
bvalue attr;
var_setstr(&attr, str_literal(vm, "()"));
module_attribute(vm, var, &attr, var); /* exception if not found */
goto recall; /* call '()' method */
break;
}
default:
call_error(vm, var);
}
reg = vm->reg;
dispatch();
}
opcase(RET): {
bcallframe *cf;
bvalue *ret;
#if BE_USE_DEBUG_HOOK
be_callhook(vm, BE_HOOK_RET);
#endif
cf = vm->cf;
ret = vm->cf->func;
/* copy return value */
if (IGET_RA(ins)) {
*ret = *RKB();
} else {
var_setnil(ret);
}
vm->reg = cf->reg;
vm->top = cf->top;
vm->ip = cf->ip;
be_stack_pop(&vm->callstack); /* pop don't delete */
if (cf->status & BASE_FRAME) { /* entrance function */
bstack *cs = &vm->callstack;
if (!be_stack_isempty(cs)) {
vm->cf = be_stack_top(cs);
}
return;
}
vm->cf = be_stack_top(&vm->callstack);
goto newframe;
}
}
}
static void prep_closure(bvm *vm, int pos, int argc, int mode)
{
bvalue *v, *end;
bproto *proto = var2cl(vm->reg + pos)->proto;
push_closure(vm, vm->reg + pos, proto->nstack, mode);
end = vm->reg + proto->argc;
for (v = vm->reg + argc; v <= end; ++v) {
var_setnil(v);
}
if (proto->varg & BE_VA_VARARG) { /* there are vararg at the last argument, build the list */
/* code below uses mostly low-level calls for performance */
be_stack_require(vm, argc + 2); /* make sure we don't overflow the stack */
bvalue *top_save = vm->top; /* save original stack, we need fresh slots to create the 'list' instance */
vm->top = v; /* move top of stack right after last argument */
be_newobject(vm, "list"); /* this creates 2 objects on stack: list instance, BE_LIST object */
blist *list = var_toobj(vm->top-1); /* get low-level BE_LIST structure */
v = vm->reg + proto->argc - 1; /* last argument */
for (; v < vm->reg + argc; v++) {
be_list_push(vm, list, v); /* push all varargs into list */
}
*(vm->reg + proto->argc - 1) = *(vm->top-2); /* change the vararg argument to now contain the list instance */
vm->top = top_save; /* restore top of stack pointer */
}
}
static void do_closure(bvm *vm, int pos, int argc)
{
// bvalue *v, *end;
// bproto *proto = var2cl(reg)->proto;
// push_closure(vm, reg, proto->nstack, 0);
// v = vm->reg + argc;
// end = vm->reg + proto->argc;
// for (; v <= end; ++v) {
// var_setnil(v);
// }
prep_closure(vm, pos, argc, 0);
vm_exec(vm);
}
static void do_ntvclos(bvm *vm, int pos, int argc)
{
bntvclos *f = var_toobj(vm->reg + pos);
push_native(vm, vm->reg + pos, argc, 0);
f->f(vm); /* call C primitive function */
ret_native(vm);
}
static void do_ntvfunc(bvm *vm, int pos, int argc)
{
bntvfunc f = var_tontvfunc(vm->reg + pos);
push_native(vm, vm->reg + pos, argc, 0);
f(vm); /* call C primitive function */
ret_native(vm);
}
static void do_class(bvm *vm, int pos, int argc)
{
if (be_class_newobj(vm, var_toobj(vm->reg + pos), pos, ++argc, 0)) {
be_incrtop(vm);
be_dofunc(vm, vm->reg + pos + 1, argc);
be_stackpop(vm, 1);
}
}
void be_dofunc(bvm *vm, bvalue *v, int argc)
{
be_assert(vm->reg <= v && v < vm->stacktop);
be_assert(vm->stack <= vm->reg && vm->reg < vm->stacktop);
int pos = v - vm->reg;
be_assert(!var_isstatic(v));
switch (var_type(v)) {
case BE_CLASS: do_class(vm, pos, argc); break;
case BE_CLOSURE: do_closure(vm, pos, argc); break;
case BE_NTVCLOS: do_ntvclos(vm, pos, argc); break;
case BE_NTVFUNC: do_ntvfunc(vm, pos, argc); break;
default: call_error(vm, v);
}
}
BERRY_API void be_set_obs_hook(bvm *vm, bobshook hook)
{
(void)vm; /* avoid comiler warning */
(void)hook; /* avoid comiler warning */
vm->obshook = hook;
}
BERRY_API void be_set_ctype_func_hanlder(bvm *vm, bctypefunc handler)
{
vm->ctypefunc = handler;
}
BERRY_API bctypefunc be_get_ctype_func_hanlder(bvm *vm)
{
return vm->ctypefunc;
}