minimal problem example
This commit is contained in:
commit
81de3b30e6
7 changed files with 8519 additions and 0 deletions
1
README
Normal file
1
README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
compile with `gcc -o main -lm main.c microvium.c`
|
87
main.c
Normal file
87
main.c
Normal file
|
@ -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);
|
||||||
|
}
|
11
main.js
Normal file
11
main.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const print = vmImport(0);
|
||||||
|
const fgets = vmImport(1);
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
while(true) {
|
||||||
|
var c = fgets();
|
||||||
|
print(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vmExport(0, main);
|
BIN
main.mvm-bc
Normal file
BIN
main.mvm-bc
Normal file
Binary file not shown.
7718
microvium.c
Normal file
7718
microvium.c
Normal file
File diff suppressed because it is too large
Load diff
408
microvium.h
Normal file
408
microvium.h
Normal file
|
@ -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
|
294
microvium_port.h
Normal file
294
microvium_port.h
Normal file
|
@ -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 a new issue