| /* |
| |
| # Instructions |
| |
| Make a copy of this file and name it exactly `microvium_port.h`. Put the copy |
| somewhere in your project where it is accessible by a `#include |
| "microvium_port.h"` directive. |
| |
| Customize your copy of the port file with platform-specific configurations. |
| |
| The recommended workflow is to keep the vm source files separate from your |
| custom port file, so that you can update the vm source files regularly with bug |
| fixes and improvement from the original github or npm repository. |
| |
| */ |
| #pragma once |
| |
| #include <assert.h> |
| #include <cheri-builtins.h> |
| #include <compartment.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| /** |
| * The version of the port interface that this file is implementing. |
| */ |
| #define MVM_PORT_VERSION 1 |
| |
| /** |
| * Number of bytes to use for the stack. |
| * |
| * Note: the that stack is fixed-size, even though the heap grows dynamically |
| * as-needed. |
| */ |
| #define MVM_STACK_SIZE 256 |
| |
| /** |
| * When more space is needed for the VM heap, the VM will malloc blocks with a |
| * minimum of this size from the host. |
| * |
| * Note that the VM can also allocate blocks larger than this. It will do so if |
| * it needs a larger contiguous space than will fit in a standard block, and |
| * also during heap compaction (`runGC`) where it defragments the heap into as |
| * few mallocd blocks as possible to make access more efficient. |
| */ |
| #define MVM_ALLOCATION_BUCKET_SIZE 256 |
| |
| /** |
| * The maximum size of the virtual heap before an MVM_E_OUT_OF_MEMORY error is |
| * given. |
| * |
| * When the VM reaches this level, it will first try to perform a garbage |
| * collection cycle. If a GC cycle does not free enough memory, a fatal |
| * MVM_E_OUT_OF_MEMORY error is given. |
| * |
| * Note: this is the space in the virtual heap (the amount consumed by |
| * allocations in the VM), not the physical space malloc'd from the host, the |
| * latter of which can peak at roughly twice the virtual space during a garbage |
| * collection cycle in the worst case. |
| */ |
| #define MVM_MAX_HEAP_SIZE 1024 |
| |
| /** |
| * Set to 1 if a `void*` pointer is natively 16-bit (e.g. if compiling for |
| * 16-bit architectures). This allows some optimizations since then a native |
| * pointer can fit in a Microvium value slot. |
| */ |
| #define MVM_NATIVE_POINTER_IS_16_BIT 0 |
| |
| /** |
| * Set to 1 to compile in support for floating point operations (64-bit). This |
| * adds significant cost in smaller devices, but is required if you want the VM |
| * to be compliant with the ECMAScript standard. |
| * |
| * When float support is disabled, operations on floats will throw. |
| */ |
| #define MVM_SUPPORT_FLOAT 0 |
| |
| #if MVM_SUPPORT_FLOAT |
| |
| /** |
| * The type to use for double-precision floating point. Note that anything other |
| * than an IEEE 754 double-precision float is not compliant with the ECMAScript |
| * spec and results may not always be as expected. Also remember that the |
| * bytecode is permitted to have floating point literals embedded in it, and |
| * these must match the exact format specification used here if doubles are to |
| * persist correctly across a snapshot. |
| * |
| * Note that on some embedded systems, the `double` type is actually 32-bit, so |
| * this may need to be `long double` or whatever the equivalent 64-bit type is |
| * on your system. |
| */ |
| # define MVM_FLOAT64 double |
| |
| /** |
| * Value to use for NaN |
| */ |
| # define MVM_FLOAT64_NAN ((MVM_FLOAT64)(INFINITY * 0.0)) |
| |
| #endif // MVM_SUPPORT_FLOAT |
| |
| /** |
| * Set to `1` to enable additional internal consistency checks, or `0` to |
| * disable them. Note that consistency at the API boundary is always checked, |
| * regardless of this setting. Consistency checks make the VM *significantly* |
| * bigger and slower, and are really only intended for testing. |
| */ |
| #define MVM_SAFE_MODE 0 |
| |
| /** |
| * Set to `1` to enable extra validation checks of bytecode while executing. |
| * This is _beyond_ the basic version and CRC checks that are done upon loading, |
| * and should only be enabled if you expect bugs in the bytecode compiler. |
| */ |
| #define MVM_DONT_TRUST_BYTECODE 1 |
| |
| /** |
| * Not recommended! |
| * |
| * Set to `1` to enable extra checks for pointer safety within the engine. In |
| * particular, this triggers a GC collection cycle at every new allocation in |
| * order to find potential dangling pointer issues, and each GC collection |
| * shifts the address space a little to invalidate native pointers early. |
| * This option is only intended for testing purposes. |
| */ |
| #define MVM_VERY_EXPENSIVE_MEMORY_CHECKS 0 |
| |
| /** |
| * A long pointer is a type that can refer to either ROM or RAM. It is not size |
| * restricted. |
| * |
| * On architectures where bytecode is directly addressable with a normal |
| * pointer, this can just be `void*` (e.g. 32-bit architectures). On |
| * architectures where bytecode can be addressed with a special pointer, this |
| * might be something like `__data20 void*` (MSP430). On Harvard architectures |
| * such as AVR8 where ROM and RAM are in different address spaces, |
| * `MVM_LONG_PTR_TYPE` can be some integer type such as `uint32_t`, where you |
| * use part of the value to distinguish which address space and part of the |
| * value as the actual pointer value. |
| * |
| * The chosen representation/encoding of `MVM_LONG_PTR_TYPE` must be an integer |
| * or pointer type, such that `0`/`NULL` represents the null pointer. |
| * |
| * Microvium doesn't access data through pointers of this type directly -- it |
| * does so through macro operations in this port file. |
| */ |
| #define MVM_LONG_PTR_TYPE void * |
| |
| /** |
| * Convert a normal pointer to a long pointer |
| */ |
| #define MVM_LONG_PTR_NEW(p) ((MVM_LONG_PTR_TYPE)p) |
| |
| /** |
| * Truncate a long pointer to a normal pointer. |
| * |
| * This will only be invoked on pointers to VM RAM data. |
| */ |
| #define MVM_LONG_PTR_TRUNCATE(p) ((void *)p) |
| |
| /** |
| * Add an offset `s` in bytes onto a long pointer `p`. The result must be a |
| * MVM_LONG_PTR_TYPE. |
| * |
| * The maximum offset that will be passed is 16-bit. |
| * |
| * Offset may be negative |
| */ |
| #define MVM_LONG_PTR_ADD(p, s) \ |
| ((MVM_LONG_PTR_TYPE)((uint8_t *)p + (ptrdiff_t)s)) |
| |
| /** |
| * Subtract two long pointers to get an offset. The result must be a signed |
| * 16-bit integer of p2 - p1 (where p2 is the FIRST param). |
| */ |
| #define MVM_LONG_PTR_SUB(p2, p1) ((int16_t)((uint8_t *)p2 - (uint8_t *)p1)) |
| |
| /* |
| * Read memory of 1 or 2 bytes |
| */ |
| #define MVM_READ_LONG_PTR_1(lpSource) (*((uint8_t *)lpSource)) |
| #define MVM_READ_LONG_PTR_2(lpSource) (*((uint16_t *)lpSource)) |
| |
| /** |
| * Reference to an implementation of memcmp where p1 and p2 are LONG_PTR |
| */ |
| #define MVM_LONG_MEM_CMP(p1, p2, size) memcmp(p1, p2, size) |
| |
| /** |
| * Reference to an implementation of memcpy where `source` is a LONG_PTR |
| */ |
| #define MVM_LONG_MEM_CPY(target, source, size) memcpy(target, source, size) |
| |
| /** |
| * This is invoked when the virtual machine encounters a critical internal error |
| * and execution of the VM should halt. |
| * |
| * Note that API-level errors are communicated via returned error codes from |
| * each of the API functions and will not trigger a fatal error. |
| * |
| * Note: if malloc fails, this is considered a fatal error since many embedded |
| * systems cannot safely continue when they run out of memory. |
| * |
| * If you need to halt the VM without halting the host, consider running the VM |
| * in a separate RTOS thread, or using setjmp/longjmp to escape the VM without |
| * returning to it. Either way, the VM should NOT be allowed to continue |
| * executing after MVM_FATAL_ERROR (control should not return). |
| */ |
| #define MVM_FATAL_ERROR(vm, e) panic() |
| |
| /** |
| * Set MVM_ALL_ERRORS_FATAL to 1 to have the MVM_FATAL_ERROR handler called |
| * eagerly when a new error is encountered, rather than returning an error code |
| * from `mvm_call`. This is mainly for debugging the VM itself, since the |
| * MVM_FATAL_ERROR handler is called before unwinding the C stack. |
| */ |
| #define MVM_ALL_ERRORS_FATAL 0 |
| |
| #define MVM_SWITCH(tag, upper) switch (tag) |
| #define MVM_CASE(value) case value |
| |
| /** |
| * Macro that evaluates to true if the CRC of the given data matches the |
| * expected value. Note that this is evaluated against the bytecode, so lpData |
| * needs to be a long pointer type. If you don't want the overhead of validating |
| * the CRC, just return `true`. |
| */ |
| #define MVM_CHECK_CRC16_CCITT(lpData, size, expected) \ |
| (crc16(lpData, size) == expected) |
| |
| static uint16_t crc16(MVM_LONG_PTR_TYPE lp, uint16_t size) |
| { |
| uint16_t r = 0xFFFF; |
| while (size--) |
| { |
| r = (uint8_t)(r >> 8) | (r << 8); |
| r ^= MVM_READ_LONG_PTR_1(lp); |
| lp = MVM_LONG_PTR_ADD(lp, 1); |
| r ^= (uint8_t)(r & 0xff) >> 4; |
| r ^= (r << 8) << 4; |
| r ^= ((r & 0xff) << 4) << 1; |
| } |
| return r; |
| } |
| |
| /** |
| * Set to 1 to compile in the ability to generate snapshots (mvm_createSnapshot) |
| */ |
| #define MVM_INCLUDE_SNAPSHOT_CAPABILITY 0 |
| |
| /** |
| * Set to 1 to compile support for the debug API (mvm_dbg_*) |
| */ |
| #define MVM_INCLUDE_DEBUG_CAPABILITY 0 |
| |
| #if MVM_INCLUDE_SNAPSHOT_CAPABILITY |
| /** |
| * Calculate the CRC. This is only used when generating snapshots. |
| * |
| * Unlike MVM_CHECK_CRC16_CCITT, pData here is a pointer to RAM. |
| */ |
| # define MVM_CALC_CRC16_CCITT(pData, size) (crc16(pData, size)) |
| #endif // MVM_INCLUDE_SNAPSHOT_CAPABILITY |
| |
| /** |
| * On architectures like small ARM MCUs where there is a large address space |
| * (e.g. 32-bit) but only a small region of that is used for heap allocations, |
| * Microvium is more efficient if you can tell it the high bits of the addresses |
| * so it can store the lower 16-bits. |
| * |
| * If MVM_USE_SINGLE_RAM_PAGE is set to 1, then MVM_RAM_PAGE_ADDR must be |
| * the address of the page. |
| */ |
| #define MVM_USE_SINGLE_RAM_PAGE 0 |
| |
| #if MVM_USE_SINGLE_RAM_PAGE |
| /** |
| * Address of the RAM page to use, such that all pointers to RAM are between |
| * MVM_RAM_PAGE_ADDR and (MVM_RAM_PAGE_ADDR + 0xFFFF) |
| */ |
| # define MVM_RAM_PAGE_ADDR 0x12340000 |
| #endif |
| |
| /// All public functions are exported from the library. |
| #define MVM_EXPORT __cheri_libcall |
| |
| /** |
| * Implementation of malloc and free to use. |
| * |
| * Note that MVM_CONTEXTUAL_FREE needs to accept null pointers as well. |
| * |
| * If MVM_USE_SINGLE_RAM_PAGE is set, pointers returned by MVM_CONTEXTUAL_MALLOC |
| * must always be within 64kB of MVM_RAM_PAGE_ADDR. |
| * |
| * The `context` passed to these macros is whatever value that the host passes |
| * to `mvm_restore`. It can be any value that fits in a pointer. |
| * |
| * Similarly to `malloc` and `calloc`, this will only ever |
| * block to wait for the quarantine to be processed. |
| */ |
| #define MVM_CONTEXTUAL_MALLOC(size, context) \ |
| ({ \ |
| Timeout t = {0, MALLOC_WAIT_TICKS}; \ |
| void *ret = \ |
| heap_allocate(&t, context, size, AllocateWaitRevocationNeeded); \ |
| if (!__builtin_cheri_tag_get(ret)) \ |
| { \ |
| ret = NULL; \ |
| } \ |
| ret; \ |
| }) |
| #define MVM_CONTEXTUAL_FREE(ptr, context) heap_free(context, ptr) |
| |
| /** |
| * Expose the timeout APIs. |
| */ |
| #define MVM_GAS_COUNTER |
| |
| /** |
| * Helper for Microvium to convert strings to integers. |
| * |
| * This is used only in the int to string coercion function, so is marked as |
| * always inline to ensure that we don't get a function call. |
| */ |
| __always_inline static int mvm_int_to_string_helper(char buffer[12], |
| int32_t value) |
| { |
| char *insert = buffer; |
| |
| // If this is negative, add a minus sign and negate it. |
| if (value < 0) |
| { |
| *(insert++) = '-'; |
| value = 0 - value; |
| } |
| const char Digits[] = "0123456789"; |
| // To skip leading zeroes, write the value backwards into this buffer and |
| // then scan it forward, skipping zeroes, inserting into the output buffer. |
| char tmp[10]; |
| for (int i = sizeof(tmp) - 1; i >= 0; i--) |
| { |
| tmp[i] = Digits[value % 10]; |
| value /= 10; |
| } |
| bool skipZero = true; |
| for (int i = 0; i < sizeof(tmp); i++) |
| { |
| char c = tmp[i]; |
| if (skipZero && (c == '0')) |
| { |
| continue; |
| } |
| skipZero = false; |
| *(insert++) = c; |
| } |
| if (skipZero) |
| { |
| *(insert++) = '0'; |
| } |
| return insert - buffer; |
| } |
| |
| #define MVM_INT32TOSTRING(buffer, i) mvm_int_to_string_helper(buffer, i) |
| |
| /** |
| * Apply bounds to the buffer. |
| */ |
| #define MVM_POINTER_SET_BOUNDS(ptr, bounds) \ |
| __builtin_cheri_bounds_set(ptr, bounds) |
| |
| /** |
| * Remove all permissions except load and global. |
| */ |
| #define MVM_POINTER_MAKE_IMMUTABLE(ptr) \ |
| __builtin_cheri_perms_and(ptr, CHERI_PERM_GLOBAL | CHERI_PERM_LOAD) |