diff --git a/libs/core/ManagedBuffer.cpp b/libs/core/ManagedBuffer.cpp new file mode 100644 index 00000000..25b9cb26 --- /dev/null +++ b/libs/core/ManagedBuffer.cpp @@ -0,0 +1,373 @@ +#include "MicroBit.h" +#include "ManagedBuffer.h" +#include + +static const char empty[] __attribute__ ((aligned (4))) = "\xff\xff\0\0\0"; + +/** + * Internal constructor helper. + * Configures this ManagedBuffer to refer to the static empty buffer. + */ +void ManagedBuffer::initEmpty() +{ + ptr = (BufferData*)(void*)empty; +} + +/** + * Default Constructor. + * Creates an empty ManagedBuffer. + * + * Example: + * @code + * ManagedBuffer p(); + * @endcode + */ +ManagedBuffer::ManagedBuffer() +{ + initEmpty(); +} + +/** + * Constructor. + * Creates an empty ManagedBuffer of the given size. + * + * @param length The length of the buffer to create. + * + * Example: + * @code + * ManagedBuffer p(16); // Creates a ManagedBuffer 16 bytes long. + * @endcode + */ +ManagedBuffer::ManagedBuffer(int length) +{ + this->init(NULL, length); +} + +/** + * Constructor. + * Creates a new ManagedBuffer of the given size, + * and fills it with the data provided. + * + * @param data The data with which to fill the buffer. + * @param length The length of the buffer to create. + * + * Example: + * @code + * uint8_t buf = {13,5,2}; + * ManagedBuffer p(buf, 3); // Creates a ManagedBuffer 3 bytes long. + * @endcode + */ +ManagedBuffer::ManagedBuffer(uint8_t *data, int length) +{ + this->init(data, length); +} + +/** + * Copy Constructor. + * Add ourselves as a reference to an existing ManagedBuffer. + * + * @param buffer The ManagedBuffer to reference. + * + * Example: + * @code + * ManagedBuffer p(); + * ManagedBuffer p2(i); // Refers to the same buffer as p. + * @endcode + */ +ManagedBuffer::ManagedBuffer(const ManagedBuffer &buffer) +{ + ptr = buffer.ptr; + ptr->incr(); +} + +/** + * Constructor. + * Create a buffer from a raw BufferData pointer. It will ptr->incr(). This is to be used by specialized runtimes. + * + * @param p The pointer to use. + */ +ManagedBuffer::ManagedBuffer(BufferData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Internal constructor-initialiser. + * + * @param data The data with which to fill the buffer. + * @param length The length of the buffer to create. + * + */ +void ManagedBuffer::init(uint8_t *data, int length) +{ + if (length <= 0) { + initEmpty(); + return; + } + + ptr = (BufferData *) malloc(sizeof(BufferData) + length); + ptr->init(); + + ptr->length = length; + + // Copy in the data buffer, if provided. + if (data) + memcpy(ptr->payload, data, length); + else + memset(ptr->payload, 0, length); +} + +/** + * Destructor. + * Removes buffer resources held by the instance. + */ +ManagedBuffer::~ManagedBuffer() +{ + ptr->decr(); +} + +/** + * Copy assign operation. + * + * Called when one ManagedBuffer is assigned the value of another using the '=' operator. + * Decrements our reference count and free up the buffer as necessary. + * Then, update our buffer to refer to that of the supplied ManagedBuffer, + * and increase its reference count. + * + * @param p The ManagedBuffer to reference. + * + * Example: + * @code + * uint8_t buf = {13,5,2}; + * ManagedBuffer p1(16); + * ManagedBuffer p2(buf, 3); + * + * p1 = p2; + * @endcode + */ +ManagedBuffer& ManagedBuffer::operator = (const ManagedBuffer &p) +{ + if(ptr == p.ptr) + return *this; + + ptr->decr(); + ptr = p.ptr; + ptr->incr(); + + return *this; +} + +/** + * Equality operation. + * + * Called when one ManagedBuffer is tested to be equal to another using the '==' operator. + * + * @param p The ManagedBuffer to test ourselves against. + * @return true if this ManagedBuffer is identical to the one supplied, false otherwise. + * + * Example: + * @code + * + * uint8_t buf = {13,5,2}; + * ManagedBuffer p1(16); + * ManagedBuffer p2(buf, 3); + * + * if(p1 == p2) // will be true + * uBit.display.scroll("same!"); + * @endcode + */ +bool ManagedBuffer::operator== (const ManagedBuffer& p) +{ + if (ptr == p.ptr) + return true; + else + return (ptr->length == p.ptr->length && (memcmp(ptr->payload, p.ptr->payload, ptr->length)==0)); +} + +/** + * Sets the byte at the given index to value provided. + * @param position The index of the byte to change. + * @param value The new value of the byte (0-255). + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1.setByte(0,255); // Sets the firts byte in the buffer to the value 255. + * @endcode + */ +int ManagedBuffer::setByte(int position, uint8_t value) +{ + if (0 <= position && position < ptr->length) + { + ptr->payload[position] = value; + return MICROBIT_OK; + } + else + { + return MICROBIT_INVALID_PARAMETER; + } +} + +/** + * Determines the value of the given byte in the buffer. + * + * @param position The index of the byte to read. + * @return The value of the byte at the given position, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1.setByte(0,255); // Sets the firts byte in the buffer to the value 255. + * p1.getByte(0); // Returns 255. + * @endcode + */ +int ManagedBuffer::getByte(int position) +{ + if (0 <= position && position < ptr->length) + return ptr->payload[position]; + else + return MICROBIT_INVALID_PARAMETER; +} + +/** + * Get current ptr, do not decr() it, and set the current instance to an empty buffer. + * This is to be used by specialized runtimes which pass BufferData around. + */ +BufferData *ManagedBuffer::leakData() +{ + BufferData* res = ptr; + initEmpty(); + return res; +} + + +int ManagedBuffer::fill(uint8_t value, int offset, int length) +{ + if (offset < 0 || offset > ptr->length) + return MICROBIT_INVALID_PARAMETER; + if (length < 0) + length = ptr->length; + length = min(length, ptr->length - offset); + + memset(ptr->payload + offset, value, length); + + return MICROBIT_OK; +} + +ManagedBuffer ManagedBuffer::slice(int offset, int length) const +{ + offset = min(ptr->length, offset); + if (length < 0) + length = ptr->length; + length = min(length, ptr->length - offset); + return ManagedBuffer(ptr->payload + offset, length); +} + +void ManagedBuffer::shift(int offset, int start, int len) +{ + if (len < 0) len = ptr->length - start; + if (start < 0 || start + len > ptr->length || start + len < start + || len == 0 || offset == 0 || offset == INT_MIN) return; + if (offset <= -len || offset >= len) { + fill(0); + return; + } + + uint8_t *data = ptr->payload + start; + if (offset < 0) { + offset = -offset; + memmove(data + offset, data, len - offset); + memset(data, 0, offset); + } else { + len = len - offset; + memmove(data, data + offset, len); + memset(data + len, 0, offset); + } +} + +void ManagedBuffer::rotate(int offset, int start, int len) +{ + if (len < 0) len = ptr->length - start; + if (start < 0 || start + len > ptr-> length || start + len < start + || len == 0 || offset == 0 || offset == INT_MIN) return; + + if (offset < 0) + offset += len << 8; // try to make it positive + offset %= len; + if (offset < 0) + offset += len; + + uint8_t *data = ptr->payload + start; + + uint8_t *n_first = data + offset; + uint8_t *first = data; + uint8_t *next = n_first; + uint8_t *last = data + len; + + while (first != next) { + uint8_t tmp = *first; + *first++ = *next; + *next++ = tmp; + if (next == last) { + next = n_first; + } else if (first == n_first) { + n_first = next; + } + } +} + +int ManagedBuffer::writeBuffer(int dstOffset, const ManagedBuffer &src, int srcOffset, int length) +{ + if (length < 0) + length = src.length(); + + if (srcOffset < 0 || dstOffset < 0 || dstOffset > ptr->length) + return MICROBIT_INVALID_PARAMETER; + + length = min(src.length() - srcOffset, ptr->length - dstOffset); + + if (length < 0) + return MICROBIT_INVALID_PARAMETER; + + if (ptr == src.ptr) { + memmove(getBytes() + dstOffset, src.ptr->payload + srcOffset, length); + } else { + memcpy(getBytes() + dstOffset, src.ptr->payload + srcOffset, length); + } + + return MICROBIT_OK; +} + +int ManagedBuffer::writeBytes(int offset, uint8_t *src, int length, bool swapBytes) +{ + if (offset < 0 || length < 0 || offset + length > ptr->length) + return MICROBIT_INVALID_PARAMETER; + + if (swapBytes) { + uint8_t *p = ptr->payload + offset + length; + for (int i = 0; i < length; ++i) + *--p = src[i]; + } else { + memcpy(ptr->payload + offset, src, length); + } + + return MICROBIT_OK; +} + +int ManagedBuffer::readBytes(uint8_t *dst, int offset, int length, bool swapBytes) const +{ + if (offset < 0 || length < 0 || offset + length > ptr->length) + return MICROBIT_INVALID_PARAMETER; + + if (swapBytes) { + uint8_t *p = ptr->payload + offset + length; + for (int i = 0; i < length; ++i) + dst[i] = *--p; + } else { + memcpy(dst, ptr->payload + offset, length); + } + + return MICROBIT_OK; +} diff --git a/libs/core/ManagedBuffer.h b/libs/core/ManagedBuffer.h new file mode 100644 index 00000000..991dd8d7 --- /dev/null +++ b/libs/core/ManagedBuffer.h @@ -0,0 +1,257 @@ +#ifndef MICROBIT_MANAGED_BUFFER_H +#define MICROBIT_MANAGED_BUFFER_H + +#include "mbed.h" +#include "RefCounted.h" + +struct BufferData : RefCounted +{ + uint16_t length; // The length of the payload in bytes + uint8_t payload[0]; // ManagedBuffer data +}; + +/** + * Class definition for a ManagedBuffer. + * A ManagedBuffer holds a series of bytes, used with MicroBitRadio channels and in other places. + * n.b. This is a mutable, managed type. + */ +class ManagedBuffer +{ + BufferData *ptr; // Pointer to payload data + + public: + + /** + * Default Constructor. + * Creates an empty ManagedBuffer. The 'ptr' field in all empty buffers is shared. + * + * Example: + * @code + * ManagedBuffer p(); + * @endcode + */ + ManagedBuffer(); + + /** + * Constructor. + * Creates a new ManagedBuffer of the given size. + * + * @param length The length of the buffer to create. + * + * Example: + * @code + * ManagedBuffer p(16); // Creates a ManagedBuffer 16 bytes long. + * @endcode + */ + ManagedBuffer(int length); + + /** + * Constructor. + * Creates an empty ManagedBuffer of the given size, + * and fills it with the data provided. + * + * @param data The data with which to fill the buffer. + * @param length The length of the buffer to create. + * + * Example: + * @code + * uint8_t buf[] = {13,5,2}; + * ManagedBuffer p(buf, 3); // Creates a ManagedBuffer 3 bytes long. + * @endcode + */ + ManagedBuffer(uint8_t *data, int length); + + /** + * Copy Constructor. + * Add ourselves as a reference to an existing ManagedBuffer. + * + * @param buffer The ManagedBuffer to reference. + * + * Example: + * @code + * ManagedBuffer p(); + * ManagedBuffer p2(i); // Refers to the same buffer as p. + * @endcode + */ + ManagedBuffer(const ManagedBuffer &buffer); + + /** + * Constructor. + * Create a buffer from a raw BufferData pointer. It will ptr->incr(). This is to be used by specialized runtimes. + * + * @param p The pointer to use. + */ + ManagedBuffer(BufferData *p); + + /** + * Internal constructor helper. + * Configures this ManagedBuffer to refer to the static empty buffer. + */ + void initEmpty(); + + /** + * Internal constructor-initialiser. + * + * @param data The data with which to fill the buffer. + * @param length The length of the buffer to create. + * + */ + void init(uint8_t *data, int length); + + /** + * Destructor. + * Removes buffer resources held by the instance. + */ + ~ManagedBuffer(); + + /** + * Provide an array containing the buffer data. + * @return The contents of this buffer, as an array of bytes. + */ + uint8_t *getBytes() + { + return ptr->payload; + } + + /** + * Get current ptr, do not decr() it, and set the current instance to an empty buffer. + * This is to be used by specialized runtimes which pass BufferData around. + */ + BufferData *leakData(); + + /** + * Copy assign operation. + * + * Called when one ManagedBuffer is assigned the value of another using the '=' operator. + * Decrements our reference count and free up the buffer as necessary. + * Then, update our buffer to refer to that of the supplied ManagedBuffer, + * and increase its reference count. + * + * @param p The ManagedBuffer to reference. + * + * Example: + * @code + * uint8_t buf = {13,5,2}; + * ManagedBuffer p1(16); + * ManagedBuffer p2(buf, 3); + * + * p1 = p2; + * @endcode + */ + ManagedBuffer& operator = (const ManagedBuffer& p); + + /** + * Array access operation (read). + * + * Called when a ManagedBuffer is dereferenced with a [] operation. + * Transparently map this through to the underlying payload for elegance of programming. + * + * Example: + * @code + * ManagedBuffer p1(16); + * uint8_t data = p1[0]; + * @endcode + */ + uint8_t operator [] (int i) const + { + return ptr->payload[i]; + } + + /** + * Array access operation (modify). + * + * Called when a ManagedBuffer is dereferenced with a [] operation. + * Transparently map this through to the underlying payload for elegance of programming. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1[0] = 42; + * @endcode + */ + uint8_t& operator [] (int i) + { + return ptr->payload[i]; + } + + /** + * Equality operation. + * + * Called when one ManagedBuffer is tested to be equal to another using the '==' operator. + * + * @param p The ManagedBuffer to test ourselves against. + * @return true if this ManagedBuffer is identical to the one supplied, false otherwise. + * + * Example: + * @code + * + * uint8_t buf = {13,5,2}; + * ManagedBuffer p1(16); + * ManagedBuffer p2(buf, 3); + * + * if(p1 == p2) // will be true + * uBit.display.scroll("same!"); + * @endcode + */ + bool operator== (const ManagedBuffer& p); + + /** + * Sets the byte at the given index to value provided. + * @param position The index of the byte to change. + * @param value The new value of the byte (0-255). + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * @endcode + */ + int setByte(int position, uint8_t value); + + /** + * Determines the value of the given byte in the buffer. + * + * @param position The index of the byte to read. + * @return The value of the byte at the given position, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * p1.getByte(0); // Returns 255. + * @endcode + */ + int getByte(int position); + + /** + * Gets number of bytes in this buffer + * @return The size of the buffer in bytes. + * + * Example: + * @code + * ManagedBuffer p1(16); + * p1.length(); // Returns 16. + * @endcode + */ + int length() const { return ptr->length; } + + int fill(uint8_t value, int offset = 0, int length = -1); + + ManagedBuffer slice(int offset = 0, int length = -1) const; + + void shift(int offset, int start = 0, int length = -1); + + void rotate(int offset, int start = 0, int length = -1); + + int readBytes(uint8_t *dst, int offset, int length, bool swapBytes = false) const; + + int writeBytes(int dstOffset, uint8_t *src, int length, bool swapBytes = false); + + int writeBuffer(int dstOffset, const ManagedBuffer &src, int srcOffset = 0, int length = -1); + + bool isReadOnly() const { return ptr->isReadOnly(); } +}; + +#endif + diff --git a/libs/core/pxt.cpp b/libs/core/pxt.cpp new file mode 100644 index 00000000..4f6188d4 --- /dev/null +++ b/libs/core/pxt.cpp @@ -0,0 +1,472 @@ +#include "pxt.h" +#include + +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); + delete 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; + } + } + + 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); + } + + 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; + } + } + + 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() + { + } + + 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); + } + + 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); + } + + 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 allptrs; + void debugMemLeaks() + { + printf("LIVE POINTERS:\n"); + for(std::set::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, 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 diff --git a/libs/core/pxt.h b/libs/core/pxt.h new file mode 100644 index 00000000..72942b63 --- /dev/null +++ b/libs/core/pxt.h @@ -0,0 +1,334 @@ +#ifndef __PXT_H +#define __PXT_H + +// #define DEBUG_MEMLEAKS 1 + +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#include "MicroBit.h" +#include "MicroBitImage.h" +#include "ManagedString.h" +#include "ManagedType.h" +#include "ManagedBuffer.h" + +#define printf(...) uBit.serial.printf(__VA_ARGS__) +// #define printf(...) + +#define intcheck(...) check(__VA_ARGS__) +//#define intcheck(...) do {} while (0) + +#include +#include +#include +#include + +#ifdef DEBUG_MEMLEAKS +#include +#endif + +extern MicroBit uBit; + +namespace pxt { + typedef uint32_t Action; + typedef uint32_t ImageLiteral; + + + typedef enum { + ERR_INVALID_BINARY_HEADER = 5, + ERR_OUT_OF_BOUNDS = 8, + ERR_REF_DELETED = 7, + ERR_SIZE = 9, + } ERROR; + + extern const uint32_t functionsAndBytecode[]; + extern uint32_t *globals; + extern uint16_t *bytecode; + class RefRecord; + + // Utility functions + extern MicroBitEvent lastEvent; + void registerWithDal(int id, int event, Action a); + void runInBackground(Action a); + uint32_t runAction3(Action a, int arg0, int arg1, int arg2); + uint32_t runAction2(Action a, int arg0, int arg1); + uint32_t runAction1(Action a, int arg0); + uint32_t runAction0(Action a); + Action mkAction(int reflen, int totallen, int startptr); + void error(ERROR code, int subcode = 0); + void exec_binary(uint16_t *pc); + void start(); + void debugMemLeaks(); + // allocate [sz] words and clear them + uint32_t *allocate(uint16_t sz); + int templateHash(); + int programHash(); + int getNumGlobals(); + RefRecord* mkClassInstance(int vtableOffset); + + // The standard calling convention is: + // - when a pointer is loaded from a local/global/field etc, and incr()ed + // (in other words, its presence on stack counts as a reference) + // - after a function call, all pointers are popped off the stack and decr()ed + // This does not apply to the RefRecord and st/ld(ref) methods - they unref() + // the RefRecord* this. + int incr(uint32_t e); + void decr(uint32_t e); + + inline void *ptrOfLiteral(int offset) + { + return &bytecode[offset]; + } + + inline ImageData* imageBytes(int offset) + { + return (ImageData*)(void*)&bytecode[offset]; + } + + // Checks if object has a VTable, or if its RefCounted* from the runtime. + inline bool hasVTable(uint32_t e) + { + return (*((uint32_t*)e) & 1) == 0; + } + + inline void check(int cond, ERROR code, int subcode = 0) + { + if (!cond) error(code, subcode); + } + + + class RefObject; +#ifdef DEBUG_MEMLEAKS + extern std::set allptrs; +#endif + + typedef void (*RefObjectMethod)(RefObject *self); + typedef void *PVoid; + typedef void **PPVoid; + + const PPVoid RefMapMarker = (PPVoid)(void*)43; + + struct VTable { + uint16_t numbytes; // in the entire object, including the vtable pointer + uint16_t userdata; + PVoid *ifaceTable; + PVoid methods[2]; // we only use up to two methods here; pxt will generate more + // refmask sits at &methods[nummethods] + }; + + const int vtableShift = 2; + + // A base abstract class for ref-counted objects. + class RefObject + { + public: + uint16_t refcnt; + uint16_t vtable; + + RefObject(uint16_t vt) + { + refcnt = 2; + vtable = vt; +#ifdef DEBUG_MEMLEAKS + allptrs.insert(this); +#endif + } + + inline VTable *getVTable() { + return (VTable*)(vtable << vtableShift); + } + + void destroy(); + void print(); + + // Call to disable pointer tracking on the current instance (in destructor or some other hack) + inline void untrack() { +#ifdef DEBUG_MEMLEAKS + allptrs.erase(this); +#endif + } + + // Increment/decrement the ref-count. Decrementing to zero deletes the current object. + inline void ref() + { + check(refcnt > 0, ERR_REF_DELETED); + //printf("INCR "); this->print(); + refcnt += 2; + } + + inline void unref() + { + //printf("DECR "); this->print(); + refcnt -= 2; + if (refcnt == 0) { + destroy(); + } + } + }; + + // A ref-counted collection of either primitive or ref-counted objects (String, Image, + // user-defined record, another collection) + class RefCollection + : public RefObject + { + public: + // 1 - collection of refs (need decr) + // 2 - collection of strings (in fact we always have 3, never 2 alone) + inline uint32_t getFlags() { return getVTable()->userdata; } + inline bool isRef() { return getFlags() & 1; } + inline bool isString() { return getFlags() & 2; } + + std::vector data; + + RefCollection(uint16_t f); + + inline bool in_range(int x) { + return (0 <= x && x < (int)data.size()); + } + + inline int length() { return data.size(); } + + void destroy(); + void print(); + + void push(uint32_t x); + uint32_t getAt(int x); + void removeAt(int x); + void setAt(int x, uint32_t y); + int indexOf(uint32_t x, int start); + int removeElement(uint32_t x); + }; + + struct MapEntry { + uint32_t key; + uint32_t val; + }; + + class RefMap + : public RefObject + { + public: + std::vector data; + + RefMap(); + void destroy(); + void print(); + int findIdx(uint32_t key); + }; + + // A ref-counted, user-defined JS object. + class RefRecord + : public RefObject + { + public: + // The object is allocated, so that there is space at the end for the fields. + uint32_t fields[]; + + RefRecord(uint16_t v) : RefObject(v) {} + + uint32_t ld(int idx); + uint32_t ldref(int idx); + void st(int idx, uint32_t v); + void stref(int idx, uint32_t v); + }; + + // these are needed when constructing vtables for user-defined classes + void RefRecord_destroy(RefRecord *r); + void RefRecord_print(RefRecord *r); + + class RefAction; + typedef uint32_t (*ActionCB)(uint32_t *captured, uint32_t arg0, uint32_t arg1, uint32_t arg2); + + // Ref-counted function pointer. It's currently always a ()=>void procedure pointer. + class RefAction + : public RefObject + { + public: + // This is the same as for RefRecord. + uint8_t len; + uint8_t reflen; + ActionCB func; // The function pointer + // fields[] contain captured locals + uint32_t fields[]; + + void destroy(); + void print(); + + RefAction(); + + inline void stCore(int idx, uint32_t v) + { + //printf("ST [%d] = %d ", idx, v); this->print(); + intcheck(0 <= idx && idx < len, ERR_OUT_OF_BOUNDS, 10); + intcheck(fields[idx] == 0, ERR_OUT_OF_BOUNDS, 11); // only one assignment permitted + fields[idx] = v; + } + + inline uint32_t runCore(int arg0, int arg1, int arg2) // internal; use runAction*() functions + { + this->ref(); + uint32_t r = this->func(&this->fields[0], arg0, arg1, arg2); + this->unref(); + return r; + } + }; + + // These two are used to represent locals written from inside inline functions + class RefLocal + : public RefObject + { + public: + uint32_t v; + void destroy(); + void print(); + RefLocal(); + }; + + class RefRefLocal + : public RefObject + { + public: + uint32_t v; + void destroy(); + void print(); + RefRefLocal(); + }; +} + +// The ARM Thumb generator in the JavaScript code is parsing +// the hex file and looks for the magic numbers as present here. +// +// Then it fetches function pointer addresses from there. + +#define PXT_SHIMS_BEGIN \ +namespace pxt { \ + const uint32_t functionsAndBytecode[] __attribute__((aligned(0x20))) = { \ + 0x08010801, 0x42424242, 0x08010801, 0x8de9d83e, + +#define PXT_SHIMS_END }; } + +#pragma GCC diagnostic ignored "-Wpmf-conversions" + +#define PXT_VTABLE_TO_INT(vt) ((uint32_t)(vt) >> vtableShift) +#define PXT_VTABLE_BEGIN(classname, flags, iface) \ +const VTable classname ## _vtable \ + __attribute__((aligned(1 << vtableShift))) \ + = { \ + sizeof(classname), \ + flags, \ + iface, \ + { \ + (void*)&classname::destroy, \ + (void*)&classname::print, + +#define PXT_VTABLE_END } }; + +#define PXT_VTABLE_INIT(classname) \ + RefObject(PXT_VTABLE_TO_INT(&classname ## _vtable)) + +#define PXT_VTABLE_CTOR(classname) \ + PXT_VTABLE_BEGIN(classname, 0, 0) PXT_VTABLE_END \ + classname::classname() : PXT_VTABLE_INIT(classname) + +#endif + +// vim: ts=2 sw=2 expandtab diff --git a/libs/core/pxt.json b/libs/core/pxt.json index a99f7978..247344c5 100644 --- a/libs/core/pxt.json +++ b/libs/core/pxt.json @@ -4,6 +4,10 @@ "installedVersion": "tsmdvf", "files": [ "README.md", + "ManagedBuffer.cpp", + "ManagedBuffer.h", + "pxt.cpp", + "pxt.h", "dal.d.ts", "enums.d.ts", "shims.d.ts", diff --git a/pxtarget.json b/pxtarget.json index d3a3e8d8..bce540f2 100644 --- a/pxtarget.json +++ b/pxtarget.json @@ -166,10 +166,10 @@ }, "compileService": { "yottaTarget": "bbc-microbit-classic-gcc", - "yottaCorePackage": "pxt-calliope-core", - "githubCorePackage": "microsoft/pxt-calliope-core", - "gittag": "v0.5.16", - "serviceId": "calliope" + "yottaCorePackage": "microbit", + "githubCorePackage": "calliope-mini/microbit", + "gittag": "v1.0.2-calliope", + "serviceId": "calliopemini" }, "serial": { "manufacturerFilter": "^mbed$",