409 lines
16 KiB
C
409 lines
16 KiB
C
// 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
|