coder-mike / microvium

A compact, embeddable scripting engine for applications and microcontrollers for executing programs written in a subset of the JavaScript language.
MIT License
569 stars 25 forks source link

Shared microvium instances with private heaps #51

Closed davidchisnall closed 1 year ago

davidchisnall commented 1 year ago

We'd like to be able to have a single instance of the microvium code, but allow different compartments to use it with different heaps. This requires our malloc / free callbacks to have some way of identifying which mvm_VM they are being called on behalf of (and, ideally, getting some state from it). My ideal interface would make it possible to add a header to the mvm_VM structure and have the mvm_VM be passed to the alloc and free callbacks.

Somewhat related: it would be nice if the functions in microvium.h that are implemented in microvium.c had a MICROVIUM_EXPORT macro in front of them (with a default empty definition) that integrators can use to define custom calling conventions, visibility, and so on.

coder-mike commented 1 year ago

Hi David. Yes, that sounds like something useful. However, mvm_restore also performs a malloc but before the vm exists (because it's the malloc for the vm itself). What if the MVM_MALLOC macro instead accepted the context as an argument rather than the VM itself?

My ideal interface would make it possible to add a header to the mvm_VM structure

I'm not sure what you mean by "header" here. If you're talking about a field, then the context field may be what you're looking for. User code passes it in to mvm_restore, and can access it again using mvm_getContext. It can be anything you like that fits in a pointer.

Somewhat related: it would be nice if the functions in microvium.h that are implemented in microvium.c had a MICROVIUM_EXPORT macro in front of them (with a default empty definition) that integrators can use to define custom calling conventions, visibility, and so on.

Can you give me an example to make it clearer?

davidchisnall commented 1 year ago

I'm not sure what you mean by "header" here. If you're talking about a field, then the context field may be what you're looking for. User code passes it in to mvm_restore, and can access it again using mvm_getContext. It can be anything you like that fits in a pointer.

That would be ideal. If the allocate and deallocate functions were simply passed this context, then this would be very easy for us to support. I believe this could be implemented in a backwards-compatible way by doing something like:

#ifndef MVM_CONTEXT_MALLOC
#    define MVM_CONTEXT_MALLOC(ctx, size) MVM_MALLOC(size)
#endif

And then replacing uses of MVM_MALLOC with MVM_CONTEXT_MALLOC in the implementaiton.

That way, if someone implements MVM_MALLOC but not MVM_CONTEXT_MALLOC, it just calls that. For our use, this would let us put Microvium in a shared code region (read-only, no globals) but still have it allocate memory that can be accounted to each the compartment that instantiates a JavaScript heap (so we could very cheaply have multiple hardware-isolated JavaScript VMs on a tiny device).

Can you give me an example to make it clearer?

#ifndef MICROVIUM_EXPORT
#    define MICROVIUM_EXPORT
#endif
...
MICROVIUM_EXPORT mvm_TeError mvm_restore(mvm_VM** result, MVM_LONG_PTR_TYPE snapshotBytecode, size_t bytecodeSize, void* context, mvm_TfResolveImport resolveImport);

We would then #define this to an attribute that specifies the calling convention that we want. If embedders don't care about this, it doesn't affect them.

coder-mike commented 1 year ago

Ok, sure. Do you want to take a look at this PR and tell me if it looks right for you?

https://github.com/coder-mike/microvium/pull/52

In particular, I'm not sure if MVM_EXPORT needs to be on both the header declaration and the implementation.

davidchisnall commented 1 year ago

I've tested this and, with the following diff, am now able to run multiple JavaScript VMs, in different compartments, sharing the same code:

diff --git a/dist-c/microvium.h b/dist-c/microvium.h
index 29024d8..1985778 100644
--- a/dist-c/microvium.h
+++ b/dist-c/microvium.h
@@ -202,8 +202,8 @@ MVM_EXPORT void* mvm_getContext(mvm_VM* vm);
 MVM_EXPORT void mvm_initializeHandle(mvm_VM* vm, mvm_Handle* handle); // Handle must be released by mvm_releaseHandle
 MVM_EXPORT void mvm_cloneHandle(mvm_VM* vm, mvm_Handle* target, const mvm_Handle* source); // Target must be released by mvm_releaseHandle
 MVM_EXPORT mvm_TeError mvm_releaseHandle(mvm_VM* vm, mvm_Handle* handle);
-MVM_EXPORT static inline mvm_Value mvm_handleGet(const mvm_Handle* handle) { return handle->_value; }
-MVM_EXPORT static inline void mvm_handleSet(mvm_Handle* handle, mvm_Value value) { handle->_value = value; }
+static inline mvm_Value mvm_handleGet(const 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

I didn't notice when I looked at the PR that the export macro was added to static inline functions (which are not exported).

coder-mike commented 1 year ago

Yeah, sorry, good point. Thanks for the PR -- I've merged it.

davidchisnall commented 1 year ago

I think this can be closed now - everything seems to be working nicely for us. Thanks!