pxt-calliope/libs/core/core.cpp
2018-08-01 12:45:56 -07:00

1118 lines
24 KiB
C++

#include "pxtbase.h"
#include <limits.h>
#include <stdlib.h>
using namespace std;
#define p10(v) __builtin_powi(10, v)
namespace pxt {
static HandlerBinding *handlerBindings;
HandlerBinding *findBinding(int source, int value) {
for (auto p = handlerBindings; p; p = p->next) {
if (p->source == source && p->value == value) {
return p;
}
}
return 0;
}
void setBinding(int source, int value, Action act) {
auto curr = findBinding(source, value);
incr(act);
if (curr) {
decr(curr->action);
curr->action = act;
return;
}
curr = new HandlerBinding();
curr->next = handlerBindings;
curr->source = source;
curr->value = value;
curr->action = act;
handlerBindings = curr;
}
static const uint16_t emptyString[]
__attribute__((aligned(4))) = {0xffff, PXT_REF_TAG_STRING, 0, 0};
static const uint16_t emptyBuffer[]
__attribute__((aligned(4))) = {0xffff, PXT_REF_TAG_BUFFER, 0, 0};
String mkString(const char *data, int len) {
if (len < 0)
len = strlen(data);
if (len == 0)
return (String)emptyString;
String r = new (::operator new(sizeof(BoxedString) + len + 1)) BoxedString();
r->length = len;
if (data)
memcpy(r->data, data, len);
r->data[len] = 0;
MEMDBG("mkString: len=%d => %p", len, r);
return r;
}
Buffer mkBuffer(const uint8_t *data, int len) {
if (len <= 0)
return (Buffer)emptyBuffer;
Buffer r = new (::operator new(sizeof(BoxedBuffer) + len)) BoxedBuffer();
r->length = len;
if (data)
memcpy(r->data, data, len);
else
memset(r->data, 0, len);
MEMDBG("mkBuffer: len=%d => %p", len, r);
return r;
}
#ifndef X86_64
TNumber mkNaN() {
// TODO optimize
return fromDouble(NAN);
}
#endif
static unsigned random_value = 0xC0DA1;
void seedRandom(unsigned seed) {
random_value = seed;
}
unsigned getRandom(unsigned max) {
unsigned m, result;
do {
m = (unsigned)max;
result = 0;
do {
// Cycle the LFSR (Linear Feedback Shift Register).
// We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneier here
// (a true legend in the field!),
// For those interested, it's documented in his paper:
// "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent
// generator for 32-bit Microprocessors"
// https://www.schneier.com/paper-pseudorandom-sequence.html
unsigned r = random_value;
r = ((((r >> 31) ^ (r >> 6) ^ (r >> 4) ^ (r >> 2) ^ (r >> 1) ^ r) & 1) << 31) |
(r >> 1);
random_value = r;
result = ((result << 1) | (r & 0x00000001));
} while (m >>= 1);
} while (result > (unsigned)max);
return result;
}
PXT_DEF_STRING(sTrue, "\x04\x00true")
PXT_DEF_STRING(sFalse, "\x05\x00false")
PXT_DEF_STRING(sUndefined, "\x09\x00undefined")
PXT_DEF_STRING(sNull, "\x04\x00null")
PXT_DEF_STRING(sObject, "\x08\x00[Object]")
PXT_DEF_STRING(sFunction, "\x0A\x00[Function]")
PXT_DEF_STRING(sNaN, "\x03\x00NaN")
PXT_DEF_STRING(sInf, "\x08\x00Infinity")
PXT_DEF_STRING(sMInf, "\x09\x00-Infinity")
} // namespace pxt
#ifndef X86_64
namespace String_ {
//%
String mkEmpty() {
return mkString("", 0);
}
//%
String fromCharCode(int code) {
char buf[] = {(char)code, 0};
return mkString(buf, 1);
}
//%
String charAt(String s, int pos) {
if (s && 0 <= pos && pos < s->length) {
return fromCharCode(s->data[pos]);
} else {
return mkEmpty();
}
}
//%
TNumber charCodeAt(String s, int pos) {
if (s && 0 <= pos && pos < s->length) {
return fromInt(s->data[pos]);
} else {
return mkNaN();
}
}
//%
String concat(String s, String other) {
if (!s)
s = (String)sNull;
if (!other)
other = (String)sNull;
if (s->length == 0)
return (String)incrRC(other);
if (other->length == 0)
return (String)incrRC(s);
String r = mkString(NULL, s->length + other->length);
memcpy(r->data, s->data, s->length);
memcpy(r->data + s->length, other->data, other->length);
return r;
}
//%
int compare(String s, String that) {
if (s == that)
return 0;
// TODO this isn't quite right, in JS both `null < "foo"` and `null > "foo"` are false
if (!s)
return -1;
if (!that)
return 1;
int compareResult = strcmp(s->data, that->data);
if (compareResult < 0)
return -1;
else if (compareResult > 0)
return 1;
return 0;
}
//%
int length(String s) {
return s->length;
}
#define isspace(c) ((c) == ' ')
double mystrtod(const char *p, char **endp) {
while (isspace(*p))
p++;
double m = 1;
double v = 0;
int dot = 0;
if (*p == '+')
p++;
if (*p == '-') {
m = -1;
p++;
}
if (*p == '0' && (p[1] | 0x20) == 'x') {
return m * strtol(p, endp, 16);
}
while (*p) {
int c = *p - '0';
if (0 <= c && c <= 9) {
v *= 10;
v += c;
if (dot)
m /= 10;
} else if (!dot && *p == '.') {
dot = 1;
} else if (*p == 'e' || *p == 'E') {
break;
} else {
while (isspace(*p))
p++;
if (*p)
return NAN;
break;
}
p++;
}
v *= m;
if (*p) {
p++;
int pw = strtol(p, endp, 10);
v *= p10(pw);
}
else {
*endp = (char *) p;
}
return v;
}
//%
TNumber toNumber(String s) {
// JSCHECK
char *endptr;
double v = mystrtod(s->data, &endptr);
if (endptr != s->data + s->length)
v = NAN;
else if (v == 0.0 || v == -0.0)
v = v;
else if (!isnormal(v))
v = NAN;
return fromDouble(v);
}
//%
String substr(String s, int start, int length) {
if (length <= 0)
return mkEmpty();
if (start < 0)
start = max(s->length + start, 0);
length = min(length, s->length - start);
return mkString(s->data + start, length);
}
} // namespace String_
namespace Boolean_ {
//%
bool bang(int v) {
return v == 0;
}
} // namespace Boolean_
namespace pxt {
// ES5 9.5, 9.6
unsigned toUInt(TNumber v) {
if (isNumber(v))
return numValue(v);
if (isSpecial(v)) {
if ((intptr_t)v >> 6)
return 1;
else
return 0;
}
if (!v)
return 0;
double num = toDouble(v);
if (!isnormal(num))
return 0;
double rem = fmod(trunc(num), 4294967296.0);
if (rem < 0.0)
rem += 4294967296.0;
return (unsigned)rem;
}
int toInt(TNumber v) {
return (int)toUInt(v);
}
// only support double in tagged mode
double toDouble(TNumber v) {
if (isTagged(v))
return toInt(v);
// JSCHECK
ValType t = valType(v);
if (t == ValType::Number) {
BoxedNumber *p = (BoxedNumber *)v;
return p->num;
} else if (t == ValType::String) {
return toDouble(String_::toNumber((String)v));
} else {
return NAN;
}
}
float toFloat(TNumber v) {
// TODO optimize?
return (float)toDouble(v);
}
TNumber fromDouble(double r) {
#ifndef PXT_BOX_DEBUG
int ri = ((int)r) << 1;
if ((ri >> 1) == r)
return (TNumber)(ri | 1);
#endif
BoxedNumber *p = new BoxedNumber();
p->num = r;
MEMDBG("mkNum: %p", p);
return (TNumber)p;
}
TNumber fromFloat(float r) {
// TODO optimize
return fromDouble(r);
}
TNumber fromInt(int v) {
if (canBeTagged(v))
return TAG_NUMBER(v);
return fromDouble(v);
}
TNumber fromUInt(unsigned v) {
#ifndef PXT_BOX_DEBUG
if (v <= 0x3fffffff)
return TAG_NUMBER(v);
#endif
return fromDouble(v);
}
TValue fromBool(bool v) {
if (v)
return TAG_TRUE;
else
return TAG_FALSE;
}
TNumber eqFixup(TNumber v) {
if (v == TAG_NULL)
return TAG_UNDEFINED;
if (v == TAG_TRUE)
return TAG_NUMBER(1);
if (v == TAG_FALSE)
return TAG_NUMBER(0);
return v;
}
bool eqq_bool(TValue a, TValue b) {
// TODO improve this
if (a == b)
return true;
ValType ta = valType(a);
ValType tb = valType(b);
if (ta != tb)
return false;
if (ta == ValType::String)
return String_::compare((String)a, (String)b) == 0;
int aa = (int)a;
int bb = (int)b;
// if at least one of the values is tagged, they are not equal
if ((aa | bb) & 3)
return false;
if (ta == ValType::Number)
return toDouble(a) == toDouble(b);
else
return a == b;
}
bool eq_bool(TValue a, TValue b) {
return eqq_bool(eqFixup(a), eqFixup(b));
}
//%
bool switch_eq(TValue a, TValue b) {
if (eqq_bool(eqFixup(a), eqFixup(b))) {
decr(b);
return true;
}
return false;
}
} // namespace pxt
namespace langsupp {
//%
TValue ptreq(TValue a, TValue b) {
return eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TValue ptreqq(TValue a, TValue b) {
return eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TValue ptrneq(TValue a, TValue b) {
return !eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TValue ptrneqq(TValue a, TValue b) {
return !eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
} // namespace langsupp
#define NUMOP(op) return fromDouble(toDouble(a) op toDouble(b));
#define BITOP(op) return fromInt(toInt(a) op toInt(b));
namespace numops {
//%
int toBool(TValue v) {
if (isTagged(v)) {
if (v == TAG_UNDEFINED || v == TAG_NULL || v == TAG_FALSE || v == TAG_NUMBER(0))
return 0;
else
return 1;
}
ValType t = valType(v);
if (t == ValType::String) {
String s = (String)v;
if (s->length == 0)
return 0;
} else if (t == ValType::Number) {
double x = toDouble(v);
if (isnan(x) || x == 0.0 || x == -0.0)
return 0;
else
return 1;
}
return 1;
}
//%
int toBoolDecr(TValue v) {
if (v == TAG_TRUE)
return 1;
if (v == TAG_FALSE)
return 0;
int r = toBool(v);
decr(v);
return r;
}
// TODO
// The integer, non-overflow case for add/sub/bit opts is handled in assembly
//%
TNumber adds(TNumber a, TNumber b){NUMOP(+)}
//%
TNumber subs(TNumber a, TNumber b){NUMOP(-)}
//%
TNumber muls(TNumber a, TNumber b) {
if (bothNumbers(a, b)) {
int aa = (int)a;
int bb = (int)b;
// if both operands fit 15 bits, the result will not overflow int
if ((aa >> 15 == 0 || aa >> 15 == -1) && (bb >> 15 == 0 || bb >> 15 == -1)) {
// it may overflow 31 bit int though - use fromInt to convert properly
return fromInt((aa >> 1) * (bb >> 1));
}
}
NUMOP(*)
}
//%
TNumber div(TNumber a, TNumber b){NUMOP(/)}
//%
TNumber mod(TNumber a, TNumber b) {
if (isNumber(a) && isNumber(b) && numValue(b))
BITOP(%)
return fromDouble(fmod(toDouble(a), toDouble(b)));
}
//%
TNumber lsls(TNumber a, TNumber b){BITOP(<<)}
//%
TNumber lsrs(TNumber a, TNumber b) {
return fromUInt(toUInt(a) >> toUInt(b));
}
//%
TNumber asrs(TNumber a, TNumber b){BITOP(>>)}
//%
TNumber eors(TNumber a, TNumber b){BITOP (^)}
//%
TNumber orrs(TNumber a, TNumber b){BITOP(|)}
//%
TNumber bnot(TNumber a) {
return fromInt(~toInt(a));
}
//%
TNumber ands(TNumber a, TNumber b) {
BITOP(&)
}
#define CMPOP_RAW(op) \
if (bothNumbers(a, b)) \
return (int)a op((int)b); \
return toDouble(a) op toDouble(b);
#define CMPOP(op) \
if (bothNumbers(a, b)) \
return ((int)a op((int)b)) ? TAG_TRUE : TAG_FALSE; \
return toDouble(a) op toDouble(b) ? TAG_TRUE : TAG_FALSE;
//%
bool lt_bool(TNumber a, TNumber b){CMPOP_RAW(<)}
//%
TNumber le(TNumber a, TNumber b){CMPOP(<=)}
//%
TNumber lt(TNumber a, TNumber b){CMPOP(<)}
//%
TNumber ge(TNumber a, TNumber b){CMPOP(>=)}
//%
TNumber gt(TNumber a, TNumber b){CMPOP(>)}
//%
TNumber eq(TNumber a, TNumber b) {
return pxt::eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TNumber neq(TNumber a, TNumber b) {
return !pxt::eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TNumber eqq(TNumber a, TNumber b) {
return pxt::eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
//%
TNumber neqq(TNumber a, TNumber b) {
return !pxt::eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
void mycvt(double d, char *buf) {
if (d < 0) {
*buf++ = '-';
d = -d;
}
if (!d) {
*buf++ = '0';
*buf++ = 0;
return;
}
int pw = (int)log10(d);
int e = 1;
int beforeDot = 1;
if (0.000001 <= d && d < 1e21) {
if (pw > 0) {
d /= p10(pw);
beforeDot = 1 + pw;
}
} else {
d /= p10(pw);
e = pw;
}
int sig = 0;
while (sig < 17 || beforeDot > 0) {
// printf("%f sig=%d bd=%d\n", d, sig, beforeDot);
int c = (int)d;
*buf++ = '0' + c;
d = (d - c) * 10;
if (--beforeDot == 0)
*buf++ = '.';
if (sig || c)
sig++;
}
buf--;
while (*buf == '0')
buf--;
if (*buf == '.')
buf--;
buf++;
if (e != 1) {
*buf++ = 'e';
itoa(e, buf);
} else {
*buf = 0;
}
}
//%
String toString(TValue v) {
if (v == TAG_UNDEFINED)
return (String)(void *)sUndefined;
else if (v == TAG_FALSE)
return (String)(void *)sFalse;
else if (v == TAG_TRUE)
return (String)(void *)sTrue;
else if (v == TAG_NULL)
return (String)(void *)sNull;
ValType t = valType(v);
if (t == ValType::String) {
return (String)(void *)incr(v);
} else if (t == ValType::Number) {
char buf[64];
if (isNumber(v)) {
itoa(numValue(v), buf);
return mkString(buf);
}
double x = toDouble(v);
if (isnan(x))
return (String)(void *)sNaN;
if (isinf(x)) {
if (x < 0)
return (String)(void *)sMInf;
else
return (String)(void *)sInf;
}
mycvt(x, buf);
return mkString(buf);
} else if (t == ValType::Function) {
return (String)(void *)sFunction;
} else {
return (String)(void *)sObject;
}
}
} // namespace numops
namespace Math_ {
//%
TNumber pow(TNumber x, TNumber y) {
// regular pow() from math.h is 4k of code
return fromDouble(__builtin_powi(toDouble(x), toInt(y)));
}
//%
TNumber atan2(TNumber y, TNumber x) {
return fromDouble(::atan2(toDouble(y), toDouble(y)));
}
double randomDouble() {
return getRandom(UINT_MAX) / ((double)UINT_MAX + 1) +
getRandom(0xffffff) / ((double)UINT_MAX * 0xffffff);
}
//%
TNumber random() {
return fromDouble(randomDouble());
}
//%
TNumber randomRange(TNumber min, TNumber max) {
if (isNumber(min) && isNumber(max)) {
int mini = numValue(min);
int maxi = numValue(max);
if (mini > maxi) {
int temp = mini;
mini = maxi;
maxi = temp;
}
if (maxi == mini)
return fromInt(mini);
else
return fromInt(mini + getRandom(maxi - mini));
} else {
double mind = toDouble(min);
double maxd = toDouble(max);
if (mind > maxd) {
double temp = mind;
mind = maxd;
maxd = temp;
}
if (maxd == mind)
return fromDouble(mind);
else {
return fromDouble(mind + randomDouble() * (maxd - mind));
}
}
}
#define SINGLE(op) return fromDouble(::op(toDouble(x)));
//%
TNumber log(TNumber x){SINGLE(log)}
//%
TNumber log10(TNumber x){SINGLE(log10)}
//%
TNumber sqrt(TNumber x){SINGLE(sqrt)}
//%
TNumber floor(TNumber x){SINGLE(floor)}
//%
TNumber ceil(TNumber x){SINGLE(ceil)}
//%
TNumber trunc(TNumber x){SINGLE(trunc)}
//%
TNumber round(TNumber x) {
SINGLE(round)
}
//%
int imul(int x, int y) {
return x * y;
}
//%
int idiv(int x, int y) {
return x / y;
}
} // namespace Math_
namespace Array_ {
//%
RefCollection *mk(unsigned flags) {
auto r = new RefCollection();
MEMDBG("mkColl: %p", r);
return r;
}
//%
int length(RefCollection *c) {
return c->length();
}
//%
void setLength(RefCollection *c, int newLength) {
c->setLength(newLength);
}
//%
void push(RefCollection *c, TValue x) {
c->push(x);
}
//%
TValue pop(RefCollection *c) {
return c->pop();
}
//%
TValue getAt(RefCollection *c, int x) {
return c->getAt(x);
}
//%
void setAt(RefCollection *c, int x, TValue y) {
c->setAt(x, y);
}
//%
TValue removeAt(RefCollection *c, int x) {
return c->removeAt(x);
}
//%
void insertAt(RefCollection *c, int x, TValue value) {
c->insertAt(x, value);
}
//%
int indexOf(RefCollection *c, TValue x, int start) {
return c->indexOf(x, start);
}
//%
bool removeElement(RefCollection *c, TValue x) {
return c->removeElement(x);
}
} // namespace Array_
namespace pxt {
//%
void *ptrOfLiteral(int offset);
//%
unsigned programSize() {
return bytecode[17] * 2;
}
//%
int getConfig(int key, int defl) {
int *cfgData = *(int **)&bytecode[18];
for (int i = 0;; i += 2) {
if (cfgData[i] == key)
return cfgData[i + 1];
if (cfgData[i] == 0)
return defl;
}
}
} // namespace pxt
namespace pxtrt {
//%
TValue ldloc(RefLocal *r) {
return r->v;
}
//%
TValue ldlocRef(RefRefLocal *r) {
TValue tmp = r->v;
incr(tmp);
return tmp;
}
//%
void stloc(RefLocal *r, TValue v) {
r->v = v;
}
//%
void stlocRef(RefRefLocal *r, TValue v) {
decr(r->v);
r->v = v;
}
//%
RefLocal *mkloc() {
auto r = new RefLocal();
MEMDBG("mkloc: %p", r);
return r;
}
//%
RefRefLocal *mklocRef() {
auto r = new RefRefLocal();
MEMDBG("mklocRef: %p", r);
return r;
}
// All of the functions below unref() self. This is for performance reasons -
// the code emitter will not emit the unrefs for them.
//%
TValue ldfld(RefRecord *r, int idx) {
TValue tmp = r->ld(idx);
r->unref();
return tmp;
}
//%
TValue ldfldRef(RefRecord *r, int idx) {
TValue tmp = r->ldref(idx);
r->unref();
return tmp;
}
//%
void stfld(RefRecord *r, int idx, TValue val) {
r->st(idx, val);
r->unref();
}
//%
void stfldRef(RefRecord *r, int idx, TValue val) {
r->stref(idx, val);
r->unref();
}
// Store a captured local in a closure. It returns the action, so it can be chained.
//%
RefAction *stclo(RefAction *a, int idx, TValue v) {
// DBG("STCLO "); a->print(); DBG("@%d = %p\n", idx, (void*)v);
a->stCore(idx, v);
return a;
}
//%
void panic(int code) {
target_panic(code);
}
//%
String emptyToNull(String s) {
if (!s || s->length == 0)
return NULL;
return s;
}
//%
int ptrToBool(TValue p) {
if (p) {
decr(p);
return 1;
} else {
return 0;
}
}
//%
RefMap *mkMap() {
auto r = new RefMap();
MEMDBG("mkMap: %p", r);
return r;
}
//%
TValue mapGet(RefMap *map, unsigned key) {
int i = map->findIdx(key);
if (i < 0) {
map->unref();
return 0;
}
TValue r = incr(map->values.get(i));
map->unref();
return r;
}
//%
TValue mapGetRef(RefMap *map, unsigned key) {
return mapGet(map, key);
}
//%
void mapSet(RefMap *map, unsigned key, TValue val) {
int i = map->findIdx(key);
if (i < 0) {
map->keys.push((TValue)key);
map->values.push(val);
} else {
map->values.setRef(i, val);
}
map->unref();
}
//%
void mapSetRef(RefMap *map, unsigned key, TValue val) {
mapSet(map, key, val);
}
//
// Debugger
//
// This is only to be called once at the beginning of lambda function
//%
void *getGlobalsPtr() {
#ifdef DEVICE_GROUP_ID_USER
fiber_set_group(DEVICE_GROUP_ID_USER);
#endif
return globals;
}
//%
void runtimeWarning(String s) {
// noop for now
}
} // namespace pxtrt
#endif
namespace pxt {
//%
ValType valType(TValue v) {
if (isTagged(v)) {
if (!v)
return ValType::Undefined;
if (isNumber(v))
return ValType::Number;
if (v == TAG_TRUE || v == TAG_FALSE)
return ValType::Boolean;
else if (v == TAG_NULL)
return ValType::Object;
else {
oops();
return ValType::Object;
}
} else {
int tag = ((RefObject *)v)->vtable;
if (tag == PXT_REF_TAG_STRING)
return ValType::String;
else if (tag == PXT_REF_TAG_NUMBER)
return ValType::Number;
else if (tag == PXT_REF_TAG_ACTION || getVTable((RefObject *)v) == &RefAction_vtable)
return ValType::Function;
return ValType::Object;
}
}
PXT_DEF_STRING(sObjectTp, "\x06\x00object")
PXT_DEF_STRING(sBooleanTp, "\x07\x00boolean")
PXT_DEF_STRING(sStringTp, "\x06\x00string")
PXT_DEF_STRING(sNumberTp, "\x06\x00number")
PXT_DEF_STRING(sFunctionTp, "\x08\x00function")
PXT_DEF_STRING(sUndefinedTp, "\x09\x00undefined")
//%
String typeOf(TValue v) {
switch (valType(v)) {
case ValType::Undefined:
return (String)sUndefinedTp;
case ValType::Boolean:
return (String)sBooleanTp;
case ValType::Number:
return (String)sNumberTp;
case ValType::String:
return (String)sStringTp;
case ValType::Object:
return (String)sObjectTp;
case ValType::Function:
return (String)sFunctionTp;
default:
oops();
return 0;
}
}
// Maybe in future we will want separate print methods; for now ignore
void anyPrint(TValue v) {
if (valType(v) == ValType::Object) {
if (isRefCounted(v)) {
auto o = (RefObject *)v;
auto meth = ((RefObjectMethod)getVTable(o)->methods[1]);
if ((void *)meth == (void *)&anyPrint)
DMESG("[RefObject refs=%d vt=%p]", o->refcnt, o->vtable);
else
meth(o);
} else {
DMESG("[Native %p]", v);
}
} else {
#ifndef X86_64
String s = numops::toString(v);
DMESG("[%s %p = %s]", pxt::typeOf(v)->data, v, s->data);
decr((TValue)s);
#endif
}
}
void dtorDoNothing() {}
#define PRIM_VTABLE(name, sz) \
const VTable name = {sz, \
0, \
0, \
{ \
(void *)&dtorDoNothing, \
(void *)&anyPrint, \
}};
PRIM_VTABLE(string_vt, 0)
PRIM_VTABLE(image_vt, 0)
PRIM_VTABLE(buffer_vt, 0)
PRIM_VTABLE(number_vt, 12)
PRIM_VTABLE(action_vt, 0)
static const VTable *primVtables[] = {0, // 0
&string_vt, // 1
&buffer_vt, // 2
&image_vt, // 3
// filler:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
&number_vt, // 32
&action_vt, // 33
0};
VTable *getVTable(RefObject *r) {
if (r->vtable >= 34)
return (VTable *)((uintptr_t)r->vtable << vtableShift);
if (r->vtable == 0)
target_panic(100);
return (VTable *)primVtables[r->vtable];
}
} // namespace pxt