mvm-tests/microvium.h

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