minimal problem example
This commit is contained in:
commit
81de3b30e6
|
@ -0,0 +1,87 @@
|
|||
#include "stdio.h"
|
||||
#include "string.h"
|
||||
#include "termios.h"
|
||||
#include "microvium.h"
|
||||
|
||||
unsigned char main_mvm_bc[] = {
|
||||
0x06, 0x1c, 0x06, 0x00, 0x7c, 0x00, 0xc1, 0xda, 0x03, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x24, 0x00, 0x2a, 0x00, 0x2c, 0x00,
|
||||
0x68, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00,
|
||||
0x71, 0x00, 0x6d, 0x00, 0x01, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x40,
|
||||
0x70, 0x75, 0x73, 0x68, 0x00, 0x00, 0x02, 0x60, 0x00, 0x00, 0x02, 0x60,
|
||||
0x01, 0x00, 0x0d, 0x50, 0x04, 0x31, 0x30, 0x30, 0x88, 0x1d, 0x00, 0x6b,
|
||||
0x12, 0x6f, 0x67, 0x01, 0x60, 0x00, 0x18, 0x50, 0x04, 0x01, 0x04, 0x70,
|
||||
0x02, 0x01, 0x60, 0x89, 0x01, 0x00, 0x01, 0x78, 0x01, 0xa0, 0x89, 0x00,
|
||||
0x00, 0x01, 0x12, 0x78, 0x02, 0x67, 0x76, 0xea, 0x39, 0x00, 0x3d, 0x00,
|
||||
0x02, 0x00, 0x19, 0x00, 0x01, 0x00, 0x08, 0xc0, 0x05, 0x00, 0x05, 0x00,
|
||||
0x31, 0x00, 0x41, 0x00
|
||||
};
|
||||
unsigned int main_mvm_bc_len = 124;
|
||||
|
||||
static mvm_VM* vm;
|
||||
|
||||
#define IMPORT_COUNT 1
|
||||
|
||||
static const uint16_t importIDs[IMPORT_COUNT] = { 0, 1 };
|
||||
|
||||
mvm_TeError print(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount);
|
||||
mvm_TeError mvm_fgets(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount);
|
||||
|
||||
static struct {
|
||||
mvm_Value main;
|
||||
} imports;
|
||||
|
||||
mvm_TfHostFunction exports[] = {
|
||||
&print,
|
||||
&mvm_fgets,
|
||||
NULL
|
||||
};
|
||||
|
||||
mvm_TeError print(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) {
|
||||
assert(argCount == 1);
|
||||
|
||||
printf("argc: %d\n", argCount);
|
||||
|
||||
size_t len;
|
||||
// XXX failure occurs here, if print(fgets()) is called
|
||||
const char *str = mvm_toStringUtf8(vm, args[0], &len);
|
||||
if(str[len - 1] == '\0') {
|
||||
printf("%s\n", str);
|
||||
} else {
|
||||
printf("%.*s\n", len, str);
|
||||
}
|
||||
|
||||
return MVM_E_SUCCESS;
|
||||
}
|
||||
|
||||
mvm_TeError mvm_fgets(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) {
|
||||
char c = fgetc(stdin);
|
||||
|
||||
*result = mvm_newString(vm, &c, 1);
|
||||
|
||||
mvm_runGC(vm, true);
|
||||
|
||||
return MVM_E_SUCCESS;
|
||||
}
|
||||
|
||||
static mvm_TeError resolveHostFunction(mvm_HostFunctionID funcID, void* context, mvm_TfHostFunction* out) {
|
||||
if(exports[funcID] != NULL) {
|
||||
*out = exports[funcID];
|
||||
|
||||
return MVM_E_SUCCESS;
|
||||
}
|
||||
return MVM_E_UNRESOLVED_IMPORT;
|
||||
}
|
||||
|
||||
void main() {
|
||||
mvm_TeError err;
|
||||
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
err = mvm_restore(&vm, main_mvm_bc, main_mvm_bc_len, NULL, &resolveHostFunction);
|
||||
err = mvm_resolveExports(vm, importIDs, (mvm_Value*)&imports, IMPORT_COUNT);
|
||||
|
||||
// call main.js#main()
|
||||
mvm_call(vm, imports.main, NULL, 0, 0);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
const print = vmImport(0);
|
||||
const fgets = vmImport(1);
|
||||
|
||||
function main() {
|
||||
while(true) {
|
||||
var c = fgets();
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
vmExport(0, main);
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,408 @@
|
|||
// Copyright 2020 Michael Hunter. Part of the Microvium project. Links to full code at https://microvium.com for license details.
|
||||
|
||||
/*
|
||||
* Microvium Bytecode Interpreter
|
||||
*
|
||||
* Version: 0.0.21
|
||||
*
|
||||
* This is the main header for the Microvium bytecode interpreter. Latest source
|
||||
* available at https://microvium.com. Raise issues at
|
||||
* https://github.com/coder-mike/microvium/issues.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "microvium_port.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint16_t mvm_Value;
|
||||
typedef uint16_t mvm_VMExportID;
|
||||
typedef uint16_t mvm_HostFunctionID;
|
||||
|
||||
typedef enum mvm_TeError {
|
||||
/* 0 */ MVM_E_SUCCESS,
|
||||
/* 1 */ MVM_E_UNEXPECTED,
|
||||
/* 2 */ MVM_E_MALLOC_FAIL,
|
||||
/* 3 */ MVM_E_ALLOCATION_TOO_LARGE,
|
||||
/* 4 */ MVM_E_INVALID_ADDRESS,
|
||||
/* 5 */ MVM_E_COPY_ACROSS_BUCKET_BOUNDARY,
|
||||
/* 6 */ MVM_E_FUNCTION_NOT_FOUND,
|
||||
/* 7 */ MVM_E_INVALID_HANDLE,
|
||||
/* 8 */ MVM_E_STACK_OVERFLOW,
|
||||
/* 9 */ MVM_E_UNRESOLVED_IMPORT,
|
||||
/* 10 */ MVM_E_ATTEMPT_TO_WRITE_TO_ROM,
|
||||
/* 11 */ MVM_E_INVALID_ARGUMENTS,
|
||||
/* 12 */ MVM_E_TYPE_ERROR,
|
||||
/* 13 */ MVM_E_TARGET_NOT_CALLABLE,
|
||||
/* 14 */ MVM_E_HOST_ERROR,
|
||||
/* 15 */ MVM_E_NOT_IMPLEMENTED,
|
||||
/* 16 */ MVM_E_HOST_RETURNED_INVALID_VALUE,
|
||||
/* 17 */ MVM_E_ASSERTION_FAILED,
|
||||
/* 18 */ MVM_E_INVALID_BYTECODE,
|
||||
/* 19 */ MVM_E_UNRESOLVED_EXPORT,
|
||||
/* 20 */ MVM_E_RANGE_ERROR,
|
||||
/* 21 */ MVM_E_DETACHED_EPHEMERAL,
|
||||
/* 22 */ MVM_E_TARGET_IS_NOT_A_VM_FUNCTION,
|
||||
/* 23 */ MVM_E_FLOAT64,
|
||||
/* 24 */ MVM_E_NAN,
|
||||
/* 25 */ MVM_E_NEG_ZERO,
|
||||
/* 26 */ MVM_E_OPERATION_REQUIRES_FLOAT_SUPPORT,
|
||||
/* 27 */ MVM_E_BYTECODE_CRC_FAIL,
|
||||
/* 28 */ MVM_E_BYTECODE_REQUIRES_FLOAT_SUPPORT,
|
||||
/* 29 */ MVM_E_PROTO_IS_READONLY, // The __proto__ property of objects and arrays is not mutable
|
||||
/* 30 */ MVM_E_SNAPSHOT_TOO_LARGE, // The resulting snapshot does not fit in the 64kB boundary
|
||||
/* 31 */ MVM_E_MALLOC_MUST_RETURN_POINTER_TO_EVEN_BOUNDARY,
|
||||
/* 32 */ MVM_E_ARRAY_TOO_LONG,
|
||||
/* 33 */ MVM_E_OUT_OF_MEMORY, // Allocating a new block of memory from the host causes it to exceed MVM_MAX_HEAP_SIZE
|
||||
/* 34 */ MVM_E_TOO_MANY_ARGUMENTS, // Exceeded the maximum number of arguments for a function (255)
|
||||
/* 35 */ MVM_E_REQUIRES_LATER_ENGINE, // Please update your microvium.h and microvium.c files
|
||||
/* 36 */ MVM_E_PORT_FILE_VERSION_MISMATCH, // Please migrate your port file to the required version
|
||||
/* 37 */ MVM_E_PORT_FILE_MACRO_TEST_FAILURE, // Something in microvium_port.h doesn't behave as expected
|
||||
/* 38 */ MVM_E_EXPECTED_POINTER_SIZE_TO_BE_16_BIT, // MVM_NATIVE_POINTER_IS_16_BIT is 1 but pointer size is not 16-bit
|
||||
/* 39 */ MVM_E_EXPECTED_POINTER_SIZE_NOT_TO_BE_16_BIT, // MVM_NATIVE_POINTER_IS_16_BIT is 0 but pointer size is 16-bit
|
||||
/* 40 */ MVM_E_TYPE_ERROR_TARGET_IS_NOT_CALLABLE, // The script tried to call something that wasn't a function
|
||||
/* 41 */ MVM_E_TDZ_ERROR, // The script tried to access a local variable before its declaration
|
||||
/* 42 */ MVM_E_MALLOC_NOT_WITHIN_RAM_PAGE, // See instructions in example port file at the defitions MVM_USE_SINGLE_RAM_PAGE and MVM_RAM_PAGE_ADDR
|
||||
/* 43 */ MVM_E_INVALID_ARRAY_INDEX, // Array indexes must be integers in the range 0 to 8191
|
||||
/* 44 */ MVM_E_UNCAUGHT_EXCEPTION, // The script threw an exception with `throw` that was wasn't caught before returning to the host
|
||||
/* 45 */ MVM_E_FATAL_ERROR_MUST_KILL_VM, // Please make sure that MVM_FATAL_ERROR does not return, or bad things can happen. (Kill the process, the thread, or use longjmp)
|
||||
/* 46 */ MVM_E_OBJECT_KEYS_ON_NON_OBJECT, // Can only use Reflect.ownKeys on plain objects (not functions, arrays, or other values)
|
||||
/* 47 */ MVM_E_INVALID_UINT8_ARRAY_LENGTH, // Either non-numeric or out-of-range argument for creating a Uint8Array
|
||||
/* 48 */ MVM_E_CAN_ONLY_ASSIGN_BYTES_TO_UINT8_ARRAY, // Value assigned to index of Uint8Array must be an integer in the range 0 to 255
|
||||
/* 49 */ MVM_E_WRONG_BYTECODE_VERSION, // The version of bytecode is different to what the engine supports
|
||||
/* 50 */ MVM_E_USING_NEW_ON_NON_CLASS, // The `new` operator can only be used on classes
|
||||
} mvm_TeError;
|
||||
|
||||
typedef enum mvm_TeType {
|
||||
VM_T_UNDEFINED = 0,
|
||||
VM_T_NULL = 1,
|
||||
VM_T_BOOLEAN = 2,
|
||||
VM_T_NUMBER = 3,
|
||||
VM_T_STRING = 4,
|
||||
VM_T_FUNCTION = 5,
|
||||
VM_T_OBJECT = 6,
|
||||
VM_T_ARRAY = 7,
|
||||
VM_T_UINT8_ARRAY = 8,
|
||||
VM_T_CLASS = 9,
|
||||
VM_T_SYMBOL = 10, // Reserved
|
||||
VM_T_BIG_INT = 11, // Reserved
|
||||
|
||||
VM_T_END,
|
||||
} mvm_TeType;
|
||||
|
||||
typedef struct mvm_VM mvm_VM;
|
||||
|
||||
typedef mvm_TeError (*mvm_TfHostFunction)(mvm_VM* vm, mvm_HostFunctionID hostFunctionID, mvm_Value* result, mvm_Value* args, uint8_t argCount);
|
||||
|
||||
typedef mvm_TeError (*mvm_TfResolveImport)(mvm_HostFunctionID hostFunctionID, void* context, mvm_TfHostFunction* out_hostFunction);
|
||||
|
||||
typedef void (*mvm_TfBreakpointCallback)(mvm_VM* vm, uint16_t bytecodeAddress);
|
||||
|
||||
typedef struct mvm_TsMemoryStats {
|
||||
// Total RAM currently allocated by the VM from the host
|
||||
size_t totalSize;
|
||||
|
||||
// Number of distinct, currently-allocated memory allocations (mallocs) from the host
|
||||
size_t fragmentCount;
|
||||
|
||||
// RAM size of VM core state
|
||||
size_t coreSize;
|
||||
|
||||
// RAM allocated to the VM import table (table of functions resolved from the host)
|
||||
size_t importTableSize;
|
||||
|
||||
// RAM allocated to global variables in RAM
|
||||
size_t globalVariablesSize;
|
||||
|
||||
// If the machine registers are allocated (if a call is active), this says how
|
||||
// much RAM these consume. Otherwise zero if there is no active stack.
|
||||
size_t registersSize;
|
||||
|
||||
// Virtual stack size (bytes) currently allocated (if a call is active), or
|
||||
// zero if there is no active stack. Note that virtual stack space is
|
||||
// malloc'd, not allocated on the C stack.
|
||||
size_t stackHeight;
|
||||
|
||||
// Virtual stack space capacity if a call is active, otherwise zero.
|
||||
size_t stackAllocatedCapacity;
|
||||
|
||||
// Maximum stack size over the lifetime of the VM. This value can be used to
|
||||
// tune the MVM_STACK_SIZE port definition
|
||||
size_t stackHighWaterMark;
|
||||
|
||||
// Amount of virtual heap that the VM is currently using
|
||||
size_t virtualHeapUsed;
|
||||
|
||||
// Maximum amount of virtual heap space ever used by this VM
|
||||
size_t virtualHeapHighWaterMark;
|
||||
|
||||
// Current total size of virtual heap (will expand as needed up to a max of MVM_MAX_HEAP_SIZE)
|
||||
size_t virtualHeapAllocatedCapacity;
|
||||
|
||||
} mvm_TsMemoryStats;
|
||||
|
||||
/**
|
||||
* A handle holds a value that must not be garbage collected.
|
||||
*/
|
||||
typedef struct mvm_Handle { struct mvm_Handle* _next; mvm_Value _value; } mvm_Handle;
|
||||
|
||||
#include "microvium_port.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Creates a VM (restores the state of a virtual machine from a snapshot)
|
||||
*
|
||||
* A VM created with mvm_restore needs to be freed with mvm_free.
|
||||
*
|
||||
* Note: the bytecode should be aligned to the processor word size.
|
||||
*
|
||||
* @param resolveImport A callback function that the VM will call when it needs
|
||||
* to import a host function.
|
||||
* @param context Any value. The context for a VM can be retrieved later using
|
||||
* `mvm_getContext`. It can be used to attach user-defined data to a VM.
|
||||
*/
|
||||
mvm_TeError mvm_restore(mvm_VM** result, MVM_LONG_PTR_TYPE snapshotBytecode, size_t bytecodeSize, void* context, mvm_TfResolveImport resolveImport);
|
||||
|
||||
/**
|
||||
* Free all memory associated with a VM. The VM must not be used again after freeing.
|
||||
*/
|
||||
void mvm_free(mvm_VM* vm);
|
||||
|
||||
/**
|
||||
* Call a function in the VM
|
||||
*
|
||||
* @param func The function value to call
|
||||
* @param out_result Where to put the result, or NULL if the result is not
|
||||
* needed
|
||||
* @param args Pointer to arguments array, or NULL if no arguments
|
||||
* @param argCount Number of arguments
|
||||
*
|
||||
* If the JS code throws an exception, the return value will be
|
||||
* MVM_E_UNCAUGHT_EXCEPTION and the exception value will be put into
|
||||
* `out_result`
|
||||
*/
|
||||
mvm_TeError mvm_call(mvm_VM* vm, mvm_Value func, mvm_Value* out_result, mvm_Value* args, uint8_t argCount);
|
||||
|
||||
void* mvm_getContext(mvm_VM* vm);
|
||||
|
||||
void mvm_initializeHandle(mvm_VM* vm, mvm_Handle* handle); // Handle must be released by mvm_releaseHandle
|
||||
void mvm_cloneHandle(mvm_VM* vm, mvm_Handle* target, const mvm_Handle* source); // Target must be released by mvm_releaseHandle
|
||||
mvm_TeError mvm_releaseHandle(mvm_VM* vm, mvm_Handle* handle);
|
||||
static inline mvm_Value mvm_handleGet(mvm_Handle* handle) { return handle->_value; }
|
||||
static inline void mvm_handleSet(mvm_Handle* handle, mvm_Value value) { handle->_value = value; }
|
||||
|
||||
/**
|
||||
* Roughly like the `typeof` operator in JS, except with distinct values for
|
||||
* null and arrays
|
||||
*/
|
||||
mvm_TeType mvm_typeOf(mvm_VM* vm, mvm_Value value);
|
||||
|
||||
/**
|
||||
* Converts the value to a string encoded as UTF-8.
|
||||
*
|
||||
* @param out_sizeBytes Returns the length of the string in bytes, or provide NULL if not needed.
|
||||
* @return A pointer to the string data which may be in VM memory or bytecode.
|
||||
*
|
||||
* Note: for convenience, the returned data has an extra null character appended
|
||||
* to the end of it, so that the result is directly usable in printf, strcpy,
|
||||
* etc. The returned size in bytes is the size of the original string data,
|
||||
* excluding the extra null.
|
||||
*
|
||||
* The string data itself is permitted to contain nulls or any other data. For
|
||||
* example, if the string value is "abc\0", the size returned is "4", and the
|
||||
* returned pointer points to the data "abc\0\0" (i.e. with the extra safety
|
||||
* null beyond the user-provided data).
|
||||
*
|
||||
* The memory pointed to by the return value may be transient: it is only guaranteed
|
||||
* to exist until the next garbage collection cycle. See
|
||||
* [memory-management.md](https://github.com/coder-mike/microvium/blob/master/doc/native-vm/memory-management.md)
|
||||
* for details.
|
||||
*/
|
||||
const char* mvm_toStringUtf8(mvm_VM* vm, mvm_Value value, size_t* out_sizeBytes);
|
||||
|
||||
/**
|
||||
* Convert the value to a bool based on its truthiness.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Glossary/Truthy
|
||||
*/
|
||||
bool mvm_toBool(mvm_VM* vm, mvm_Value value);
|
||||
|
||||
/**
|
||||
* Converts the value to a 32-bit signed integer.
|
||||
*
|
||||
* The result of this should be the same as `value|0` in JavaScript code.
|
||||
*/
|
||||
int32_t mvm_toInt32(mvm_VM* vm, mvm_Value value);
|
||||
|
||||
#if MVM_SUPPORT_FLOAT
|
||||
/**
|
||||
* Converts the value to a number.
|
||||
*
|
||||
* The result of this should be the same as `+value` in JavaScript code.
|
||||
*
|
||||
* For efficiency, use mvm_toInt32 instead if your value is an integer.
|
||||
*/
|
||||
MVM_FLOAT64 mvm_toFloat64(mvm_VM* vm, mvm_Value value);
|
||||
|
||||
/**
|
||||
* Create a number value in the VM.
|
||||
*
|
||||
* For efficiency, use mvm_newInt32 instead if your value is an integer.
|
||||
*
|
||||
* Design note: mvm_newNumber creates a number *from* a float64, so it's named
|
||||
* `newNumber` and not `newFloat64`
|
||||
*/
|
||||
mvm_Value mvm_newNumber(mvm_VM* vm, MVM_FLOAT64 value);
|
||||
#endif
|
||||
|
||||
bool mvm_isNaN(mvm_Value value);
|
||||
|
||||
extern const mvm_Value mvm_undefined;
|
||||
extern const mvm_Value mvm_null;
|
||||
mvm_Value mvm_newBoolean(bool value);
|
||||
mvm_Value mvm_newInt32(mvm_VM* vm, int32_t value);
|
||||
mvm_Value mvm_newString(mvm_VM* vm, const char* valueUtf8, size_t sizeBytes);
|
||||
|
||||
/**
|
||||
* A Uint8Array in Microvium is an efficient buffer of bytes. It is mutable but
|
||||
* cannot be resized. The new Uint8Array created by this method will contain a
|
||||
* *copy* of the supplied data.
|
||||
*
|
||||
* Within the VM, you can create a new Uint8Array using the global
|
||||
* `Microvium.newUint8Array`.
|
||||
*
|
||||
* See also: mvm_uint8ArrayToBytes
|
||||
*/
|
||||
mvm_Value mvm_uint8ArrayFromBytes(mvm_VM* vm, const uint8_t* data, size_t size);
|
||||
|
||||
/**
|
||||
* Given a Uint8Array, this will give a pointer to its data and its size (in
|
||||
* bytes).
|
||||
*
|
||||
* Warning: The data pointer should be considered invalid on the next call to
|
||||
* any of the Microvium API methods, since a garbage can move the data. It is
|
||||
* recommended to call this method again each time you need the pointer.
|
||||
*
|
||||
* The returned pointer can also be used to mutate the buffer, with caution.
|
||||
*
|
||||
* See also: mvm_newUint8Array
|
||||
*/
|
||||
mvm_TeError mvm_uint8ArrayToBytes(mvm_VM* vm, mvm_Value uint8ArrayValue, uint8_t** out_data, size_t* out_size);
|
||||
|
||||
/**
|
||||
* Resolves (finds) the values exported by the VM, identified by ID.
|
||||
*
|
||||
* @param ids An array of `count` IDs to look up.
|
||||
* @param results An array of `count` output values that result from each
|
||||
* lookup
|
||||
*
|
||||
* Note: Exports are immutable (shallow immutable), so they don't need to be
|
||||
* captured by a mvm_Handle. In typical usage, exports will each be function
|
||||
* values, but any value type is valid.
|
||||
*/
|
||||
mvm_TeError mvm_resolveExports(mvm_VM* vm, const mvm_VMExportID* ids, mvm_Value* results, uint8_t count);
|
||||
|
||||
/**
|
||||
* Run a garbage collection cycle.
|
||||
*
|
||||
* If `squeeze` is `true`, the GC runs in 2 passes: the first pass computes the
|
||||
* exact required size, and the second pass compacts into exactly that size.
|
||||
*
|
||||
* If `squeeze` is `false`, the GC runs in a single pass, estimating the amount
|
||||
* of needed as the amount of space used after the last compaction, and then
|
||||
* adding blocks as-necessary.
|
||||
*/
|
||||
void mvm_runGC(mvm_VM* vm, bool squeeze);
|
||||
|
||||
/**
|
||||
* Compares two values for equality. The same semantics as JavaScript `===`
|
||||
*/
|
||||
bool mvm_equal(mvm_VM* vm, mvm_Value a, mvm_Value b);
|
||||
|
||||
/**
|
||||
* The current bytecode address being executed (relative to the beginning of the
|
||||
* bytecode image), or null if the machine is not currently active.
|
||||
*
|
||||
* This value can be looked up in the map file generated by the CLI flag
|
||||
* `--map-file`
|
||||
*/
|
||||
uint16_t mvm_getCurrentAddress(mvm_VM* vm);
|
||||
|
||||
/**
|
||||
* Get stats about the VM memory
|
||||
*/
|
||||
void mvm_getMemoryStats(mvm_VM* vm, mvm_TsMemoryStats* out_stats);
|
||||
|
||||
#if MVM_INCLUDE_SNAPSHOT_CAPABILITY
|
||||
/**
|
||||
* Create a snapshot of the VM
|
||||
*
|
||||
* @param vm The virtual machine to snapshot.
|
||||
* @param out_size Pointer to variable which will receive the size of the
|
||||
* generated snapshot
|
||||
*
|
||||
* The snapshot generated by this function is suitable to be used in a call to
|
||||
* mvm_restore to be restored later.
|
||||
*
|
||||
* It's recommended to run a garbage collection cycle (mvm_runGC) before
|
||||
* creating the snapshot, to get as compact a snapshot as possible.
|
||||
*
|
||||
* No snapshots ever contain the stack or register states -- they only encode
|
||||
* the heap and global variable states.
|
||||
*
|
||||
* Note: The result is malloc'd on the host heap, and so needs to be freed with
|
||||
* a call to *free*.
|
||||
*/
|
||||
void* mvm_createSnapshot(mvm_VM* vm, size_t* out_size);
|
||||
#endif // MVM_INCLUDE_SNAPSHOT_CAPABILITY
|
||||
|
||||
#if MVM_INCLUDE_DEBUG_CAPABILITY
|
||||
/**
|
||||
* Set a breakpoint on the given bytecode address.
|
||||
*
|
||||
* Whenever the VM executes the instruction at the given bytecode address, the
|
||||
* VM will invoke the breakpointcallback (see mvm_dbg_setBreakpointCallback).
|
||||
*
|
||||
* The given bytecode address is measured from the beginning of the given
|
||||
* bytecode image (passed to mvm_restore). The address point exactly to the
|
||||
* beginning of a bytecode instruction (addresses corresponding to the middle of
|
||||
* a multi-byte instruction are ignored).
|
||||
*
|
||||
* The breakpoint remains registered/active until mvm_dbg_removeBreakpoint is
|
||||
* called with the exact same bytecode address.
|
||||
*
|
||||
* Setting a breakpoint a second time on the same address of an existing active
|
||||
* breakpoint will have no effect.
|
||||
*/
|
||||
void mvm_dbg_setBreakpoint(mvm_VM* vm, uint16_t bytecodeAddress);
|
||||
|
||||
/**
|
||||
* Remove a breakpoint added by mvm_dbg_setBreakpoint
|
||||
*/
|
||||
void mvm_dbg_removeBreakpoint(mvm_VM* vm, uint16_t bytecodeAddress);
|
||||
|
||||
/**
|
||||
* Set the function to be called when any breakpoint is hit.
|
||||
*
|
||||
* The callback only applies to the given virtual machine (the callback can be
|
||||
* different for different VMs).
|
||||
*
|
||||
* The callback is invoked with the bytecode address corresponding to where the
|
||||
* VM is stopped. The VM will continue execution when the breakpoint callback
|
||||
* returns. To suspend the VM indefinitely, the callback needs to
|
||||
* correspondingly block indefinitely.
|
||||
*
|
||||
* It's possible but not recommended for the callback itself call into the VM
|
||||
* again (mvm_call), causing control to re-enter the VM while the breakpoint is
|
||||
* still active. This should *NOT* be used to continue execution, but could
|
||||
* theoretically be used to evaluate debug watch expressions.
|
||||
*/
|
||||
void mvm_dbg_setBreakpointCallback(mvm_VM* vm, mvm_TfBreakpointCallback cb);
|
||||
#endif // MVM_INCLUDE_DEBUG_CAPABILITY
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
|
||||
# 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 <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.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 mallocd 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 1
|
||||
|
||||
/**
|
||||
* Set to 1 to enable overflow checking for 32 bit integers in compliance with
|
||||
* ES262 standard. If set to 0, then operations on 32-bit integers have
|
||||
* wrap-around behavior. Wrap around behavior is faster and the Microvium
|
||||
* runtime is smaller.
|
||||
*/
|
||||
#define MVM_PORT_INT32_OVERFLOW_CHECKS 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 1
|
||||
|
||||
/**
|
||||
* 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 + (intptr_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) (assert(false), exit(e))
|
||||
|
||||
/**
|
||||
* 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 1
|
||||
|
||||
/**
|
||||
* Set to 1 to compile support for the debug API (mvm_dbg_*)
|
||||
*/
|
||||
#define MVM_INCLUDE_DEBUG_CAPABILITY 1
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* Implementation of malloc and free to use.
|
||||
*
|
||||
* If MVM_USE_SINGLE_RAM_PAGE is set, pointers returned by MVM_MALLOC must
|
||||
* always be within 64kB of MVM_RAM_PAGE_ADDR.
|
||||
*/
|
||||
#define MVM_MALLOC(size) malloc(size)
|
||||
#define MVM_FREE(ptr) free(ptr)
|
Loading…
Reference in New Issue