481 lines
11 KiB
C++
481 lines
11 KiB
C++
#include "pxt.h"
|
|
#include <map>
|
|
|
|
MicroBit uBit;
|
|
|
|
namespace pxt {
|
|
int incr(uint32_t e)
|
|
{
|
|
if (e) {
|
|
if (hasVTable(e))
|
|
((RefObject*)e)->ref();
|
|
else
|
|
((RefCounted*)e)->incr();
|
|
}
|
|
return e;
|
|
}
|
|
|
|
void decr(uint32_t e)
|
|
{
|
|
if (e) {
|
|
if (hasVTable(e))
|
|
((RefObject*)e)->unref();
|
|
else
|
|
((RefCounted*)e)->decr();
|
|
}
|
|
}
|
|
|
|
Action mkAction(int reflen, int totallen, int startptr)
|
|
{
|
|
check(0 <= reflen && reflen <= totallen, ERR_SIZE, 1);
|
|
check(reflen <= totallen && totallen <= 255, ERR_SIZE, 2);
|
|
check(bytecode[startptr] == 0xffff, ERR_INVALID_BINARY_HEADER, 3);
|
|
check(bytecode[startptr + 1] == 0, ERR_INVALID_BINARY_HEADER, 4);
|
|
|
|
uint32_t tmp = (uint32_t)&bytecode[startptr];
|
|
|
|
if (totallen == 0) {
|
|
return tmp; // no closure needed
|
|
}
|
|
|
|
void *ptr = ::operator new(sizeof(RefAction) + totallen * sizeof(uint32_t));
|
|
RefAction *r = new (ptr) RefAction();
|
|
r->len = totallen;
|
|
r->reflen = reflen;
|
|
r->func = (ActionCB)((tmp + 4) | 1);
|
|
memset(r->fields, 0, r->len * sizeof(uint32_t));
|
|
|
|
return (Action)r;
|
|
}
|
|
|
|
uint32_t runAction3(Action a, int arg0, int arg1, int arg2)
|
|
{
|
|
if (hasVTable(a))
|
|
return ((RefAction*)a)->runCore(arg0, arg1, arg2);
|
|
else {
|
|
check(*(uint16_t*)a == 0xffff, ERR_INVALID_BINARY_HEADER, 4);
|
|
return ((ActionCB)((a + 4) | 1))(NULL, arg0, arg1, arg2);
|
|
}
|
|
}
|
|
|
|
uint32_t runAction2(Action a, int arg0, int arg1)
|
|
{
|
|
return runAction3(a, arg0, arg1, 0);
|
|
}
|
|
|
|
uint32_t runAction1(Action a, int arg0)
|
|
{
|
|
return runAction3(a, arg0, 0, 0);
|
|
}
|
|
|
|
uint32_t runAction0(Action a)
|
|
{
|
|
return runAction3(a, 0, 0, 0);
|
|
}
|
|
|
|
RefRecord* mkClassInstance(int vtableOffset)
|
|
{
|
|
VTable *vtable = (VTable*)&bytecode[vtableOffset];
|
|
|
|
intcheck(vtable->methods[0] == &RefRecord_destroy, ERR_SIZE, 3);
|
|
intcheck(vtable->methods[1] == &RefRecord_print, ERR_SIZE, 4);
|
|
|
|
void *ptr = ::operator new(vtable->numbytes);
|
|
RefRecord *r = new (ptr) RefRecord(PXT_VTABLE_TO_INT(vtable));
|
|
memset(r->fields, 0, vtable->numbytes - sizeof(RefRecord));
|
|
return r;
|
|
}
|
|
|
|
uint32_t RefRecord::ld(int idx)
|
|
{
|
|
//intcheck((reflen == 255 ? 0 : reflen) <= idx && idx < len, ERR_OUT_OF_BOUNDS, 1);
|
|
return fields[idx];
|
|
}
|
|
|
|
uint32_t RefRecord::ldref(int idx)
|
|
{
|
|
//printf("LD %p len=%d reflen=%d idx=%d\n", this, len, reflen, idx);
|
|
//intcheck(0 <= idx && idx < reflen, ERR_OUT_OF_BOUNDS, 2);
|
|
uint32_t tmp = fields[idx];
|
|
incr(tmp);
|
|
return tmp;
|
|
}
|
|
|
|
void RefRecord::st(int idx, uint32_t v)
|
|
{
|
|
//intcheck((reflen == 255 ? 0 : reflen) <= idx && idx < len, ERR_OUT_OF_BOUNDS, 3);
|
|
fields[idx] = v;
|
|
}
|
|
|
|
void RefRecord::stref(int idx, uint32_t v)
|
|
{
|
|
//printf("ST %p len=%d reflen=%d idx=%d\n", this, len, reflen, idx);
|
|
//intcheck(0 <= idx && idx < reflen, ERR_OUT_OF_BOUNDS, 4);
|
|
decr(fields[idx]);
|
|
fields[idx] = v;
|
|
}
|
|
|
|
void RefObject::destroy() {
|
|
((RefObjectMethod)getVTable()->methods[0])(this);
|
|
}
|
|
|
|
void RefObject::print() {
|
|
((RefObjectMethod)getVTable()->methods[1])(this);
|
|
}
|
|
|
|
void RefRecord_destroy(RefRecord *r) {
|
|
auto tbl = r->getVTable();
|
|
uint8_t *refmask = (uint8_t*)&tbl->methods[tbl->userdata & 0xff];
|
|
int len = (tbl->numbytes >> 2) - 1;
|
|
for (int i = 0; i < len; ++i) {
|
|
if (refmask[i]) decr(r->fields[i]);
|
|
r->fields[i] = 0;
|
|
}
|
|
//RefRecord is allocated using placement new
|
|
r->~RefRecord();
|
|
::operator delete(r);
|
|
}
|
|
|
|
void RefRecord_print(RefRecord *r)
|
|
{
|
|
printf("RefRecord %p r=%d size=%d bytes\n", r, r->refcnt, r->getVTable()->numbytes);
|
|
}
|
|
|
|
void RefCollection::push(uint32_t x) {
|
|
if (isRef()) incr(x);
|
|
data.push_back(x);
|
|
}
|
|
|
|
uint32_t RefCollection::getAt(int x) {
|
|
if (in_range(x)) {
|
|
uint32_t tmp = data.at(x);
|
|
if (isRef()) incr(tmp);
|
|
return tmp;
|
|
}
|
|
else {
|
|
error(ERR_OUT_OF_BOUNDS);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void RefCollection::removeAt(int x) {
|
|
if (!in_range(x))
|
|
return;
|
|
|
|
if (isRef()) decr(data.at(x));
|
|
data.erase(data.begin()+x);
|
|
}
|
|
|
|
void RefCollection::setAt(int x, uint32_t y) {
|
|
if (!in_range(x))
|
|
return;
|
|
|
|
if (isRef()) {
|
|
decr(data.at(x));
|
|
incr(y);
|
|
}
|
|
data.at(x) = y;
|
|
}
|
|
|
|
int RefCollection::indexOf(uint32_t x, int start) {
|
|
if (!in_range(start))
|
|
return -1;
|
|
|
|
if (isString()) {
|
|
StringData *xx = (StringData*)x;
|
|
for (uint32_t i = start; i < data.size(); ++i) {
|
|
StringData *ee = (StringData*)data.at(i);
|
|
if (xx->len == ee->len && memcmp(xx->data, ee->data, xx->len) == 0)
|
|
return (int)i;
|
|
}
|
|
} else {
|
|
for (uint32_t i = start; i < data.size(); ++i)
|
|
if (data.at(i) == x)
|
|
return (int)i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int RefCollection::removeElement(uint32_t x) {
|
|
int idx = indexOf(x, 0);
|
|
if (idx >= 0) {
|
|
removeAt(idx);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
namespace Coll0 {
|
|
PXT_VTABLE_BEGIN(RefCollection, 0, 0)
|
|
PXT_VTABLE_END
|
|
}
|
|
namespace Coll1 {
|
|
PXT_VTABLE_BEGIN(RefCollection, 1, 0)
|
|
PXT_VTABLE_END
|
|
}
|
|
namespace Coll3 {
|
|
PXT_VTABLE_BEGIN(RefCollection, 3, 0)
|
|
PXT_VTABLE_END
|
|
}
|
|
|
|
RefCollection::RefCollection(uint16_t flags) : RefObject(0) {
|
|
switch (flags) {
|
|
case 0:
|
|
vtable = PXT_VTABLE_TO_INT(&Coll0::RefCollection_vtable);
|
|
break;
|
|
case 1:
|
|
vtable = PXT_VTABLE_TO_INT(&Coll1::RefCollection_vtable);
|
|
break;
|
|
case 3:
|
|
vtable = PXT_VTABLE_TO_INT(&Coll3::RefCollection_vtable);
|
|
break;
|
|
default:
|
|
error(ERR_SIZE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RefCollection::destroy()
|
|
{
|
|
if (this->isRef())
|
|
for (uint32_t i = 0; i < this->data.size(); ++i) {
|
|
decr(this->data[i]);
|
|
this->data[i] = 0;
|
|
}
|
|
this->data.resize(0);
|
|
delete this;
|
|
}
|
|
|
|
void RefCollection::print()
|
|
{
|
|
printf("RefCollection %p r=%d flags=%d size=%d [%p, ...]\n", this, refcnt, getFlags(), data.size(), data.size() > 0 ? data[0] : 0);
|
|
}
|
|
|
|
PXT_VTABLE_CTOR(RefAction) {}
|
|
|
|
// fields[] contain captured locals
|
|
void RefAction::destroy()
|
|
{
|
|
for (int i = 0; i < this->reflen; ++i) {
|
|
decr(fields[i]);
|
|
fields[i] = 0;
|
|
}
|
|
//RefAction is allocated using placement new
|
|
this->~RefAction();
|
|
::operator delete(this);
|
|
}
|
|
|
|
void RefAction::print()
|
|
{
|
|
printf("RefAction %p r=%d pc=0x%lx size=%d (%d refs)\n", this, refcnt, (const uint8_t*)func - (const uint8_t*)bytecode, len, reflen);
|
|
}
|
|
|
|
void RefLocal::print()
|
|
{
|
|
printf("RefLocal %p r=%d v=%d\n", this, refcnt, v);
|
|
}
|
|
|
|
void RefLocal::destroy()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
PXT_VTABLE_CTOR(RefLocal) {
|
|
v = 0;
|
|
}
|
|
|
|
PXT_VTABLE_CTOR(RefRefLocal) {
|
|
v = 0;
|
|
}
|
|
|
|
void RefRefLocal::print()
|
|
{
|
|
printf("RefRefLocal %p r=%d v=%p\n", this, refcnt, (void*)v);
|
|
}
|
|
|
|
void RefRefLocal::destroy()
|
|
{
|
|
decr(v);
|
|
delete this;
|
|
}
|
|
|
|
PXT_VTABLE_BEGIN(RefMap, 0, RefMapMarker)
|
|
PXT_VTABLE_END
|
|
RefMap::RefMap() : PXT_VTABLE_INIT(RefMap) {}
|
|
|
|
void RefMap::destroy() {
|
|
for (unsigned i = 0; i < data.size(); ++i) {
|
|
if (data[i].key & 1) {
|
|
decr(data[i].val);
|
|
}
|
|
data[i].val = 0;
|
|
}
|
|
data.resize(0);
|
|
delete this;
|
|
}
|
|
|
|
int RefMap::findIdx(uint32_t key) {
|
|
for (unsigned i = 0; i < data.size(); ++i) {
|
|
if (data[i].key >> 1 == key)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void RefMap::print()
|
|
{
|
|
printf("RefMap %p r=%d size=%d\n", this, refcnt, data.size());
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_MEMLEAKS
|
|
std::set<RefObject*> allptrs;
|
|
void debugMemLeaks()
|
|
{
|
|
printf("LIVE POINTERS:\n");
|
|
for(std::set<RefObject*>::iterator itr = allptrs.begin();itr!=allptrs.end();itr++)
|
|
{
|
|
(*itr)->print();
|
|
}
|
|
printf("\n");
|
|
}
|
|
#else
|
|
void debugMemLeaks() {}
|
|
#endif
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// An adapter for the API expected by the run-time.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
map<pair<int, int>, Action> handlersMap;
|
|
|
|
MicroBitEvent lastEvent;
|
|
|
|
// We have the invariant that if [dispatchEvent] is registered against the DAL
|
|
// for a given event, then [handlersMap] contains a valid entry for that
|
|
// event.
|
|
void dispatchEvent(MicroBitEvent e) {
|
|
|
|
lastEvent = e;
|
|
|
|
Action curr = handlersMap[{ e.source, e.value }];
|
|
if (curr)
|
|
runAction1(curr, e.value);
|
|
|
|
curr = handlersMap[{ e.source, MICROBIT_EVT_ANY }];
|
|
if (curr)
|
|
runAction1(curr, e.value);
|
|
}
|
|
|
|
void registerWithDal(int id, int event, Action a) {
|
|
Action prev = handlersMap[{ id, event }];
|
|
if (prev)
|
|
decr(prev);
|
|
else
|
|
uBit.messageBus.listen(id, event, dispatchEvent);
|
|
incr(a);
|
|
handlersMap[{ id, event }] = a;
|
|
}
|
|
|
|
void fiberDone(void *a)
|
|
{
|
|
decr((Action)a);
|
|
release_fiber();
|
|
}
|
|
|
|
|
|
void runInBackground(Action a) {
|
|
if (a != 0) {
|
|
incr(a);
|
|
create_fiber((void(*)(void*))runAction0, (void*)a, fiberDone);
|
|
}
|
|
}
|
|
|
|
|
|
void error(ERROR code, int subcode)
|
|
{
|
|
printf("Error: %d [%d]\n", code, subcode);
|
|
uBit.panic(42);
|
|
}
|
|
|
|
uint16_t *bytecode;
|
|
uint32_t *globals;
|
|
int numGlobals;
|
|
|
|
uint32_t *allocate(uint16_t sz)
|
|
{
|
|
uint32_t *arr = new uint32_t[sz];
|
|
memset(arr, 0, sz * 4);
|
|
return arr;
|
|
}
|
|
|
|
void checkStr(bool cond, const char *msg)
|
|
{
|
|
if (!cond) {
|
|
while (true) {
|
|
uBit.display.scroll(msg, 100);
|
|
uBit.sleep(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
int templateHash()
|
|
{
|
|
return ((int*)bytecode)[4];
|
|
}
|
|
|
|
int programHash()
|
|
{
|
|
return ((int*)bytecode)[6];
|
|
}
|
|
|
|
int getNumGlobals()
|
|
{
|
|
return bytecode[16];
|
|
}
|
|
|
|
void exec_binary(int32_t *pc)
|
|
{
|
|
// XXX re-enable once the calibration code is fixed and [editor/embedded.ts]
|
|
// properly prepends a call to [internal_main].
|
|
// ::touch_develop::internal_main();
|
|
|
|
// unique group for radio based on source hash
|
|
// ::touch_develop::micro_bit::radioDefaultGroup = programHash();
|
|
|
|
// repeat error 4 times and restart as needed
|
|
microbit_panic_timeout(4);
|
|
|
|
int32_t ver = *pc++;
|
|
checkStr(ver == 0x4209, ":( Bad runtime version");
|
|
|
|
bytecode = *((uint16_t**)pc++); // the actual bytecode is here
|
|
globals = allocate(getNumGlobals());
|
|
|
|
// just compare the first word
|
|
checkStr(((uint32_t*)bytecode)[0] == 0x923B8E70 &&
|
|
templateHash() == *pc,
|
|
":( Failed partial flash");
|
|
|
|
uint32_t startptr = (uint32_t)bytecode;
|
|
startptr += 48; // header
|
|
startptr |= 1; // Thumb state
|
|
|
|
((uint32_t (*)())startptr)();
|
|
|
|
#ifdef DEBUG_MEMLEAKS
|
|
pxt::debugMemLeaks();
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
void start()
|
|
{
|
|
exec_binary((int32_t*)functionsAndBytecode);
|
|
}
|
|
}
|
|
|
|
// vim: ts=2 sw=2 expandtab
|