I'm trying to make a simple Nim app which runs on Emscripten. I'm also using jsbind to call a JavaScript function, namely console.log.
I have the following file, test.nim:
import jsbind
type Window* = ref object of JSObj
type Console* = ref object of JSObj
proc getConsole*(): Console {.jsimportgWithName: "function(){return console;}".}
proc log*(c: Console, a: any) {.jsimport.}
echo("Before log")
getConsole().log("Hello")
echo("After log")
My nim.cfg is:
#if emscripten:
cc = clang
gc = none
clang.exe = "emcc"
clang.linkerexe = "emcc"
clang.options.linker = ""
cpu = "i386"
out = "index.html"
passC = "-Iemscripten"
passL = "-Lemscripten -s TOTAL_MEMORY=335544320"
#end
I then compile with:
nim c -d:emscripten --out=index.html test.nim
When I then open index.htmlin a browser and it gets to the getConsole().log call, I get numerous console errors about how a function UTF8ToString is not defined, and the program terminates:
This function appears to be an Emscripten prelude function - how do I ensure this and any other functions I need are included in my JavaScript output?
This error occurs both with the WebAssembly options enabled and without. I'm using Nim 0.16.0 and emcc 1.35.0.
Here is a JSFiddle of the two Emscripten output files. (They are too large for a Stack Snippet.)
Emscripten 1.35.0 is probably too old to have UTF8ToString function. Your sample works fine for me with Emscripten 1.37.1.
As a side note, I've noticed you're using fixed Emscripten heap size. In case you want dynamically growing heap you can use -s ALLOW_MEMORY_GROWTH=1 flag.
Related
We're trying to optimize for start-up time of JS code on mobile and looking for the opportunities. I've found Facebook Hermes JS engine created for a similar purpose but we heavily depend on V8 at the moment.
Can build-time precompilation be done with V8 meaning parsing and code optimization will be done in compile-time and saving some time in runtime? Generating LLVM bitcode from the source code and executing bitcode in runtime seems to be pretty close to what i imagine. WASM seems to be not an option (at least for mobile).
If it's possible, can one provide a simple example of trivial JS code optimized with V8?
PS. Probably it would also help with memory consumption which can be the secondary goal.
V8 supports heap snapshots for this very purpose. It's used by the Atom editor, for instance, to improve startup time. It's not so much about precompiling as it is about prebuilding your global environment and instantiating your functions (which may not be compiled [yet], just converted to bytecode for Ignition, which is sufficient). If you're using Electron, the mksnapshot npm package may be useful. (And if not, looking at how it works may still be useful.)
I haven't done any V8 hacking, but the example they link from the blog post above is as follows:
TEST(PerIsolateSnapshotBlobs) {
DisableTurbofan();
const char* source1 = "function f() { return 42; }";
const char* source2 =
"function f() { return g() * 2; }"
"function g() { return 43; }"
"/./.test('a')";
v8::StartupData data1 = v8::V8::CreateSnapshotDataBlob(source1);
v8::StartupData data2 = v8::V8::CreateSnapshotDataBlob(source2);
v8::Isolate::CreateParams params1;
params1.snapshot_blob = &data1;
params1.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate1 = v8::Isolate::New(params1);
{
v8::Isolate::Scope i_scope(isolate1);
v8::HandleScope h_scope(isolate1);
v8::Local<v8::Context> context = v8::Context::New(isolate1);
delete[] data1.data; // We can dispose of the snapshot blob now.
v8::Context::Scope c_scope(context);
CHECK_EQ(42, CompileRun("f()")->ToInt32(isolate1)->Int32Value());
CHECK(CompileRun("this.g")->IsUndefined());
}
isolate1->Dispose();
v8::Isolate::CreateParams params2;
params2.snapshot_blob = &data2;
params2.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate2 = v8::Isolate::New(params2);
{
v8::Isolate::Scope i_scope(isolate2);
v8::HandleScope h_scope(isolate2);
v8::Local<v8::Context> context = v8::Context::New(isolate2);
delete[] data2.data; // We can dispose of the snapshot blob now.
v8::Context::Scope c_scope(context);
CHECK_EQ(86, CompileRun("f()")->ToInt32(isolate2)->Int32Value());
CHECK_EQ(43, CompileRun("g()")->ToInt32(isolate2)->Int32Value());
}
isolate2->Dispose();
}
That blog post (and the associated example?) is from 2015 so things have probably moved on since.
I have a Node app that sends JavaScript source code as string to a worker thread that executes it in Node's VM api. I am taking a snapshot of the worker thread's heap only. This is to detect any string allocations in the JavaScript source code. However I get a lot of obscure comments as strings that bloat the heap.
I originally suspected this is due to how Node VM executes code as string so I commented the VM portion of my code, but I'm still getting these unwanted strings. Perhaps this is due to using require() and import?
My code is as follows. Again, app.js simply passes source code as string to my worker thread, worker.mjs. worker.mjs will run the passed string data inside VM sandbox and then write its heap snapshot to file.
// App.js file
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
// JavaScript source code passed as String.
let workerData = `
var nop = unescape("%u9090%u9090");
while (nop.length <= 0x100000/2) {nop += nop;}`;
const worker = new Worker('./worker.mjs', { workerData });
worker.once('message', (filename) => {
console.log(`worker heapdump: ${filename}`);
});
// Tell the worker to create a heapdump.
worker.postMessage('heapdump');
};
// worker.mjs
import { workerData, parentPort, threadId } from 'worker_threads';
import { createContext, runInContext } from 'vm';
import { writeHeapSnapshot, getHeapSnapshot } from 'v8';
parentPort.once('message', (message) => {
if (message === 'heapdump') {
const sandbox = {};
const strict = '"use strict";'
createContext(sandbox);
runInContext(strict+workerData, sandbox, {timeout: 10000 });
parentPort.postMessage(writeHeapSnapshot());
}
});
My ultimate goal is to collect all strings and concatenated strings created from only within the string source code workerData. In this example, the value of nop variable.
But as shown, there's so much fluff data in concatenated string as well.
"encodingOps.ucs2.byteLength"#29509
"encodingOps.utf16le.byteLength"#29531
"encodingOps.latin1.byteLength"#29555
"encodingOps.ascii.byteLength"#29579
"encodingOps.base64.byteLength"#29603
"encodingOps.hex.byteLength"#29627
"module.exports.getModuleFromWrap"#39059
...
...
"internal/modules/package_json_reader.js"#10997
"internal/modules/esm/translators.js"#11001
"internal/modules/esm/transform_source.js"#11011
"internal/modules/esm/resolve.js"#11021
"internal/modules/esm/module_map.js"#11025
"internal/modules/esm/module_job.js"#11029
"internal/modules/esm/loader.js"#11033
"internal/modules/esm/get_source.js"#11043
"internal/modules/esm/get_format.js"
The vm module enables compiling and running code within V8 Virtual Machine contexts. The vm module is not a security mechanism. Do not use it to run untrusted code. https://nodejs.org/api/vm.html
I understand that Node VM executes code in its own context. Would it be possible to retrieve the context id of the VM and then filter the heap snapshot for strings lived in that specific context? In this case, I only want nop variable. I'm hoping for some way to parse the snapshot JSON without using chrome dev-tools.
Perhaps this is due to using require() and import?
Essentially yes. You wanted all strings, you're getting all strings. JavaScript uses lots of strings. (The specific import mechanism doesn't matter. If you execute any code, you'll see its strings/etc in the heap snapshot.)
Would it be possible to retrieve the context id of the VM and then filter the heap snapshot for strings lived in that specific context?
No, there is no association between heap objects and contexts.
I've been looking a the WebAssembly website and tutorials and I feel a bit lost.
I have the following C code :
void EMSCRIPTEN_KEEPALIVE hello(char * value){
printf("%s\n", value);
}
I compiled it with (I'm also not sure this part is the best way to go) :
emcc demo.c -s WASM=1 -s NO_EXIT_RUNTIME=1 -o demo.js
From what I understand I can now use the demo.js glue code in my javascript class and call the method that way :
...
<script src="demo.js"></script>
<script>
function hello(){
// Get the value
var value = document.getElementById("sample");
_hello(value.innerHTML);
}
</script>
...
What I see being printed in the console when I call the method is :
(null)
Is there something I'm missing to pass a string value to C code compiled with WebAssembly ?
Thanks a lot
I actually found an answer to my question. I simply had to use the functions that Emscripten builds automatically within the 'Glue' code that's also generated when you build your C++ code to WASM.
So basically, to pass a String to C++ code compiled to WebAssembly with Emscripten you simply do it like this :
// Create a pointer using the 'Glue' method and the String value
var ptr = allocate(intArrayFromString(myStrValue), 'i8', ALLOC_NORMAL);
// Call the method passing the pointer
val retPtr = _hello(ptr);
// Retransform back your pointer to string using 'Glue' method
var resValue = Pointer_stringify(retPtr);
// Free the memory allocated by 'allocate'
_free(ptr);
More complete information on Emscripten's page.
Maybe it is stupid question (I am newbie to C++, just wanted to use it as library for android), but I am not able to run evaluation of some JS multiple times.
I have started with "hello world" tutorial. But then I have wanted simple thing, re-run main (just wrap content of tutorial code into function and run it twice in newly empty main.
This is what I got:
#
# Fatal error in ../src/isolate.cc, line 1868
# Check failed: thread_data_table_.
#
==== C stack trace ===============================
1: 0xa890b9
2: 0x6a22fc
3: 0x42694f
4: 0x405f66
5: 0x405ec7
6: __libc_start_main
7: 0x405dc9
Illegal instruction (core dumped)
This cames after creating new isolate
Isolate* isolate = Isolate::New(create_params);
Well, what I should do? Am I using wrong construct or so? Should I close/delete/clear something more?
In bigger view I just want to do evaluate function, that can be triggered multiple times, and beside that also run multiple js snipets in same context (how to split this function?).
Any idea?
UPDATE:
Ok, lets say that the main can be split into three logical parts:
init
int main(int argc, char* argv[]) {
// Initialize V8.
V8::InitializeICU();
V8::InitializeExternalStartupData(argv[0]);
Platform* platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
V8::Initialize();
// Create a new Isolate and make it the current one.
ArrayBufferAllocator allocator;
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = &allocator;
evaluation
Isolate* isolate = Isolate::New(create_params);
{
Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
HandleScope handle_scope(isolate);
// Create a new context.
Local<Context> context = Context::New(isolate);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(isolate, "'Hello' + ', World!'",
NewStringType::kNormal).ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
Local<Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(result);
printf("%s\n", *utf8);
}
isolate->Dispose();
and clean
// Dispose and tear down V8.
V8::Dispose();
V8::ShutdownPlatform();
delete platform;
return 0;
Now as I said before if I run main consists of init->evaluation->clean twice, that mean init->evaluation->clean->init->evaluation->clean, then the error occurs. I have figured out, that if I extract evaluation part into separate function I can run it multiple times e.g. as init->(evaluation){2}->clean
Is that how should it work? Next step is to divide this main into tree separate function that mean I have to have static member with platform? Could it cause leak somehow?
NOTE: that I want to run it from android, that mean e.g. click in UI, propagate js source to C via JNI and then call c++ V8, which is already initialized or not. hm?
Prefered way is to have "blackbox", but if I have to hold platform, so be it. It maybe could be also faster without re-initialization of V8, right?
UPDATE 2:
Well, still have problems with splitting evaluation part to achieve multiple runs in same isolate/context.
I have splitted it after creating context with stored isolate and context, but with no luck. When in second part try to create source string it fails, probably because of using stored isolate (something with isolate scope I guess).
:(
My assumption as I introduced in UPDATE1 was correct. That part works well.
According to UPDATE2 I have splitted evaluation part into two.
First for initialize isolate and context:
mIsolate = Isolate::New(mCreate_params);
Isolate::Scope isolate_scope(mIsolate);
{
// Create a stack-allocated handle scope.
HandleScope handle_scope(mIsolate);
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(mIsolate);
// Bind the global 'print' function to the C++ Print callback.
global->Set(v8::String::NewFromUtf8(mIsolate, "print"), v8::FunctionTemplate::New(mIsolate, Print));
// Create a new context.
mContext = Context::New(mIsolate, NULL, global);
Persistent<Context, CopyablePersistentTraits<Context>> persistent(mIsolate, mContext);
mContext_persistent = persistent;
}
and second that will run js in same context:
Isolate::Scope isolate_scope(mIsolate);
{
HandleScope handle_scope(mIsolate);
mContext = Local<Context>::New(mIsolate, mContext_persistent);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(mContext);
{
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(mIsolate, js_source, NewStringType::kNormal).ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(mContext, source).ToLocalChecked();
TryCatch trycatch(mIsolate);
// Run the script to get the result.
v8::Local<v8::Value> result;
if(!script->Run(mContext).ToLocal(&result)){
v8::String::Utf8Value exception_str(trycatch.Exception());
dprint(*exception_str);
}else{
if(!result->IsUndefined()){
String::Utf8Value utf8(result);
dprint(*utf8);
}
}
}
}
Well the code works very well on linux, but I still have some issues when I try to run first part for the second time (create new context) on android:
A/art: art/runtime/thread.cc:986] pthread_getschedparam failed for DumpState: No such process
A/art: art/runtime/base/mutex.cc:485] Unexpected state_ 0 in unlock for logging lock
But that's another question I guess. Peace.
Did you initialize v8 more than once?
v8::V8::Initialize() this method should be called once per process.
deep into project source file "v8/src/v8.cc", you will find the prove
bool V8::Initialize() {
InitializeOncePerProcess();
return true;
}
I noticed that if I execute a JavaScript script using the mongo command, the script can treat a cursor object as if it was an array.
var conn = new Mongo('localhost:27017');
var db = conn.getDB('learn');
db.test.remove({});
db.test.insert({foo: 'bar'});
var cur = db.test.find();
print(cur[0].foo); //prints: bar
print(cur[1]); // prints: undefined
This seems like it should be beyond the capabilities of the JavaScript language, since there is no way to "overload the subscript operator". So how does this actually work?
As documentation says, it is special ability of driver. It automagicly converts cursor[0] to cursor.toArray()[0]. You can prove it by overriding toArray() with print function or new Error().stack to get callstack back. Here it is:
at DBQuery.a.toArray ((shell):1:32)
at DBQuery.arrayAccess (src/mongo/shell/query.js:290:17)
at (shell):1:2
As you can see, indexing calls arrayAccess. How? Here we have a dbQueryIndexAccess function, which calls arrayAccess.
v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get(
v8::String::New("arrayAccess"));
...
v8::Handle<v8::Function> f = arrayAccess.As<v8::Function>();
...
return f->Call(info.This(), 1, argv);
And here we have a code, which sets indexed property handler to this function. WOW, v8 API gives us ability to add this handler!
DBQueryFT()->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess);
... and injects it into JS cursor class, which is defined originaly in JS.
injectV8Function("DBQuery", DBQueryFT(), _global);
Tl;dr: It is hacked in C++ source code of mongo shell.