In the previous article Running JavaVM with WebAssembly, I tried to make a Java interpreter (JavaVM) using WebAssembly, but for later, a little ingenuity. Or, keep a memorandum for reference when expanding. By the way, the WebAssembly compiler assumes "Emscripten".
I have uploaded a set of C language source files of Java interpreter (Java VM) to the following GitHub. https://github.com/poruruba/javaemu
We are using the following linkages.
Calling C function from Javascript
Memory sharing between Javascript and C
It seems that you can also call Javascript functions from C language, but I have not used it this time.
First is the function definition on the C language side that is called. Add EMSCRIPTEN_KEEPALIVE to the function definition. Only this.
main.c
#include <emscripten/emscripten.h>
#ifdef __cplusplus
extern "C" {
#endif
int EMSCRIPTEN_KEEPALIVE setInoutBuffer(const unsigned char *buffer, long bufferSize );
int EMSCRIPTEN_KEEPALIVE setRomImage(const unsigned char *romImage, long romSize );
int EMSCRIPTEN_KEEPALIVE callStaticMain(char *className, char *param );
#ifdef __cplusplus
}
#endif
(However, since the expressiveness of the return value from C language to Javascript is poor, parameters are exchanged using the shared memory that will be explained later.)
The call from the Javascript side looks like the following. The utility "Module" defined in javaemu.js generated at compile time by emcc is useful.
main.js
var ret = Module.ccall('setRomImage', "number", ["number", "number"], [g_pointer, bin.length]);
The second argument indicates the return type of the C language function. The third argument shows the type of the list of arguments for the C language function. The fourth argument is the value you actually pass to the C language function argument.
The function called ccall is one of the functions specified at compile time.
s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'UTF8ArrayToString', 'stringToUTF8Array']"
I am using it as follows.
(Reference information) https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#access-memory-from-javascript
//For example, var bin= { array: [1, 2, 3, 4] };
g_pointer = Module._malloc(bin.array.length);
Module.HEAPU8.set(bin.array, g_pointer);
var ret = Module.ccall('setRomImage', "number", ["number", "number"], [g_pointer, bin.array.length]);
Allocate the memory you want to share by calling Module._malloc. This is fine for an array of unsigned chars, but for shorts and longs you need to specify the area size accordingly. By calling Module.HEAPU8.set, the value of the array that Javascript wants to set is set in the memory. Since it is an array of unsigned chars, a macro called HEAPU8 is used when accessing memory.
You can also set it as follows.
Module.HEAPU8[g_pointer + ptr] = 123;
On the C language side, in the setRomImage function, remember the address of the shared memory, and then refer to or set it.
main.c
int EMSCRIPTEN_KEEPALIVE setRomImage(const unsigned char *romImage, long romSize )
{
FUNC_CALL();
classRom_ext = (unsigned char*)romImage;
classRomSize_ext = romSize;
FUNC_RETURN();
return FT_ERR_OK;
}
The function setRomImage is used for sharing the class file in the Jar file, and the function setInoutBuffer is used for exchanging the arguments when calling the function callStaticMain and the return value from the C language side.
C language fopen is also available, but the browser operates in Sandbox, so you cannot access local files on your PC. Instead, you can take the compile-time folder structure and files as a snapshot as an image file (javaemu.data) and read it with fopen at run time.
(Reference information) https://emscripten.org/docs/porting/files/index.html
The following is an excerpt of that part.
alloc_class.c
#define PRECLASS_DIR "pre_classes"
char *baseClassDir = PRECLASS_DIR;
static long file_read(const char *p_fname, unsigned char **pp_bin, long *p_size)
{
FILE *fp;
long fsize;
char path[255];
strcpy(path, baseClassDir);
strcat(path, "/");
strcat(path, p_fname);
strcat(path, ".class");
fp = fopen(path, "rb");
if (fp == NULL)
return FT_ERR_NOTFOUND;
//・ ・ ・
pre_classes is that that was specified at compile time in emcc.
emcc *.c -s WASM=1 -o javaemu.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'UTF8ArrayToString', 'stringToUTF8Array']" --preload-file pre_classes
It is an implementation specification of the implemented Java interpreter, not WebAssembly.
Use the function callStaticMain.
main.c
int EMSCRIPTEN_KEEPALIVE callStaticMain(char *className, char *param )
In this, the Java VM, which is a Java interpreter, is initialized, and the Java class functions are called in the following parts.
main.c
ret = startStaticMain(className, param, &retType, &retVar);
In the argument, set the class name to call and the argument you want to pass. The function name and type on the Java side called by this are fixed.
public static void main( String[] args ){
Therefore, it is necessary to implement the above function name in the Java source of the target class. Implement the logic you want to process in this Java function. Argument exchange is
String[] input = System.getInput(-1);
Then, get the argument from Javascript side and
System.setOutput(new String[]{ "World", "Good evening" });
Returns the return value to the Javascript side.
You can do some things with Java implementation, but I still want to implement native processing using C language. To do this, define the following function in your Java source. The function name can be anything. In short, add the qualifier native static.
public native static final String[] getInput(int max);
Then, if you haven't implemented it natively yet, the interpreter will exit with an error similar to the following in the browser's developer console.
** Native Method Missing:
javaemu.js:2178 // base/framework/System_getInput_(I)[Ljava/lang/String;
javaemu.js:2178 { 320190814UL, func },
It means that there is no definition of a native function. Implement the native function in C language and set the pointer of the implemented C language function in the following part of waba_native.c.
waba_native.c
NativeMethod nativeMethods[] = {
//・ ・ ・ ・ ・ ・ ・
// base/framework/System_printStackTrace_()V
{ 320187986UL, FCSystem_printStackTrace },
// base/framework/System_getInput_(I)[Ljava/lang/String; //★ Addition
{ 320190814UL, FCSystem_getInput }, //★ Addition
// base/framework/System_sleep_(I)I
{ 320192265UL, FCSystem_sleep },
//・ ・ ・ ・ ・ ・ ・
};
NativeMethod nativeMethods [] has C language native function implementations. They are arranged in ascending order of hash values, so insert them in the appropriate places. (Otherwise the interpreter won't find it)
The implementation of a native function in C language looks like the following, for example.
waba_native.c
// base/framework/System_getInput_(I)[Ljava/lang/String;
long FCSystem_getInput(Var stack[]) {
Var v;
unsigned char num;
unsigned char i;
WObject strArray;
WObject *obj;
unsigned long ptr;
long max;
if (inoutBuff_ext == NULL)
return ERR_CondNotSatisfied;
max = stack[0].intValue;
num = inoutBuff_ext[0];
if (max >= 0 && num > max)
num = (unsigned char)max;
strArray = createArrayObject(TYPE_OBJECT, num);
obj = (WObject *)WOBJ_arrayStart(strArray);
if (pushObject(strArray) != FT_ERR_OK)
return ERR_OutOfObjectMem;
ptr = 1;
for (i = 0; i < num; i++) {
obj[i] = createString((const char*)&inoutBuff_ext[ptr]);
if (obj[i] == WOBJECT_NULL) {
popObject();
return ERR_OutOfObjectMem;
}
ptr += strlen((const char*)&inoutBuff_ext[ptr]) + 1;
}
popObject();
v.obj = strArray;
stack[0] = v;
return 0;
}
Please refer to other native functions. The input arguments are on the Var stack []. Set the response to stack [0]. (Because it is ←, it is not possible to create a native function without arguments) If there is no problem, return 0; is returned.
JavaVM has limited functionality. For example, bytecode using float, double and long is not supported. As you can see by looking at the function below, all the above bytecodes are commented out.
waba.c
long executeMethod(WClass *wclass, WClassMethod *method, Var params[], unsigned short numParams, unsigned char *retType, Var* retValue) {
//・ ・ ・
// NOTE: this is the full list of unsupported opcodes. Adding all
// these cases here does not cause the VM executable code to be any
// larger, it just makes sure that the compiler uses a jump table
// with no spaces in it to make sure performance is as good as we
// can get (tested under Codewarrior for PalmOS).
/*
case OP_lconst_0:
case OP_lconst_1:
case OP_dconst_0:
//・ ・ ・
that's all
Recommended Posts