better support for memory_grow and memory_size in OVM
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 12 Feb 2023 02:09:28 +0000 (20:09 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 12 Feb 2023 02:09:28 +0000 (20:09 -0600)
interpreter/include/vm.h
interpreter/include/vm_codebuilder.h
interpreter/src/vm/code_builder.c
interpreter/src/vm/vm.c
interpreter/src/vm/vm_instrs.h
interpreter/src/wasm/instance.c
interpreter/src/wasm/memory.c
interpreter/src/wasm/module_parsing.h

index d3664a8bf55659115b97597cadb1415b9fe77598..85a75789e2d63871833c6407763a25441c9e2ef2 100644 (file)
@@ -119,6 +119,7 @@ struct ovm_engine_t {
 
 ovm_engine_t *ovm_engine_new(ovm_store_t *store);
 void          ovm_engine_delete(ovm_engine_t *engine);
+bool          ovm_engine_memory_ensure_capacity(ovm_engine_t *engine, i64 minimum_size);
 void          ovm_engine_memory_copy(ovm_engine_t *engine, i64 target, void *data, i64 size);
 
 bool ovm_program_load_from_file(ovm_program_t *program, ovm_engine_t *engine, char *filename);
@@ -352,6 +353,9 @@ struct ovm_instr_t {
 
 #define OVMI_BREAK             0x4d
 
+#define OVMI_MEM_SIZE          0x4e   // %r = <size in bytes of memory>
+#define OVMI_MEM_GROW          0x4f   // %r = <grow memory, return new size in bytes>
+
 //
 // OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I32) == instruction for adding i32s
 //
index 347ee5a088921ad02c1173243608e6ff2a314480..c4b0f454de6e459240e0fd9366de94db2b437c77 100644 (file)
@@ -87,5 +87,7 @@ void               ovm_code_builder_add_atomic_store(ovm_code_builder_t *builder
 void               ovm_code_builder_add_cmpxchg(ovm_code_builder_t *builder, u32 ovm_type, i32 offset);
 void               ovm_code_builder_add_memory_copy(ovm_code_builder_t *builder);
 void               ovm_code_builder_add_memory_fill(ovm_code_builder_t *builder);
+void               ovm_code_builder_add_memory_size(ovm_code_builder_t *builder);
+void               ovm_code_builder_add_memory_grow(ovm_code_builder_t *builder);
 
 #endif
index 59da1e86fd6fbf962a13291c1fdd45894805346c..d77251521c15b4b25500f8d0e89670374b9e5c73 100644 (file)
@@ -564,6 +564,29 @@ void ovm_code_builder_add_cmpxchg(ovm_code_builder_t *builder, u32 ovm_type, i32
     PUSH_VALUE(builder, instrs[2].r);
 }
 
+void ovm_code_builder_add_memory_size(ovm_code_builder_t *builder) {
+    ovm_instr_t instr = {0};
+    instr.full_instr = OVM_TYPED_INSTR(OVMI_MEM_SIZE, OVM_TYPE_NONE);
+    instr.r = NEXT_VALUE(builder);
+
+    debug_info_builder_emit_location(builder->debug_builder);
+    ovm_program_add_instructions(builder->program, 1, &instr);
+
+    PUSH_VALUE(builder, instr.r);
+}
+
+void ovm_code_builder_add_memory_grow(ovm_code_builder_t *builder) {
+    ovm_instr_t instr = {0};
+    instr.full_instr = OVM_TYPED_INSTR(OVMI_MEM_GROW, OVM_TYPE_NONE);
+    instr.a = POP_VALUE(builder);
+    instr.r = NEXT_VALUE(builder);
+
+    debug_info_builder_emit_location(builder->debug_builder);
+    ovm_program_add_instructions(builder->program, 1, &instr);
+
+    PUSH_VALUE(builder, instr.r);
+}
+
 void ovm_code_builder_add_memory_copy(ovm_code_builder_t *builder) {
     ovm_instr_t instr = {0};
     instr.full_instr = OVM_TYPED_INSTR(OVMI_COPY, OVM_TYPE_NONE);
index 500e69d8d5b004cda22b0e261c1890cae7b8da59..ecf4f0955e13474fbd4c71b2c8d958b596baf015 100644 (file)
@@ -1,3 +1,5 @@
+#define _GNU_SOURCE
+
 #include "vm.h"
 
 #include <sys/mman.h>
@@ -141,11 +143,21 @@ ovm_engine_t *ovm_engine_new(ovm_store_t *store) {
     ovm_engine_t *engine = bh_alloc_item(store->heap_allocator, ovm_engine_t);
 
     engine->store = store;
-    engine->memory_size = 1ull << 32;
-    engine->memory = mmap(NULL, engine->memory_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    engine->memory_size = 0;
+    engine->memory = NULL;
+    engine->debug = NULL;
     pthread_mutex_init(&engine->atomic_mutex, NULL);
 
-    engine->debug = NULL;
+    //
+    // HACK: This should not be necessary, but because moving the memory around
+    // causes issues with other standard libraries (like STBTT and STBI), it is
+    // better to allocate a large amount here and avoid needing to reallocate.
+    // A solution should be possible, because other runtimes like Wasmer support
+    // this.
+    i64 attempt_to_allocate = 1ull << 32;
+    while (!ovm_engine_memory_ensure_capacity(engine, attempt_to_allocate)) {
+        attempt_to_allocate /= 2;
+    }
 
     return engine;
 }
@@ -153,10 +165,37 @@ ovm_engine_t *ovm_engine_new(ovm_store_t *store) {
 void ovm_engine_delete(ovm_engine_t *engine) {
     ovm_store_t *store = engine->store;
 
-    munmap(engine->memory, engine->memory_size);
+    if (engine->memory) {
+        munmap(engine->memory, engine->memory_size);
+    }
+    
     bh_free(store->heap_allocator, engine);
 }
 
+bool ovm_engine_memory_ensure_capacity(ovm_engine_t *engine, i64 minimum_size) {
+    if (engine->memory_size >= minimum_size) return true;
+
+    void *new_addr;
+
+    if (engine->memory == NULL) {
+        new_addr = mmap(NULL, minimum_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    } else {
+        new_addr = mremap(engine->memory, engine->memory_size, minimum_size, MREMAP_MAYMOVE);
+    }
+
+    if (new_addr == MAP_FAILED) {
+        // Something went wrong...
+        // At this point the program should probably horifically crash.
+
+        return false;
+    }
+
+    engine->memory_size = minimum_size;
+    engine->memory = new_addr;
+
+    return true;
+}
+
 void ovm_engine_memory_copy(ovm_engine_t *engine, i64 target, void *data, i64 size) {
     ovm_assert(engine);
     ovm_assert(engine->memory);
index 08dd165c44e506f87b4c5880db30be31f87d17a8..c1ecfdea4ac4c59bb7328caa6f9ed4a4ecbd2cec 100644 (file)
@@ -317,6 +317,7 @@ OVMI_INSTR_EXEC(return) {
     } else { \
         ovm_external_func_t external_func = state->external_funcs[func->external_func_idx]; \
         external_func.native_func(external_func.userdata, &state->param_buf[extra_params], &state->__tmp_value); \
+        memory = state->engine->memory; \
 \
         ovm__func_teardown_stack_frame(state); \
 \
@@ -461,6 +462,31 @@ CMPXCHG(OVM_TYPE_I64, i64)
 #undef CMPXCHG
 
 
+//
+// Memory
+//
+
+OVMI_INSTR_EXEC(mem_size) {
+    VAL(instr->r).u32 = (u32) state->engine->memory_size / 65536;
+    VAL(instr->r).type = OVM_TYPE_I32;
+    NEXT_OP;
+}
+
+OVMI_INSTR_EXEC(mem_grow) {
+    ovm_assert(VAL(instr->a).type == OVM_TYPE_I32);
+    VAL(instr->r).type = OVM_TYPE_I32;
+    VAL(instr->r).u32 = (u32) state->engine->memory_size / 65536;
+
+    if (!ovm_engine_memory_ensure_capacity(state->engine,
+            state->engine->memory_size + VAL(instr->a).u32 * 65536)) {
+        VAL(instr->r).i32 = -1;
+    }
+    
+    memory = state->engine->memory;
+    NEXT_OP;
+}
+
+
 OVMI_INSTR_EXEC(illegal) {
     OVMI_EXCEPTION_HOOK;
     return ((ovm_value_t) {0});
@@ -558,6 +584,8 @@ static ovmi_instr_exec_t OVMI_DISPATCH_NAME[] = {
     NULL, NULL, NULL, NULL, NULL, NULL, D(transmute_f64_i64), NULL,
     IROW_INT(cmpxchg)
     IROW_SAME(illegal)
+    IROW_UNTYPED(mem_size)
+    IROW_UNTYPED(mem_grow)
 };
 
 #undef D
index 1ffbb5f772f19698094a8ccb54e0d3837ede9b46..2b04f91b7635d320b166704abdb67fdd1a9057e2 100644 (file)
@@ -333,6 +333,10 @@ wasm_instance_t *wasm_instance_new(wasm_store_t *store, const wasm_module_t *mod
 
     prepare_instance(instance, imports);
 
+    assert(bh_arr_length(instance->memories) == 1);
+    u32 memory_size = (instance->memories[0]->inner.type->memory.limits.min) * MEMORY_PAGE_SIZE;
+    ovm_engine_memory_ensure_capacity(store->engine->engine, memory_size);
+
     if (trap) *trap = NULL;
 
     return instance;
index 38445793936cb1d26249ab4d3231375c0e4f415f..284cdc9b8e6478fdc3fe84019f5595dfaf1d96b2 100644 (file)
@@ -31,12 +31,5 @@ wasm_memory_pages_t wasm_memory_size(const wasm_memory_t *memory) {
 }
 
 bool wasm_memory_grow(wasm_memory_t *memory, wasm_memory_pages_t pages) {
-    //
-    // This will always fail, as initially the VM is created with
-    // a 4GiB mmap, so growing it will not be an option. If that
-    // changes and a dynamically allocated solution is used, then
-    // this can change. I don't see that changing however, as I will
-    // never need to use this on 32-bit systems, and that would be the
-    // only case that I would not like to try to mmap 4 gigs.
-    return false;
+    return ovm_engine_memory_ensure_capacity(memory->inner.memory.engine, pages * MEMORY_PAGE_SIZE);
 }
index ef9e9dd7f00bafa07a484ce72bf8f21810249a8b..6d927b8aed90b912de5a896b9b6bafc7120bdcf8 100644 (file)
@@ -712,18 +712,13 @@ static void parse_instruction(build_context *ctx) {
 
         case 0x3F: {
             assert(CONSUME_BYTE(ctx) == 0x00);
-
-            int memory_size = 65535;
-            ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &memory_size);
+            ovm_code_builder_add_memory_size(&ctx->builder);
             break;
         }
 
         case 0x40: {
             assert(CONSUME_BYTE(ctx) == 0x00);
-            ovm_code_builder_drop_value(&ctx->builder);
-
-            int value = -1;
-            ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &value);
+            ovm_code_builder_add_memory_grow(&ctx->builder);
             break;
         }