├── js.png ├── application.fam ├── .github └── workflows │ └── main.yml ├── README.md ├── lib └── microvium │ ├── microvium_port.h │ └── microvium.h └── js.c /js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zap8600/js-f0/HEAD/js.png -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="js", 3 | name="JavaScript", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="js_app", 6 | stack_size=4 * 1024, 7 | requires=["gui"], 8 | fap_private_libs=[ 9 | Lib( 10 | name="microvium", 11 | cflags=["-Wno-implicit-function-declaration", "-Wno-unused-parameter", "-Wno-char-subscripts", "-Wno-double-promotion", "-Wno-redundant-decls"], 12 | ), 13 | ], 14 | fap_icon="js.png", 15 | fap_category="Tools", 16 | ) 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: "FAP: Build" 2 | on: [push, pull_request] 3 | jobs: 4 | ufbt-build-action: 5 | runs-on: ubuntu-latest 6 | name: 'ufbt: Build for Release branch' 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v4 10 | - name: Build with ufbt 11 | uses: flipperdevices/flipperzero-ufbt-action@v0.1.3 12 | id: build-app 13 | with: 14 | # Set to 'release' to build for latest published release version 15 | sdk-channel: release 16 | - name: Upload app artifacts 17 | uses: actions/upload-artifact@v3 18 | with: 19 | name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} 20 | path: ${{ steps.build-app.outputs.fap-artifacts }} 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-f0 2 | JavaScript... on the Flipper Zero? 3 | ## Notice 4 | I'll be returning from my break. Development will be slow, as I have other priorities, but with the amount of interest being shown, I'd feel bad for not putting in work. 5 | ## How? 6 | This is all possible using the [microvium JavaScript engine](https://github.com/coder-mike/microvium). Check it out! 7 | ## Installation 8 | Download the artifact from the most recent successful [action](https://github.com/zap8600/js-f0/actions) and put it in `/ext/apps` on your Flipper Zero. 9 | ## Usage 10 | 1. Install microvium on your computer with `npm install -g microvium`. 11 | 2. Create a new script with the following contents: 12 | ```js 13 | // script.mvm.js 14 | console.log = vmImport(7); 15 | function main() { 16 | console.log('Hello, World!'); 17 | } 18 | vmExport(1, main); 19 | ``` 20 | 3. Compile it with `microvium script.mvm.js`. This will make a file in the same directory called `script.mvm-bc`. 21 | 4. Copy `script.mvm-bc` to `/ext/apps_data/js` on your Flipper Zero. 22 | 5. Run `JavaScript` on your Flipper Zero. 23 | 6. Press the center button to open the console and find "Hello, world!" in the log! 24 | ## Fully implemented standard functions 25 | * `console.log()` 26 | * `console.warn()` 27 | * `console.clear()` 28 | ## Incomplete implemented standard functions 29 | * `fs.openSync()` - Untested 30 | ## Currently WIP standard functions 31 | * `confirm()` 32 | -------------------------------------------------------------------------------- /lib/microvium/microvium_port.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | # Instructions 4 | 5 | Make a copy of this file and name it exactly `microvium_port.h`. Put the copy somewhere 6 | in your project where it is accessible by a `#include "microvium_port.h"` directive. 7 | 8 | Customize your copy of the port file with platform-specific configurations. 9 | 10 | The recommended workflow is to keep the vm source files separate from your 11 | custom port file, so that you can update the vm source files regularly with bug 12 | fixes and improvement from the original github or npm repository. 13 | 14 | */ 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /** 23 | * The version of the port interface that this file is implementing. 24 | */ 25 | #define MVM_PORT_VERSION 1 26 | 27 | /** 28 | * Number of bytes to use for the stack. 29 | * 30 | * Note: the that stack is fixed-size, even though the heap grows dynamically 31 | * as-needed. 32 | */ 33 | #define MVM_STACK_SIZE 256 34 | 35 | /** 36 | * When more space is needed for the VM heap, the VM will malloc blocks with a 37 | * minimum of this size from the host. 38 | * 39 | * Note that the VM can also allocate blocks larger than this. It will do so if 40 | * it needs a larger contiguous space than will fit in a standard block, and 41 | * also during heap compaction (`runGC`) where it defragments the heap into as 42 | * few mallocd blocks as possible to make access more efficient. 43 | */ 44 | #define MVM_ALLOCATION_BUCKET_SIZE 256 45 | 46 | /** 47 | * The maximum size of the virtual heap before an MVM_E_OUT_OF_MEMORY error is 48 | * given. 49 | * 50 | * When the VM reaches this level, it will first try to perform a garbage 51 | * collection cycle. If a GC cycle does not free enough memory, a fatal 52 | * MVM_E_OUT_OF_MEMORY error is given. 53 | * 54 | * Note: this is the space in the virtual heap (the amount consumed by 55 | * allocations in the VM), not the physical space malloc'd from the host, the 56 | * latter of which can peak at roughly twice the virtual space during a garbage 57 | * collection cycle in the worst case. 58 | */ 59 | #define MVM_MAX_HEAP_SIZE 1024 60 | 61 | /** 62 | * Set to 1 if a `void*` pointer is natively 16-bit (e.g. if compiling for 63 | * 16-bit architectures). This allows some optimizations since then a native 64 | * pointer can fit in a Microvium value slot. 65 | */ 66 | #define MVM_NATIVE_POINTER_IS_16_BIT 0 67 | 68 | /** 69 | * Set to 1 to compile in support for floating point operations (64-bit). This 70 | * adds significant cost in smaller devices, but is required if you want the VM 71 | * to be compliant with the ECMAScript standard. 72 | * 73 | * When float support is disabled, operations on floats will throw. 74 | */ 75 | // set to zero for f0 compilation 76 | #define MVM_SUPPORT_FLOAT 0 77 | 78 | #if MVM_SUPPORT_FLOAT 79 | 80 | /** 81 | * The type to use for double-precision floating point. Note that anything other 82 | * than an IEEE 754 double-precision float is not compliant with the ECMAScript 83 | * spec and results may not always be as expected. Also remember that the 84 | * bytecode is permitted to have floating point literals embedded in it, and 85 | * these must match the exact format specification used here if doubles are to 86 | * persist correctly across a snapshot. 87 | * 88 | * Note that on some embedded systems, the `double` type is actually 32-bit, so 89 | * this may need to be `long double` or whatever the equivalent 64-bit type is 90 | * on your system. 91 | */ 92 | #define MVM_FLOAT64 double 93 | 94 | /** 95 | * Value to use for NaN 96 | */ 97 | #define MVM_FLOAT64_NAN ((MVM_FLOAT64)(INFINITY * 0.0)) 98 | 99 | #endif // MVM_SUPPORT_FLOAT 100 | 101 | /** 102 | * Set to `1` to enable additional internal consistency checks, or `0` to 103 | * disable them. Note that consistency at the API boundary is always checked, 104 | * regardless of this setting. Consistency checks make the VM *significantly* 105 | * bigger and slower, and are really only intended for testing. 106 | */ 107 | #define MVM_SAFE_MODE 1 108 | 109 | /** 110 | * Set to `1` to enable extra validation checks of bytecode while executing. 111 | * This is _beyond_ the basic version and CRC checks that are done upon loading, 112 | * and should only be enabled if you expect bugs in the bytecode compiler. 113 | */ 114 | #define MVM_DONT_TRUST_BYTECODE 1 115 | 116 | /** 117 | * Not recommended! 118 | * 119 | * Set to `1` to enable extra checks for pointer safety within the engine. In 120 | * particular, this triggers a GC collection cycle at every new allocation in 121 | * order to find potential dangling pointer issues, and each GC collection 122 | * shifts the address space a little to invalidate native pointers early. 123 | * This option is only intended for testing purposes. 124 | */ 125 | #define MVM_VERY_EXPENSIVE_MEMORY_CHECKS 0 126 | 127 | /** 128 | * A long pointer is a type that can refer to either ROM or RAM. It is not size 129 | * restricted. 130 | * 131 | * On architectures where bytecode is directly addressable with a normal 132 | * pointer, this can just be `void*` (e.g. 32-bit architectures). On 133 | * architectures where bytecode can be addressed with a special pointer, this 134 | * might be something like `__data20 void*` (MSP430). On Harvard architectures 135 | * such as AVR8 where ROM and RAM are in different address spaces, 136 | * `MVM_LONG_PTR_TYPE` can be some integer type such as `uint32_t`, where you 137 | * use part of the value to distinguish which address space and part of the 138 | * value as the actual pointer value. 139 | * 140 | * The chosen representation/encoding of `MVM_LONG_PTR_TYPE` must be an integer 141 | * or pointer type, such that `0`/`NULL` represents the null pointer. 142 | * 143 | * Microvium doesn't access data through pointers of this type directly -- it 144 | * does so through macro operations in this port file. 145 | */ 146 | #define MVM_LONG_PTR_TYPE void* 147 | 148 | /** 149 | * Convert a normal pointer to a long pointer 150 | */ 151 | #define MVM_LONG_PTR_NEW(p) ((MVM_LONG_PTR_TYPE)p) 152 | 153 | /** 154 | * Truncate a long pointer to a normal pointer. 155 | * 156 | * This will only be invoked on pointers to VM RAM data. 157 | */ 158 | #define MVM_LONG_PTR_TRUNCATE(p) ((void*)p) 159 | 160 | /** 161 | * Add an offset `s` in bytes onto a long pointer `p`. The result must be a 162 | * MVM_LONG_PTR_TYPE. 163 | * 164 | * The maximum offset that will be passed is 16-bit. 165 | * 166 | * Offset may be negative 167 | */ 168 | #define MVM_LONG_PTR_ADD(p, s) ((MVM_LONG_PTR_TYPE)((uint8_t*)p + (intptr_t)s)) 169 | 170 | /** 171 | * Subtract two long pointers to get an offset. The result must be a signed 172 | * 16-bit integer of p2 - p1 (where p2 is the FIRST param). 173 | */ 174 | #define MVM_LONG_PTR_SUB(p2, p1) ((int16_t)((uint8_t*)p2 - (uint8_t*)p1)) 175 | 176 | /* 177 | * Read memory of 1 or 2 bytes 178 | */ 179 | #define MVM_READ_LONG_PTR_1(lpSource) (*((uint8_t *)lpSource)) 180 | #define MVM_READ_LONG_PTR_2(lpSource) (*((uint16_t *)lpSource)) 181 | 182 | /** 183 | * Reference to an implementation of memcmp where p1 and p2 are LONG_PTR 184 | */ 185 | #define MVM_LONG_MEM_CMP(p1, p2, size) memcmp(p1, p2, size) 186 | 187 | /** 188 | * Reference to an implementation of memcpy where `source` is a LONG_PTR 189 | */ 190 | #define MVM_LONG_MEM_CPY(target, source, size) memcpy(target, source, size) 191 | 192 | /** 193 | * This is invoked when the virtual machine encounters a critical internal error 194 | * and execution of the VM should halt. 195 | * 196 | * Note that API-level errors are communicated via returned error codes from 197 | * each of the API functions and will not trigger a fatal error. 198 | * 199 | * Note: if malloc fails, this is considered a fatal error since many embedded 200 | * systems cannot safely continue when they run out of memory. 201 | * 202 | * If you need to halt the VM without halting the host, consider running the VM 203 | * in a separate RTOS thread, or using setjmp/longjmp to escape the VM without 204 | * returning to it. Either way, the VM should NOT be allowed to continue 205 | * executing after MVM_FATAL_ERROR (control should not return). 206 | */ 207 | // fatalError is required due to lack of exit() on f0 208 | extern void fatalError(void* vm, int e); 209 | #define MVM_FATAL_ERROR(vm, e) fatalError(vm, e) 210 | 211 | /** 212 | * Set MVM_ALL_ERRORS_FATAL to 1 to have the MVM_FATAL_ERROR handler called 213 | * eagerly when a new error is encountered, rather than returning an error code 214 | * from `mvm_call`. This is mainly for debugging the VM itself, since the 215 | * MVM_FATAL_ERROR handler is called before unwinding the C stack. 216 | */ 217 | #define MVM_ALL_ERRORS_FATAL 0 218 | 219 | #define MVM_SWITCH(tag, upper) switch (tag) 220 | #define MVM_CASE(value) case value 221 | 222 | /** 223 | * Macro that evaluates to true if the CRC of the given data matches the 224 | * expected value. Note that this is evaluated against the bytecode, so lpData 225 | * needs to be a long pointer type. If you don't want the overhead of validating 226 | * the CRC, just return `true`. The Microvium compiler uses CCITT16 as the CRC. 227 | */ 228 | #define MVM_CHECK_CRC16_CCITT(lpData, size, expected) (default_crc16(lpData, size) == expected) 229 | 230 | /** 231 | * Set to 1 to compile in the ability to generate snapshots (mvm_createSnapshot) 232 | */ 233 | #define MVM_INCLUDE_SNAPSHOT_CAPABILITY 1 234 | 235 | /** 236 | * Set to 1 to compile support for the debug API (mvm_dbg_*) 237 | */ 238 | #define MVM_INCLUDE_DEBUG_CAPABILITY 1 239 | 240 | #if MVM_INCLUDE_SNAPSHOT_CAPABILITY 241 | /** 242 | * Calculate the CRC. This is only used when generating snapshots. 243 | * 244 | * Unlike MVM_CHECK_CRC16_CCITT, pData here is a pointer to RAM. 245 | */ 246 | #define MVM_CALC_CRC16_CCITT(pData, size) (default_crc16(pData, size)) 247 | #endif // MVM_INCLUDE_SNAPSHOT_CAPABILITY 248 | 249 | /** 250 | * On architectures like small ARM MCUs where there is a large address space 251 | * (e.g. 32-bit) but only a small region of that is used for heap allocations, 252 | * Microvium is more efficient if you can tell it the high bits of the addresses 253 | * so it can store the lower 16-bits. 254 | * 255 | * If MVM_USE_SINGLE_RAM_PAGE is set to 1, then MVM_RAM_PAGE_ADDR must be 256 | * the address of the page. 257 | */ 258 | #define MVM_USE_SINGLE_RAM_PAGE 0 259 | 260 | #if MVM_USE_SINGLE_RAM_PAGE 261 | /** 262 | * Address of the RAM page to use, such that all pointers to RAM are between 263 | * MVM_RAM_PAGE_ADDR and (MVM_RAM_PAGE_ADDR + 0xFFFF) 264 | */ 265 | #define MVM_RAM_PAGE_ADDR 0x12340000 266 | #endif 267 | 268 | /** 269 | * Implementation of malloc and free to use. 270 | * 271 | * Note that MVM_CONTEXTUAL_FREE needs to accept null pointers as well. 272 | * 273 | * If MVM_USE_SINGLE_RAM_PAGE is set, pointers returned by MVM_CONTEXTUAL_MALLOC 274 | * must always be within 64kB of MVM_RAM_PAGE_ADDR. 275 | * 276 | * The `context` passed to these macros is whatever value that the host passes 277 | * to `mvm_restore`. It can be any value that fits in a pointer. 278 | */ 279 | #define MVM_CONTEXTUAL_MALLOC(size, context) MVM_MALLOC(size) 280 | #define MVM_CONTEXTUAL_FREE(ptr, context) MVM_FREE(ptr) 281 | 282 | /** 283 | * If defined, this will enable the API methods `mvm_stopAfterNInstructions` and 284 | * `mvm_getInstructionCountRemaining`. 285 | */ 286 | #define MVM_GAS_COUNTER 287 | -------------------------------------------------------------------------------- /js.c: -------------------------------------------------------------------------------- 1 | // main.c 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "microvium.h" 15 | 16 | #define JS_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX 17 | #define TAG "microvium" 18 | 19 | static int32_t js_run(void* context); 20 | 21 | // A function in the host (this file) for the VM to call 22 | #define IMPORT_FLIPPER_FURI_DELAY_MS 1 23 | #define IMPORT_FLIPPER_CANVAS_STOP 2 24 | #define IMPORT_FLIPPER_CANVAS_SET_FONT 3 25 | #define IMPORT_FLIPPER_CANVAS_DRAW_STR 4 26 | #define IMPORT_FLIPPER_CANVAS_DRAW_STR_ALIGNED 5 27 | #define IMPORT_CONSOLE_CLEAR 6 28 | #define IMPORT_CONSOLE_LOG 7 29 | #define IMPORT_CONSOLE_WARN 8 30 | #define IMPORT_FS_OPEN_SYNC 9 31 | 32 | // A function exported by VM to for the host to call 33 | const mvm_VMExportID MAIN = 1; 34 | /* Use when needed 35 | const mvm_VMExportID INIT = 2; 36 | */ 37 | 38 | mvm_TeError resolveImport(mvm_HostFunctionID id, void*, mvm_TfHostFunction* out); 39 | mvm_TeError flipper_furi_delay_ms(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 40 | mvm_TeError flipper_canvas_stop(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 41 | mvm_TeError flipper_canvas_set_font(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 42 | mvm_TeError flipper_canvas_draw_str(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 43 | mvm_TeError flipper_canvas_draw_str_aligned(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 44 | mvm_TeError console_clear(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 45 | mvm_TeError console_log(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 46 | mvm_TeError console_warn(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 47 | mvm_TeError fs_open_sync(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 48 | 49 | typedef enum { 50 | MyEventTypeKey, 51 | MyEventTypeDone, 52 | } MyEventType; 53 | 54 | typedef struct { 55 | MyEventType type; // The reason for this event. 56 | InputEvent input; // This data is specific to keypress data. 57 | } MyEvent; 58 | 59 | typedef enum { 60 | JSDisplay, 61 | JSConsole, 62 | // JSConfirm, 63 | } ViewId; 64 | 65 | FuriMessageQueue* queue; 66 | ViewId current_view; 67 | 68 | ViewDispatcher* view_dispatcher; 69 | 70 | TextBox* text_box; 71 | 72 | /* 73 | bool confirmGot = false; 74 | bool confirmRes = false; 75 | */ 76 | 77 | typedef struct { 78 | FuriThread* thread; 79 | } JSRtThread; 80 | 81 | typedef struct { 82 | FuriString* conLog; 83 | } Console; 84 | 85 | Console* console; 86 | 87 | typedef enum { 88 | CNone, 89 | CDrawStr, 90 | CDrawStrAli, 91 | } CDrawEvent; 92 | 93 | typedef struct { 94 | CDrawEvent cEvent; 95 | Font font; 96 | const char* str; 97 | int x; 98 | int y; 99 | Align horizontal; 100 | Align vertical; 101 | } Display; 102 | 103 | Display* display; 104 | 105 | typedef struct { 106 | bool is_alloc; 107 | File* file; 108 | bool is_open; 109 | const char* path; 110 | } JSFile; 111 | 112 | JSFile jsFile[3]; 113 | 114 | Storage* storage; 115 | size_t fileSize; 116 | uint8_t* fileBuff; 117 | 118 | static void draw_callback(Canvas* canvas, void* context) { 119 | UNUSED(context); 120 | canvas_set_font(canvas, display->font); 121 | if(display->cEvent == CDrawStr) { 122 | canvas_draw_str(canvas, display->x, display->y, display->str); 123 | } else if (display->cEvent == CDrawStrAli) { 124 | canvas_draw_str_aligned(canvas, display->x, display->y, display->horizontal, display->vertical, display->str); 125 | } 126 | } 127 | 128 | static bool input_callback(InputEvent* input_event, void* context) { 129 | UNUSED(context); 130 | bool handled = false; 131 | // we set our callback context to be the view_dispatcher. 132 | 133 | if(input_event->type == InputTypeShort) { 134 | if(input_event->key == InputKeyBack) { 135 | // Default back handler. 136 | handled = false; 137 | } else if(input_event->key == InputKeyOk) { 138 | // switch the view! 139 | view_dispatcher_send_custom_event(view_dispatcher, 42); 140 | handled = true; 141 | } 142 | } 143 | 144 | return handled; 145 | } 146 | 147 | bool navigation_event_callback(void* context) { 148 | UNUSED(context); 149 | // We did not handle the event, so return false. 150 | return false; 151 | } 152 | 153 | bool custom_event_callback(void* context, uint32_t event) { 154 | UNUSED(context); 155 | bool handled = false; 156 | 157 | if(event == 42) { 158 | if(current_view == JSDisplay) { 159 | current_view = JSConsole; 160 | } 161 | 162 | view_dispatcher_switch_to_view(view_dispatcher, current_view); 163 | handled = true; 164 | } 165 | 166 | // NOTE: The return value is not currently used by the ViewDispatcher. 167 | return handled; 168 | } 169 | 170 | static uint32_t exit_console_callback(void* context) { 171 | UNUSED(context); 172 | return JSDisplay; 173 | } 174 | 175 | static int32_t js_run(void* context) { 176 | UNUSED(context); 177 | 178 | mvm_TeError err; 179 | mvm_VM* vm; 180 | mvm_Value main; 181 | mvm_Value result; 182 | 183 | // Restore the VM from the snapshot 184 | err = mvm_restore(&vm, fileBuff, fileSize, NULL, resolveImport); 185 | if (err != MVM_E_SUCCESS) { 186 | FURI_LOG_E(TAG, "Error with restore: %d", err); 187 | return err; 188 | } 189 | 190 | // Find the "sayHello" function exported by the VM 191 | err = mvm_resolveExports(vm, &MAIN, &main, 1); 192 | if (err != MVM_E_SUCCESS) { 193 | FURI_LOG_E(TAG, "Error with exports: %d", err); 194 | return err; 195 | } 196 | 197 | // Call "main" 198 | err = mvm_call(vm, main, &result, NULL, 0); 199 | if (err != MVM_E_SUCCESS) { 200 | FURI_LOG_E(TAG, "Error with call: %d", err); 201 | return err; 202 | } 203 | 204 | // Clean up 205 | mvm_runGC(vm, true); 206 | 207 | return 0; 208 | } 209 | 210 | int32_t js_app() { 211 | JSRtThread* jsThread = malloc(sizeof(JSRtThread)); 212 | 213 | storage = furi_record_open(RECORD_STORAGE); 214 | File* bytecode = storage_file_alloc(storage); 215 | storage_file_open(bytecode, APP_DATA_PATH("script.mvm-bc"), FSAM_READ, FSOM_OPEN_EXISTING); 216 | fileSize = storage_file_size(bytecode); 217 | FURI_LOG_I("microvium", "File Size: %d", fileSize); 218 | fileBuff = malloc(fileSize); 219 | storage_file_read(bytecode, fileBuff, fileSize); 220 | storage_file_close(bytecode); 221 | storage_file_free(bytecode); 222 | //furi_record_close(RECORD_STORAGE); 223 | 224 | for (int i = 0; i < 2; ++i) { 225 | jsFile[i].is_alloc = false; 226 | jsFile[i].is_open = false; 227 | } 228 | 229 | jsThread->thread = furi_thread_alloc_ex("microium", 1024, js_run, jsThread); 230 | 231 | view_dispatcher = view_dispatcher_alloc(); 232 | 233 | // For this demo, we just use view_dispatcher as our application context. 234 | void* context = view_dispatcher; 235 | 236 | View* view1 = view_alloc(); 237 | view_set_context(view1, context); 238 | view_set_draw_callback(view1, draw_callback); 239 | view_set_input_callback(view1, input_callback); 240 | view_set_orientation(view1, ViewOrientationHorizontal); 241 | 242 | text_box = text_box_alloc(); 243 | text_box_set_font(text_box, TextBoxFontText); 244 | view_set_previous_callback(text_box_get_view(text_box), exit_console_callback); 245 | 246 | /* 247 | DialogEx* dialog_ex = dialog_ex_alloc(); 248 | dialog_ex_set_left_button_text(dialog_ex, "No"); 249 | dialog_ex_set_right_button_text(dialog_ex, "Yes"); 250 | dialog_ex_set_context(dialog_ex, context); 251 | dialog_ex_set_result_callback(dialog_ex, confirm_callback); 252 | */ 253 | 254 | // set param 1 of custom event callback (impacts tick and navigation too). 255 | view_dispatcher_set_event_callback_context(view_dispatcher, context); 256 | view_dispatcher_set_navigation_event_callback( 257 | view_dispatcher, navigation_event_callback); 258 | view_dispatcher_set_custom_event_callback( 259 | view_dispatcher, custom_event_callback); 260 | view_dispatcher_enable_queue(view_dispatcher); 261 | view_dispatcher_add_view(view_dispatcher, JSDisplay, view1); 262 | view_dispatcher_add_view(view_dispatcher, JSConsole, text_box_get_view(text_box)); 263 | //view_dispatcher_add_view(view_dispatcher, JSConfirm, dialog_ex_get_view(dialog_ex)); 264 | 265 | Gui* gui = furi_record_open(RECORD_GUI); 266 | view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); 267 | current_view = JSDisplay; 268 | view_dispatcher_switch_to_view(view_dispatcher, current_view); 269 | 270 | // console init 271 | console = malloc(sizeof(Console)); 272 | console->conLog = furi_string_alloc(); 273 | 274 | // display init and set defaults 275 | display = malloc(sizeof(Display)); 276 | display->cEvent = CNone; 277 | display->font = FontSecondary; 278 | 279 | furi_thread_start(jsThread->thread); 280 | view_dispatcher_run(view_dispatcher); 281 | 282 | furi_thread_join(jsThread->thread); 283 | furi_thread_free(jsThread->thread); 284 | free(jsThread); 285 | 286 | furi_string_free(console->conLog); 287 | free(console); 288 | 289 | free(display); 290 | 291 | free(fileBuff); 292 | 293 | for (int i = 0; i < 2; ++i) { 294 | if(jsFile[i].is_alloc) { 295 | if(jsFile[i].is_open) { 296 | storage_file_close(jsFile[i].file); 297 | jsFile[i].is_open = false; 298 | } 299 | storage_file_free(jsFile[i].file); 300 | jsFile[i].is_alloc = false; 301 | } 302 | } 303 | 304 | view_dispatcher_remove_view(view_dispatcher, JSDisplay); 305 | view_dispatcher_remove_view(view_dispatcher, JSConsole); 306 | furi_record_close(RECORD_GUI); 307 | view_dispatcher_free(view_dispatcher); 308 | 309 | return 0; 310 | } 311 | 312 | void fatalError(void* vm, int e) { 313 | UNUSED(vm); 314 | FURI_LOG_E(TAG, "Error: %d\n", e); 315 | furi_crash("Microvium fatal error"); 316 | } 317 | 318 | /* 319 | * This function is called by `mvm_restore` to search for host functions 320 | * imported by the VM based on their ID. Given an ID, it needs to pass back 321 | * a pointer to the corresponding C function to be used by the VM. 322 | */ 323 | mvm_TeError resolveImport(mvm_HostFunctionID funcID, void* context, mvm_TfHostFunction* out) { 324 | UNUSED(context); 325 | if (funcID == IMPORT_FLIPPER_FURI_DELAY_MS) { 326 | *out = flipper_furi_delay_ms; 327 | } else if (funcID == IMPORT_FLIPPER_CANVAS_SET_FONT) { 328 | *out = flipper_canvas_set_font; 329 | return MVM_E_SUCCESS; 330 | } else if (funcID == IMPORT_FLIPPER_CANVAS_DRAW_STR) { 331 | *out = flipper_canvas_draw_str; 332 | return MVM_E_SUCCESS; 333 | } else if (funcID == IMPORT_FLIPPER_CANVAS_DRAW_STR_ALIGNED) { 334 | *out = flipper_canvas_draw_str_aligned; 335 | } else if (funcID == IMPORT_CONSOLE_LOG) { 336 | *out = console_log; 337 | return MVM_E_SUCCESS; 338 | } else if (funcID == IMPORT_CONSOLE_CLEAR) { 339 | *out = console_clear; 340 | return MVM_E_SUCCESS; 341 | } else if (funcID == IMPORT_CONSOLE_WARN) { 342 | *out = console_warn; 343 | return MVM_E_SUCCESS; 344 | } else if (funcID == IMPORT_FS_OPEN_SYNC) { 345 | *out = fs_open_sync; 346 | return MVM_E_SUCCESS; 347 | } 348 | return MVM_E_UNRESOLVED_IMPORT; 349 | } 350 | 351 | mvm_TeError flipper_furi_delay_ms(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 352 | UNUSED(funcID); 353 | UNUSED(result); 354 | furi_assert(argCount == 1); 355 | FURI_LOG_I(TAG, "delay_ms()"); 356 | furi_delay_ms((int32_t)mvm_toInt32(vm, args[0])); 357 | return MVM_E_SUCCESS; 358 | } 359 | 360 | mvm_TeError flipper_canvas_stop(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 361 | UNUSED(vm); 362 | UNUSED(funcID); 363 | UNUSED(result); 364 | UNUSED(args); 365 | furi_assert(argCount == 0); 366 | FURI_LOG_I(TAG, "canvas_stop()"); 367 | display->cEvent = CNone; 368 | return MVM_E_SUCCESS; 369 | } 370 | 371 | mvm_TeError flipper_canvas_set_font(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 372 | UNUSED(funcID); 373 | UNUSED(result); 374 | furi_assert(argCount == 1); 375 | FURI_LOG_I(TAG, "canvas_set_font()"); 376 | // display->cEvent = CSetFont; 377 | display->font = mvm_toInt32(vm, args[0]); 378 | // display->cEvent = CNone; 379 | return MVM_E_SUCCESS; 380 | } 381 | 382 | mvm_TeError flipper_canvas_draw_str(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 383 | UNUSED(funcID); 384 | UNUSED(result); 385 | furi_assert(argCount == 3); 386 | FURI_LOG_I(TAG, "canvas_draw_str()"); 387 | display->x = (int32_t)mvm_toInt32(vm, args[0]); 388 | display->y = (int32_t)mvm_toInt32(vm, args[1]); 389 | display->str = (const char*)mvm_toStringUtf8(vm, args[2], NULL); 390 | display->cEvent = CDrawStr; 391 | return MVM_E_SUCCESS; 392 | } 393 | 394 | mvm_TeError flipper_canvas_draw_str_aligned(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 395 | UNUSED(funcID); 396 | UNUSED(result); 397 | furi_assert(argCount == 5); 398 | FURI_LOG_I(TAG, "canvas_draw_str_aligned()"); 399 | display->x = (int32_t)mvm_toInt32(vm, args[0]); 400 | display->y = (int32_t)mvm_toInt32(vm, args[1]); 401 | display->horizontal = (int32_t)mvm_toInt32(vm, args[2]); 402 | display->vertical = (int32_t)mvm_toInt32(vm, args[3]); 403 | display->str = (const char*)mvm_toStringUtf8(vm, args[4], NULL); 404 | display->cEvent = CDrawStrAli; 405 | return MVM_E_SUCCESS; 406 | } 407 | 408 | mvm_TeError console_clear(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 409 | UNUSED(vm); 410 | UNUSED(funcID); 411 | UNUSED(result); 412 | UNUSED(args); 413 | furi_assert(argCount == 0); 414 | FURI_LOG_I(TAG, "console.clear()"); 415 | furi_string_reset(console->conLog); 416 | text_box_set_text(text_box, furi_string_get_cstr(console->conLog)); 417 | return MVM_E_SUCCESS; 418 | } 419 | 420 | mvm_TeError console_log(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 421 | UNUSED(funcID); 422 | UNUSED(result); 423 | FURI_LOG_I(TAG, "console.log()"); 424 | for (int i = 0; i < argCount-1; i++) { 425 | if(mvm_typeOf(vm, args[i]) == VM_T_NUMBER) { 426 | furi_string_cat_printf(console->conLog, "%d", (int)mvm_toInt32(vm, args[i])); 427 | } else if (mvm_typeOf(vm, args[i]) == VM_T_STRING) { 428 | furi_string_cat_printf(console->conLog, "%s", (const char*)mvm_toStringUtf8(vm, args[i], NULL)); 429 | } 430 | } 431 | furi_string_cat_printf(console->conLog, "\n"); 432 | text_box_set_text(text_box, furi_string_get_cstr(console->conLog)); 433 | return MVM_E_SUCCESS; 434 | } 435 | 436 | mvm_TeError console_warn(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 437 | UNUSED(funcID); 438 | UNUSED(result); 439 | furi_assert(argCount == 1); 440 | FURI_LOG_I(TAG, "console.warn()"); 441 | for (int i = 0; i < argCount-1; i++) { 442 | if(mvm_typeOf(vm, args[i]) == VM_T_NUMBER) { 443 | FURI_LOG_W(TAG, "%d", (int)mvm_toInt32(vm, args[i])); 444 | } else if (mvm_typeOf(vm, args[i]) == VM_T_STRING) { 445 | FURI_LOG_W(TAG, "%s", (const char*)mvm_toStringUtf8(vm, args[i], NULL)); 446 | } 447 | } 448 | FURI_LOG_W(TAG, "\n"); 449 | return MVM_E_SUCCESS; 450 | } 451 | 452 | mvm_TeError fs_open_sync(mvm_VM* vm, mvm_HostFunctionID funcID, mvm_Value* result, mvm_Value* args, uint8_t argCount) { 453 | UNUSED(funcID); 454 | furi_assert(argCount == 2); 455 | //const char* path = (const char*)mvm_toStringUtf8(vm, args[0], NULL); 456 | //const char* type = (const char*)mvm_toStringUtf8(vm, args[1], NULL); 457 | for (int i = 0; i < 2; ++i) { 458 | if (!jsFile[i].is_alloc) { 459 | jsFile[i].file = storage_file_alloc(storage); 460 | jsFile[i].is_alloc = true; 461 | } 462 | if (!jsFile[i].is_open) { 463 | jsFile[i].path = (const char*)mvm_toStringUtf8(vm, args[0], NULL); 464 | storage_file_open(jsFile[i].file, jsFile[i].path, FSAM_READ, FSOM_OPEN_EXISTING); 465 | jsFile[i].is_open = true; 466 | *result = mvm_newInt32(vm, i); 467 | return MVM_E_SUCCESS; 468 | } 469 | } 470 | return MVM_E_SUCCESS; // todo: return error 471 | } -------------------------------------------------------------------------------- /lib/microvium/microvium.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Hunter. Part of the Microvium project. Links to full code at https://microvium.com for license details. 2 | 3 | /* 4 | * Microvium Bytecode Interpreter 5 | * 6 | * Version: 8.0.0 7 | * 8 | * This is the main header for the Microvium bytecode interpreter. Latest source 9 | * available at https://microvium.com. Raise issues at 10 | * https://github.com/coder-mike/microvium/issues. 11 | */ 12 | #pragma once 13 | 14 | #include "microvium_port.h" 15 | #include 16 | #include 17 | 18 | #define MVM_ENGINE_MAJOR_VERSION 8 /* aka MVM_BYTECODE_VERSION */ 19 | #define MVM_ENGINE_MINOR_VERSION 0 /* aka MVM_ENGINE_VERSION */ 20 | 21 | typedef uint16_t mvm_Value; 22 | typedef uint16_t mvm_VMExportID; 23 | typedef uint16_t mvm_HostFunctionID; 24 | 25 | typedef enum mvm_TeError { 26 | /* 0 */ MVM_E_SUCCESS, 27 | /* 1 */ MVM_E_UNEXPECTED, 28 | /* 2 */ MVM_E_MALLOC_FAIL, 29 | /* 3 */ MVM_E_ALLOCATION_TOO_LARGE, 30 | /* 4 */ MVM_E_INVALID_ADDRESS, 31 | /* 5 */ MVM_E_COPY_ACROSS_BUCKET_BOUNDARY, 32 | /* 6 */ MVM_E_FUNCTION_NOT_FOUND, 33 | /* 7 */ MVM_E_INVALID_HANDLE, 34 | /* 8 */ MVM_E_STACK_OVERFLOW, 35 | /* 9 */ MVM_E_UNRESOLVED_IMPORT, 36 | /* 10 */ MVM_E_ATTEMPT_TO_WRITE_TO_ROM, 37 | /* 11 */ MVM_E_INVALID_ARGUMENTS, 38 | /* 12 */ MVM_E_TYPE_ERROR, 39 | /* 13 */ MVM_E_TARGET_NOT_CALLABLE, 40 | /* 14 */ MVM_E_HOST_ERROR, 41 | /* 15 */ MVM_E_NOT_IMPLEMENTED, 42 | /* 16 */ MVM_E_HOST_RETURNED_INVALID_VALUE, 43 | /* 17 */ MVM_E_ASSERTION_FAILED, 44 | /* 18 */ MVM_E_INVALID_BYTECODE, 45 | /* 19 */ MVM_E_UNRESOLVED_EXPORT, 46 | /* 20 */ MVM_E_RANGE_ERROR, 47 | /* 21 */ MVM_E_DETACHED_EPHEMERAL, 48 | /* 22 */ MVM_E_TARGET_IS_NOT_A_VM_FUNCTION, 49 | /* 23 */ MVM_E_FLOAT64, 50 | /* 24 */ MVM_E_NAN, 51 | /* 25 */ MVM_E_NEG_ZERO, 52 | /* 26 */ MVM_E_OPERATION_REQUIRES_FLOAT_SUPPORT, 53 | /* 27 */ MVM_E_BYTECODE_CRC_FAIL, 54 | /* 28 */ MVM_E_BYTECODE_REQUIRES_FLOAT_SUPPORT, 55 | /* 29 */ MVM_E_PROTO_IS_READONLY, // The __proto__ property of objects and arrays is not mutable 56 | /* 30 */ MVM_E_SNAPSHOT_TOO_LARGE, // The resulting snapshot does not fit in the 64kB boundary 57 | /* 31 */ MVM_E_MALLOC_MUST_RETURN_POINTER_TO_EVEN_BOUNDARY, 58 | /* 32 */ MVM_E_ARRAY_TOO_LONG, 59 | /* 33 */ MVM_E_OUT_OF_MEMORY, // Allocating a new block of memory from the host causes it to exceed MVM_MAX_HEAP_SIZE 60 | /* 34 */ MVM_E_TOO_MANY_ARGUMENTS, // Exceeded the maximum number of arguments for a function (255) 61 | /* 35 */ MVM_E_REQUIRES_LATER_ENGINE, // Please update your microvium.h and microvium.c files 62 | /* 36 */ MVM_E_PORT_FILE_VERSION_MISMATCH, // Please migrate your port file to the required version 63 | /* 37 */ MVM_E_PORT_FILE_MACRO_TEST_FAILURE, // Something in microvium_port.h doesn't behave as expected 64 | /* 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 65 | /* 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 66 | /* 40 */ MVM_E_TYPE_ERROR_TARGET_IS_NOT_CALLABLE, // The script tried to call something that wasn't a function 67 | /* 41 */ MVM_E_TDZ_ERROR, // The script tried to access a local variable before its declaration 68 | /* 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 69 | /* 43 */ MVM_E_INVALID_ARRAY_INDEX, // Array indexes must be integers in the range 0 to 8191 70 | /* 44 */ MVM_E_UNCAUGHT_EXCEPTION, // The script threw an exception with `throw` that was wasn't caught before returning to the host 71 | /* 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) 72 | /* 46 */ MVM_E_OBJECT_KEYS_ON_NON_OBJECT, // Can only use Reflect.ownKeys on plain objects (not functions, arrays, or other values) 73 | /* 47 */ MVM_E_INVALID_UINT8_ARRAY_LENGTH, // Either non-numeric or out-of-range argument for creating a Uint8Array 74 | /* 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 75 | /* 49 */ MVM_E_WRONG_BYTECODE_VERSION, // The version of bytecode is different to what the engine supports 76 | /* 50 */ MVM_E_USING_NEW_ON_NON_CLASS, // The `new` operator can only be used on classes 77 | /* 51 */ MVM_E_INSTRUCTION_COUNT_REACHED, // The instruction count set by `mvm_stopAfterNInstructions` has been reached 78 | /* 52 */ MVM_E_REQUIRES_ACTIVE_VM, // The given operation requires that the VM has active calls on the stack 79 | /* 53 */ MVM_E_ASYNC_START_ERROR, // mvm_asyncStart must be called exactly once at the beginning of a host function that is called from JS 80 | /* 54 */ MVM_E_ASYNC_WITHOUT_AWAIT, // mvm_asyncStart can only be used with a script that has await points. Add at least one (reachable) await point to the script. 81 | /* 55 */ MVM_E_TYPE_ERROR_AWAIT_NON_PROMISE, // Can only await a promise in Microvium 82 | /* 56 */ MVM_E_HEAP_CORRUPT, // Microvium's internal heap is not in a consistent state 83 | /* 57 */ MVM_E_CLASS_PROTOTYPE_MUST_BE_NULL_OR_OBJECT, // The prototype property of a class must be null or a plain object 84 | } mvm_TeError; 85 | 86 | typedef enum mvm_TeType { 87 | VM_T_UNDEFINED = 0, 88 | VM_T_NULL = 1, 89 | VM_T_BOOLEAN = 2, 90 | VM_T_NUMBER = 3, 91 | VM_T_STRING = 4, 92 | VM_T_FUNCTION = 5, 93 | VM_T_OBJECT = 6, 94 | VM_T_ARRAY = 7, 95 | VM_T_UINT8_ARRAY = 8, 96 | VM_T_CLASS = 9, 97 | VM_T_SYMBOL = 10, // Reserved 98 | VM_T_BIG_INT = 11, // Reserved 99 | 100 | VM_T_END, 101 | } mvm_TeType; 102 | 103 | // Prefix to attach to exported microvium API functions. If a user doesn't 104 | // specify this, we just set it up as the empty macro. 105 | #ifndef MVM_EXPORT 106 | #define MVM_EXPORT 107 | #endif 108 | 109 | #ifndef MVM_SUPPORT_FLOAT 110 | #define MVM_SUPPORT_FLOAT 1 111 | #endif 112 | 113 | #ifndef MVM_FLOAT64 114 | #define MVM_FLOAT64 double 115 | #endif 116 | 117 | #ifndef MVM_INCLUDE_DEBUG_CAPABILITY 118 | #define MVM_INCLUDE_DEBUG_CAPABILITY 1 119 | #endif 120 | 121 | typedef struct mvm_VM mvm_VM; 122 | 123 | typedef mvm_TeError (*mvm_TfHostFunction)(mvm_VM* vm, mvm_HostFunctionID hostFunctionID, mvm_Value* result, mvm_Value* args, uint8_t argCount); 124 | 125 | typedef mvm_TeError (*mvm_TfResolveImport)(mvm_HostFunctionID hostFunctionID, void* context, mvm_TfHostFunction* out_hostFunction); 126 | 127 | typedef void (*mvm_TfBreakpointCallback)(mvm_VM* vm, uint16_t bytecodeAddress); 128 | 129 | typedef struct mvm_TsMemoryStats { 130 | // Total RAM currently allocated by the VM from the host 131 | size_t totalSize; 132 | 133 | // Number of distinct, currently-allocated memory allocations (mallocs) from the host 134 | size_t fragmentCount; 135 | 136 | // RAM size of VM core state 137 | size_t coreSize; 138 | 139 | // RAM allocated to the VM import table (table of functions resolved from the host) 140 | size_t importTableSize; 141 | 142 | // RAM allocated to global variables in RAM 143 | size_t globalVariablesSize; 144 | 145 | // If the machine registers are allocated (if a call is active), this says how 146 | // much RAM these consume. Otherwise zero if there is no active stack. 147 | size_t registersSize; 148 | 149 | // Virtual stack size (bytes) currently allocated (if a call is active), or 150 | // zero if there is no active stack. Note that virtual stack space is 151 | // malloc'd, not allocated on the C stack. 152 | size_t stackHeight; 153 | 154 | // Virtual stack space capacity if a call is active, otherwise zero. 155 | size_t stackAllocatedCapacity; 156 | 157 | // Maximum stack size over the lifetime of the VM. This value can be used to 158 | // tune the MVM_STACK_SIZE port definition 159 | size_t stackHighWaterMark; 160 | 161 | // Amount of virtual heap that the VM is currently using 162 | size_t virtualHeapUsed; 163 | 164 | // Maximum amount of virtual heap space ever used by this VM 165 | size_t virtualHeapHighWaterMark; 166 | 167 | // Current total size of virtual heap (will expand as needed up to a max of MVM_MAX_HEAP_SIZE) 168 | size_t virtualHeapAllocatedCapacity; 169 | 170 | } mvm_TsMemoryStats; 171 | 172 | /** 173 | * A handle holds a value that must not be garbage collected. 174 | * 175 | * Maintainer note: `_value` is the first field so that a `mvm_Handle*` is also 176 | * a `mvm_Value*`, which allows some internal functions to be polymorphic in 177 | * whether they accept handles or just plain value pointers. 178 | */ 179 | typedef struct mvm_Handle { mvm_Value _value; struct mvm_Handle* _next; } mvm_Handle; 180 | 181 | #include "microvium_port.h" 182 | 183 | #ifdef __cplusplus 184 | extern "C" { 185 | #endif 186 | 187 | /** 188 | * Creates a VM (restores the state of a virtual machine from a snapshot) 189 | * 190 | * WARNING: The snapshot bytecode is not copied by Microvium, so it needs to 191 | * stay in memory (or flash). 192 | * 193 | * A VM created with mvm_restore needs to be freed with mvm_free. 194 | * 195 | * Note: the bytecode should be aligned to the processor word size. 196 | * 197 | * Note: the bytecode needs to live as long as the VM. 198 | * 199 | * @param resolveImport A callback function that the VM will call when it needs 200 | * to import a host function. 201 | * @param context Any value. The context for a VM can be retrieved later using 202 | * `mvm_getContext`. It can be used to attach user-defined data to a VM. 203 | */ 204 | MVM_EXPORT mvm_TeError mvm_restore(mvm_VM** result, MVM_LONG_PTR_TYPE snapshotBytecode, size_t bytecodeSize, void* context, mvm_TfResolveImport resolveImport); 205 | 206 | /** 207 | * Free all memory associated with a VM. The VM must not be used again after freeing. 208 | */ 209 | MVM_EXPORT void mvm_free(mvm_VM* vm); 210 | 211 | /** 212 | * Call a function in the VM 213 | * 214 | * @param func The function value to call 215 | * @param out_result Where to put the result, or NULL if the result is not 216 | * needed 217 | * @param args Pointer to arguments array, or NULL if no arguments 218 | * @param argCount Number of arguments 219 | * 220 | * If the JS code throws an exception, the return value will be 221 | * MVM_E_UNCAUGHT_EXCEPTION and the exception value will be put into 222 | * `out_result` 223 | */ 224 | MVM_EXPORT mvm_TeError mvm_call(mvm_VM* vm, mvm_Value func, mvm_Value* out_result, mvm_Value* args, uint8_t argCount); 225 | 226 | MVM_EXPORT void* mvm_getContext(mvm_VM* vm); 227 | 228 | /** 229 | * Handle operations. Handles are used to hold values that must not be garbage 230 | * collected. See `doc\handles-and-garbage-collection.md` for more information. 231 | */ 232 | MVM_EXPORT void mvm_initializeHandle(mvm_VM* vm, mvm_Handle* handle); // Handle must be released by mvm_releaseHandle 233 | MVM_EXPORT void mvm_cloneHandle(mvm_VM* vm, mvm_Handle* target, const mvm_Handle* source); // Target must be released by mvm_releaseHandle 234 | MVM_EXPORT mvm_TeError mvm_releaseHandle(mvm_VM* vm, mvm_Handle* handle); 235 | static inline mvm_Value mvm_handleGet(const mvm_Handle* handle) { return handle->_value; } 236 | static inline mvm_Value* mvm_handleAt(mvm_Handle* handle) { return &handle->_value; } 237 | static inline void mvm_handleSet(mvm_Handle* handle, mvm_Value value) { handle->_value = value; } 238 | 239 | /** 240 | * Roughly like the `typeof` operator in JS, except with distinct values for 241 | * null and arrays 242 | */ 243 | MVM_EXPORT mvm_TeType mvm_typeOf(mvm_VM* vm, mvm_Value value); 244 | 245 | /** 246 | * Converts the value to a string encoded as UTF-8. 247 | * 248 | * @param out_sizeBytes Returns the length of the string in bytes, or provide NULL if not needed. 249 | * @return A pointer to the string data which may be in VM memory or bytecode. 250 | * 251 | * Note: for convenience, the returned data has an extra null character appended 252 | * to the end of it, so that the result is directly usable in printf, strcpy, 253 | * etc. The returned size in bytes is the size of the original string data, 254 | * excluding the extra null. 255 | * 256 | * The string data itself is permitted to contain nulls or any other data. For 257 | * example, if the string value is "abc\0", the size returned is "4", and the 258 | * returned pointer points to the data "abc\0\0" (i.e. with the extra safety 259 | * null beyond the user-provided data). 260 | * 261 | * The memory pointed to by the return value may be transient: it is only guaranteed 262 | * to exist until the next garbage collection cycle. See 263 | * [memory-management.md](https://github.com/coder-mike/microvium/blob/master/doc/native-vm/memory-management.md) 264 | * for details. 265 | */ 266 | MVM_EXPORT const char* mvm_toStringUtf8(mvm_VM* vm, mvm_Value value, size_t* out_sizeBytes); 267 | 268 | /** 269 | * Returns the length of a string as it appears in bytes when encoded as UTF-8 270 | * (which is also the internal representation of Microvium). 271 | */ 272 | MVM_EXPORT size_t mvm_stringSizeUtf8(mvm_VM* vm, mvm_Value value); 273 | 274 | /** 275 | * Convert the value to a bool based on its truthiness. 276 | * 277 | * See https://developer.mozilla.org/en-US/docs/Glossary/Truthy 278 | */ 279 | MVM_EXPORT bool mvm_toBool(mvm_VM* vm, mvm_Value value); 280 | 281 | /** 282 | * Converts the value to a 32-bit signed integer. 283 | * 284 | * The result of this should be the same as `value|0` in JavaScript code. 285 | */ 286 | MVM_EXPORT int32_t mvm_toInt32(mvm_VM* vm, mvm_Value value); 287 | 288 | #if MVM_SUPPORT_FLOAT 289 | /** 290 | * Converts the value to a number. 291 | * 292 | * The result of this should be the same as `+value` in JavaScript code. 293 | * 294 | * For efficiency, use mvm_toInt32 instead if your value is an integer. 295 | */ 296 | MVM_EXPORT MVM_FLOAT64 mvm_toFloat64(mvm_VM* vm, mvm_Value value); 297 | 298 | /** 299 | * Create a JavaScript number value in the VM. 300 | * 301 | * WARNING: the result is eligible for garbage collection the next time the VM 302 | * has control. See `doc\handles-and-garbage-collection.md` for more information. 303 | * 304 | * For efficiency, use mvm_newInt32 instead if your value is an integer. 305 | * 306 | * Design note: mvm_newNumber creates a number *from* a float64, so it's named 307 | * `newNumber` and not `newFloat64` 308 | */ 309 | MVM_EXPORT mvm_Value mvm_newNumber(mvm_VM* vm, MVM_FLOAT64 value); 310 | #endif 311 | 312 | MVM_EXPORT bool mvm_isNaN(mvm_Value value); 313 | 314 | extern const mvm_Value mvm_undefined; 315 | extern const mvm_Value mvm_null; 316 | 317 | /** 318 | * Create a JavaScript boolean value in the VM. 319 | */ 320 | MVM_EXPORT mvm_Value mvm_newBoolean(bool value); 321 | 322 | /** 323 | * Create a JavaScript number from a 32-bit integer. 324 | * 325 | * WARNING: the result is eligible for garbage collection the next time the VM 326 | * has control. See `doc\handles-and-garbage-collection.md` for more information. 327 | */ 328 | MVM_EXPORT mvm_Value mvm_newInt32(mvm_VM* vm, int32_t value); 329 | 330 | /** 331 | * Create a new string in Microvium memory. 332 | * 333 | * WARNING: the result is eligible for garbage collection the next time the VM 334 | * has control. See `doc\handles-and-garbage-collection.md` for more information. 335 | * 336 | * @param valueUtf8 The a pointer to the string content. 337 | * @param sizeBytes The size in bytes of the string, excluding any null terminator. 338 | */ 339 | MVM_EXPORT mvm_Value mvm_newString(mvm_VM* vm, const char* valueUtf8, size_t sizeBytes); 340 | 341 | /** 342 | * A Uint8Array in Microvium is an efficient buffer of bytes. It is mutable but 343 | * cannot be resized. The new Uint8Array created by this method will contain a 344 | * *copy* of the supplied data. 345 | * 346 | * WARNING: the result is eligible for garbage collection the next time the VM 347 | * has control. See `doc\handles-and-garbage-collection.md` for more information. 348 | * 349 | * Within the VM, you can create a new Uint8Array using the global 350 | * `Microvium.newUint8Array`. 351 | * 352 | * See also: mvm_uint8ArrayToBytes 353 | */ 354 | MVM_EXPORT mvm_Value mvm_uint8ArrayFromBytes(mvm_VM* vm, const uint8_t* data, size_t size); 355 | 356 | /** 357 | * Given a Uint8Array, this will give a pointer to its data and its size (in 358 | * bytes). 359 | * 360 | * Warning: The data pointer should be considered invalid on the next call to 361 | * any of the Microvium API methods, since a garbage can move the data. It is 362 | * recommended to call this method again each time you need the pointer. 363 | * 364 | * The returned pointer can also be used to mutate the buffer, with caution. 365 | * 366 | * See also: mvm_uint8ArrayFromBytes 367 | */ 368 | MVM_EXPORT mvm_TeError mvm_uint8ArrayToBytes(mvm_VM* vm, mvm_Value uint8ArrayValue, uint8_t** out_data, size_t* out_size); 369 | 370 | /** 371 | * Resolves (finds) the values exported by the VM, identified by ID. 372 | * 373 | * @param ids An array of `count` IDs to look up. 374 | * @param results An array of `count` output values that result from each 375 | * lookup 376 | * 377 | * Note: Exports are immutable (shallow immutable), so they don't need to be 378 | * captured by a mvm_Handle. In typical usage, exports will each be function 379 | * values, but any value type is valid. 380 | */ 381 | MVM_EXPORT mvm_TeError mvm_resolveExports(mvm_VM* vm, const mvm_VMExportID* ids, mvm_Value* results, uint8_t count); 382 | 383 | /** 384 | * Run a garbage collection cycle. 385 | * 386 | * If `squeeze` is `true`, the GC runs in 2 passes: the first pass computes the 387 | * exact required size, and the second pass compacts into exactly that size. 388 | * 389 | * If `squeeze` is `false`, the GC runs in a single pass, estimating the amount 390 | * of needed as the amount of space used after the last compaction, and then 391 | * adding blocks as-necessary. 392 | */ 393 | MVM_EXPORT void mvm_runGC(mvm_VM* vm, bool squeeze); 394 | 395 | /** 396 | * Compares two values for equality. The same semantics as JavaScript `===` 397 | */ 398 | MVM_EXPORT bool mvm_equal(mvm_VM* vm, mvm_Value a, mvm_Value b); 399 | 400 | /** 401 | * The current bytecode address being executed (relative to the beginning of the 402 | * bytecode image), or null if the machine is not currently active. 403 | * 404 | * This value can be looked up in the map file generated by the CLI flag 405 | * `--map-file` 406 | */ 407 | MVM_EXPORT uint16_t mvm_getCurrentAddress(mvm_VM* vm); 408 | 409 | /** 410 | * Get stats about the VM memory 411 | */ 412 | MVM_EXPORT void mvm_getMemoryStats(mvm_VM* vm, mvm_TsMemoryStats* out_stats); 413 | 414 | 415 | /** 416 | * Call this at the beginning of an asynchronous host function. It accepts a 417 | * pointer to the synchronous result and returns a callback function that can be 418 | * used to set the asynchronous result. 419 | * 420 | * @param out_result The result pointer that was passed to the host function. 421 | * mvm_asyncStart will set the result. The host must not set 422 | * or use the result field. 423 | * 424 | * @returns A JavaScript callback function accepting arguments (isSuccess, 425 | * value) 426 | * 427 | * This function sets the synchronous result `*out_result` to a promise object 428 | * (or this promise value may be optimized away in certain cases), and returns a 429 | * JavaScript callback function that represents the caller continuation. 430 | * 431 | * If the asynchronous operation ends successfully, call the callback with 432 | * arguments (true, result). If the asynchronous operation fails, call the 433 | * callback with arguments (false, error). 434 | * 435 | * Note: The callback will not invoke the continuation immediately but will 436 | * schedule it on Microvium's job queue. 437 | * 438 | * Note: mvm_asyncStart does not do anything regarding threading. It's up to the 439 | * host to promptly return from the host function and to call the callback 440 | * later. 441 | * 442 | * @warning The returned mvm_Value is subject to garbage collection and the host 443 | * should keep it in a handle until it's ready to call it. 444 | * 445 | * @warning The host must call `mvm_asyncStart` right at the beginning of the 446 | * host function, before doing anything else, since this accesses an internal 447 | * register that is not preserved across function calls. 448 | */ 449 | mvm_Value mvm_asyncStart(mvm_VM* vm, mvm_Value* out_result); 450 | 451 | 452 | #if MVM_INCLUDE_SNAPSHOT_CAPABILITY 453 | /** 454 | * Create a snapshot of the VM 455 | * 456 | * @param vm The virtual machine to snapshot. 457 | * @param out_size Pointer to variable which will receive the size of the 458 | * generated snapshot 459 | * 460 | * The snapshot generated by this function is suitable to be used in a call to 461 | * mvm_restore to be restored later. 462 | * 463 | * It's recommended to run a garbage collection cycle (mvm_runGC) before 464 | * creating the snapshot, to get as compact a snapshot as possible. 465 | * 466 | * No snapshots ever contain the stack or register states -- they only encode 467 | * the heap and global variable states. 468 | * 469 | * Note: The result is malloc'd on the host heap, and so needs to be freed with 470 | * a call to *free*. 471 | */ 472 | MVM_EXPORT void* mvm_createSnapshot(mvm_VM* vm, size_t* out_size); 473 | #endif // MVM_INCLUDE_SNAPSHOT_CAPABILITY 474 | 475 | #if MVM_INCLUDE_DEBUG_CAPABILITY 476 | /** 477 | * Set a breakpoint on the given bytecode address. 478 | * 479 | * Use (-1) to break on every instruction. 480 | * 481 | * Whenever the VM executes the instruction at the given bytecode address, the 482 | * VM will invoke the breakpoint callback (see mvm_dbg_setBreakpointCallback). 483 | * 484 | * The given bytecode address is measured from the beginning of the given 485 | * bytecode image (passed to mvm_restore). The address point exactly to the 486 | * beginning of a bytecode instruction (addresses corresponding to the middle of 487 | * a multi-byte instruction are ignored). 488 | * 489 | * The breakpoint remains registered/active until mvm_dbg_removeBreakpoint is 490 | * called with the exact same bytecode address. 491 | * 492 | * Setting a breakpoint a second time on the same address of an existing active 493 | * breakpoint will have no effect. 494 | */ 495 | MVM_EXPORT void mvm_dbg_setBreakpoint(mvm_VM* vm, int bytecodeAddress); 496 | 497 | /** 498 | * Remove a breakpoint added by mvm_dbg_setBreakpoint 499 | */ 500 | MVM_EXPORT void mvm_dbg_removeBreakpoint(mvm_VM* vm, uint16_t bytecodeAddress); 501 | 502 | /** 503 | * Set the function to be called when any breakpoint is hit. 504 | * 505 | * The callback only applies to the given virtual machine (the callback can be 506 | * different for different VMs). 507 | * 508 | * The callback is invoked with the bytecode address corresponding to where the 509 | * VM is stopped. The VM will continue execution when the breakpoint callback 510 | * returns. To suspend the VM indefinitely, the callback needs to 511 | * correspondingly block indefinitely. 512 | * 513 | * It's possible but not recommended for the callback itself call into the VM 514 | * again (mvm_call), causing control to re-enter the VM while the breakpoint is 515 | * still active. This should *NOT* be used to continue execution, but could 516 | * theoretically be used to evaluate debug watch expressions. 517 | */ 518 | MVM_EXPORT void mvm_dbg_setBreakpointCallback(mvm_VM* vm, mvm_TfBreakpointCallback cb); 519 | #endif // MVM_INCLUDE_DEBUG_CAPABILITY 520 | 521 | #ifdef MVM_GAS_COUNTER 522 | /** 523 | * mvm_stopAfterNInstructions 524 | * 525 | * Sets the VM to stop (return error MVM_E_INSTRUCTION_COUNT_REACHED) after n 526 | * further bytecode instructions have been executed. This may help the host to 527 | * catch run-away VMs or infinite loops. Use n = -1 to disable the limit. 528 | * 529 | * If `n` is zero, the VM will stop before executing any further instructions. 530 | * 531 | * When the VM reaches the stopped state, further calls to the VM will fail with 532 | * the same error until `mvm_stopAfterNInstructions` is called again to reset 533 | * the countdown. 534 | * 535 | * The stopping unwinds the current call stack in a similar way to an exception, 536 | * except that it will not hit any catch blocks. However, be aware that if the 537 | * call to the VM is reentrant (e.g. the host calls the VM which calls the host 538 | * which calls the VM again), the stopping will only unwind the innermost call 539 | * stack. The outer call stack will then unwind if the inner host function 540 | * returns an error code (e.g. propagating the MVM_E_INSTRUCTION_COUNT_REACHED) 541 | * or simply does not reset the countdown, so that the VM will fail again when 542 | * the host returns control to the VM. 543 | */ 544 | MVM_EXPORT void mvm_stopAfterNInstructions(mvm_VM* vm, int32_t n); 545 | 546 | /** 547 | * mvm_getInstructionCountRemaining 548 | * 549 | * If `mvm_stopAfterNInstructions` has been used to set a limit on the number of 550 | * instructions to execute, this function can be used to see the remaining 551 | * number of instructions before the VM will stop. 552 | * 553 | * The return value will be negative if the countdown is currently disabled. 554 | */ 555 | MVM_EXPORT int32_t mvm_getInstructionCountRemaining(mvm_VM* vm); 556 | #endif // MVM_GAS_COUNTER 557 | 558 | #ifdef __cplusplus 559 | } // extern "C" 560 | #endif 561 | --------------------------------------------------------------------------------