From: binji Date: Mon, 22 Jun 2015 17:12:26 +0000 (-0700) Subject: Add d8 API for spawning function on a new thread (Third try) X-Git-Tag: upstream/4.7.83~1845 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=001ee86e323a722fd4c0761d0b1a2c3a0c8f8e4b;p=platform%2Fupstream%2Fv8.git Add d8 API for spawning function on a new thread (Third try) This API closely matches the Worker API. The differences: 1) The argument to the Worker constructor is a function to run, not a script. 2) Receiving a message from a worker is a synchronous API (as there is no event loop). The serialization done here is not robust as the real DOM implementation. For example, recursive data structures or otherwise duplicated objects are not allowed. BUG=chromium:497295 LOG=n Review URL: https://codereview.chromium.org/1192923002 Cr-Commit-Position: refs/heads/master@{#29195} --- diff --git a/src/d8.cc b/src/d8.cc index c46df53..3f0b6c9 100644 --- a/src/d8.cc +++ b/src/d8.cc @@ -46,6 +46,7 @@ #include "src/d8-debug.h" #include "src/debug.h" #include "src/snapshot/natives.h" +#include "src/utils.h" #include "src/v8.h" #endif // !V8_SHARED @@ -104,6 +105,19 @@ class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator { v8::Platform* g_platform = NULL; + +#ifndef V8_SHARED +bool FindInObjectList(Handle object, const Shell::ObjectList& list) { + for (int i = 0; i < list.length(); ++i) { + if (list[i]->StrictEquals(object)) { + return true; + } + } + return false; +} +#endif // !V8_SHARED + + } // namespace @@ -191,9 +205,12 @@ base::Mutex Shell::context_mutex_; const base::TimeTicks Shell::kInitialTicks = base::TimeTicks::HighResolutionNow(); Persistent Shell::utility_context_; +i::List Shell::workers_; +i::List Shell::externalized_shared_contents_; #endif // !V8_SHARED Persistent Shell::evaluation_context_; +ArrayBuffer::Allocator* Shell::array_buffer_allocator; ShellOptions Shell::options; const char* Shell::kPrompt = "d8> "; @@ -226,9 +243,8 @@ ScriptCompiler::CachedData* CompileForCachedData( name_buffer = new uint16_t[name_length]; name_string->Write(name_buffer, 0, name_length); } - ShellArrayBufferAllocator allocator; Isolate::CreateParams create_params; - create_params.array_buffer_allocator = &allocator; + create_params.array_buffer_allocator = Shell::array_buffer_allocator; Isolate* temp_isolate = Isolate::New(create_params); ScriptCompiler::CachedData* result = NULL; { @@ -661,6 +677,121 @@ void Shell::Load(const v8::FunctionCallbackInfo& args) { } +#ifndef V8_SHARED +void Shell::WorkerNew(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + if (args.Length() < 1 || !args[0]->IsFunction()) { + Throw(args.GetIsolate(), "1st argument must be function"); + return; + } + + Worker* worker = new Worker; + args.This()->SetInternalField(0, External::New(isolate, worker)); + workers_.Add(worker); + + String::Utf8Value function_string(args[0]->ToString()); + worker->StartExecuteInThread(isolate, *function_string); +} + + +void Shell::WorkerPostMessage(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + Local context = isolate->GetCurrentContext(); + + if (args.Length() < 1) { + Throw(isolate, "Invalid argument"); + return; + } + + Local this_value = args.This()->GetInternalField(0); + if (!this_value->IsExternal()) { + Throw(isolate, "this is not a Worker"); + return; + } + + Worker* worker = + static_cast(Local::Cast(this_value)->Value()); + + Handle message = args[0]; + ObjectList to_transfer; + if (args.Length() >= 2) { + if (!args[1]->IsArray()) { + Throw(isolate, "Transfer list must be an Array"); + return; + } + + Handle transfer = Handle::Cast(args[1]); + uint32_t length = transfer->Length(); + for (uint32_t i = 0; i < length; ++i) { + Handle element; + if (transfer->Get(context, i).ToLocal(&element)) { + if (!element->IsArrayBuffer() && !element->IsSharedArrayBuffer()) { + Throw(isolate, + "Transfer array elements must be an ArrayBuffer or " + "SharedArrayBuffer."); + break; + } + + to_transfer.Add(Handle::Cast(element)); + } + } + } + + ObjectList seen_objects; + SerializationData* data = new SerializationData; + if (SerializeValue(isolate, message, to_transfer, &seen_objects, data)) { + worker->PostMessage(data); + } else { + delete data; + } +} + + +void Shell::WorkerGetMessage(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + + Local this_value = args.This()->GetInternalField(0); + if (!this_value->IsExternal()) { + Throw(isolate, "this is not a Worker"); + return; + } + + Worker* worker = + static_cast(Local::Cast(this_value)->Value()); + + SerializationData* data = worker->GetMessage(); + if (data) { + int offset = 0; + Local data_value; + if (Shell::DeserializeValue(isolate, *data, &offset).ToLocal(&data_value)) { + args.GetReturnValue().Set(data_value); + } + delete data; + } +} + + +void Shell::WorkerTerminate(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + Local this_value = args.This()->GetInternalField(0); + if (!this_value->IsExternal()) { + Throw(isolate, "this is not a Worker"); + return; + } + + Worker* worker = + static_cast(Local::Cast(this_value)->Value()); + worker->Terminate(); + workers_.RemoveElement(worker); + delete worker; +} +#endif // !V8_SHARED + + void Shell::Quit(const v8::FunctionCallbackInfo& args) { int exit_code = args[0]->Int32Value(); OnExit(args.GetIsolate()); @@ -992,6 +1123,21 @@ Handle Shell::CreateGlobalTemplate(Isolate* isolate) { FunctionTemplate::New(isolate, PerformanceNow)); global_template->Set(String::NewFromUtf8(isolate, "performance"), performance_template); + + Handle worker_fun_template = + FunctionTemplate::New(isolate, WorkerNew); + worker_fun_template->PrototypeTemplate()->Set( + String::NewFromUtf8(isolate, "terminate"), + FunctionTemplate::New(isolate, WorkerTerminate)); + worker_fun_template->PrototypeTemplate()->Set( + String::NewFromUtf8(isolate, "postMessage"), + FunctionTemplate::New(isolate, WorkerPostMessage)); + worker_fun_template->PrototypeTemplate()->Set( + String::NewFromUtf8(isolate, "getMessage"), + FunctionTemplate::New(isolate, WorkerGetMessage)); + worker_fun_template->InstanceTemplate()->SetInternalFieldCount(1); + global_template->Set(String::NewFromUtf8(isolate, "Worker"), + worker_fun_template); #endif // !V8_SHARED Handle os_templ = ObjectTemplate::New(isolate); @@ -1325,9 +1471,8 @@ base::Thread::Options SourceGroup::GetThreadOptions() { void SourceGroup::ExecuteInThread() { - ShellArrayBufferAllocator allocator; Isolate::CreateParams create_params; - create_params.array_buffer_allocator = &allocator; + create_params.array_buffer_allocator = Shell::array_buffer_allocator; Isolate* isolate = Isolate::New(create_params); do { next_semaphore_.Wait(); @@ -1369,6 +1514,253 @@ void SourceGroup::WaitForThread() { done_semaphore_.Wait(); } } + + +SerializationData::~SerializationData() { + // Any ArrayBuffer::Contents are owned by this SerializationData object. + // SharedArrayBuffer::Contents may be used by other threads, so must be + // cleaned up by the main thread in Shell::CleanupWorkers(). + for (int i = 0; i < array_buffer_contents.length(); ++i) { + ArrayBuffer::Contents& contents = array_buffer_contents[i]; + Shell::array_buffer_allocator->Free(contents.Data(), contents.ByteLength()); + } +} + + +void SerializationData::WriteTag(SerializationTag tag) { data.Add(tag); } + + +void SerializationData::WriteMemory(const void* p, int length) { + i::Vector block = data.AddBlock(0, length); + memcpy(&block[0], p, length); +} + + +void SerializationData::WriteArrayBufferContents( + const ArrayBuffer::Contents& contents) { + array_buffer_contents.Add(contents); + WriteTag(kSerializationTagTransferredArrayBuffer); + int index = array_buffer_contents.length() - 1; + Write(index); +} + + +void SerializationData::WriteSharedArrayBufferContents( + const SharedArrayBuffer::Contents& contents) { + shared_array_buffer_contents.Add(contents); + WriteTag(kSerializationTagTransferredSharedArrayBuffer); + int index = shared_array_buffer_contents.length() - 1; + Write(index); +} + + +SerializationTag SerializationData::ReadTag(int* offset) const { + return static_cast(Read(offset)); +} + + +void SerializationData::ReadMemory(void* p, int length, int* offset) const { + memcpy(p, &data[*offset], length); + (*offset) += length; +} + + +void SerializationData::ReadArrayBufferContents(ArrayBuffer::Contents* contents, + int* offset) const { + int index = Read(offset); + DCHECK(index < array_buffer_contents.length()); + *contents = array_buffer_contents[index]; +} + + +void SerializationData::ReadSharedArrayBufferContents( + SharedArrayBuffer::Contents* contents, int* offset) const { + int index = Read(offset); + DCHECK(index < shared_array_buffer_contents.length()); + *contents = shared_array_buffer_contents[index]; +} + + +void SerializationDataQueue::Enqueue(SerializationData* data) { + base::LockGuard lock_guard(&mutex_); + data_.Add(data); +} + + +bool SerializationDataQueue::Dequeue(SerializationData** data) { + base::LockGuard lock_guard(&mutex_); + if (data_.is_empty()) return false; + *data = data_.Remove(0); + return true; +} + + +bool SerializationDataQueue::IsEmpty() { + base::LockGuard lock_guard(&mutex_); + return data_.is_empty(); +} + + +void SerializationDataQueue::Clear() { + base::LockGuard lock_guard(&mutex_); + for (int i = 0; i < data_.length(); ++i) { + delete data_[i]; + } + data_.Clear(); +} + + +Worker::Worker() + : in_semaphore_(0), out_semaphore_(0), thread_(NULL), script_(NULL) {} + + +Worker::~Worker() { Cleanup(); } + + +void Worker::StartExecuteInThread(Isolate* isolate, + const char* function_string) { + if (thread_) { + Throw(isolate, "Only one worker allowed"); + return; + } + + static const char format[] = "(%s).call(this);"; + size_t len = strlen(function_string) + sizeof(format); + + script_ = new char[len + 1]; + i::Vector vec(script_, static_cast(len + 1)); + i::SNPrintF(vec, format, function_string); + + thread_ = new WorkerThread(this); + thread_->Start(); +} + + +void Worker::PostMessage(SerializationData* data) { + in_queue_.Enqueue(data); + in_semaphore_.Signal(); +} + + +SerializationData* Worker::GetMessage() { + SerializationData* data; + while (!out_queue_.Dequeue(&data)) { + out_semaphore_.Wait(); + } + + return data; +} + + +void Worker::Terminate() { + if (thread_ == NULL) return; + PostMessage(NULL); + thread_->Join(); + Cleanup(); +} + + +void Worker::ExecuteInThread() { + Isolate::CreateParams create_params; + create_params.array_buffer_allocator = Shell::array_buffer_allocator; + Isolate* isolate = Isolate::New(create_params); + { + Isolate::Scope iscope(isolate); + { + HandleScope scope(isolate); + PerIsolateData data(isolate); + Local context = Shell::CreateEvaluationContext(isolate); + { + Context::Scope cscope(context); + PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate)); + + Handle global = context->Global(); + Handle this_value = External::New(isolate, this); + Handle postmessage_fun_template = + FunctionTemplate::New(isolate, PostMessageOut, this_value); + + Handle postmessage_fun; + if (postmessage_fun_template->GetFunction(context) + .ToLocal(&postmessage_fun)) { + global->Set(String::NewFromUtf8(isolate, "postMessage"), + postmessage_fun); + } + + // First run the script + Handle file_name = String::NewFromUtf8(isolate, "unnamed"); + Handle source = String::NewFromUtf8(isolate, script_); + Shell::ExecuteString(isolate, source, file_name, false, true); + + // Get the message handler + Handle onmessage = + global->Get(String::NewFromUtf8(isolate, "onmessage")); + if (onmessage->IsFunction()) { + Handle onmessage_fun = Handle::Cast(onmessage); + // Now wait for messages + bool done = false; + while (!done) { + in_semaphore_.Wait(); + SerializationData* data; + if (!in_queue_.Dequeue(&data)) continue; + if (data == NULL) { + done = true; + break; + } + int offset = 0; + Local data_value; + if (Shell::DeserializeValue(isolate, *data, &offset) + .ToLocal(&data_value)) { + Handle argv[] = {data_value}; + (void)onmessage_fun->Call(context, global, 1, argv); + } + delete data; + } + } + } + } + } + Shell::CollectGarbage(isolate); + isolate->Dispose(); +} + + +void Worker::Cleanup() { + delete thread_; + thread_ = NULL; + delete[] script_; + script_ = NULL; + in_queue_.Clear(); + out_queue_.Clear(); +} + + +void Worker::PostMessageOut(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + + if (args.Length() < 1) { + Throw(isolate, "Invalid argument"); + return; + } + + Handle message = args[0]; + + // TODO(binji): Allow transferring from worker to main thread? + Shell::ObjectList to_transfer; + + Shell::ObjectList seen_objects; + SerializationData* data = new SerializationData; + if (Shell::SerializeValue(isolate, message, to_transfer, &seen_objects, + data)) { + DCHECK(args.Data()->IsExternal()); + Handle this_value = Handle::Cast(args.Data()); + Worker* worker = static_cast(this_value->Value()); + worker->out_queue_.Enqueue(data); + worker->out_semaphore_.Signal(); + } else { + delete data; + } +} #endif // !V8_SHARED @@ -1543,6 +1935,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) { for (int i = 1; i < options.num_isolates; ++i) { options.isolate_sources[i].WaitForThread(); } + CleanupWorkers(); #endif // !V8_SHARED return 0; } @@ -1565,6 +1958,248 @@ void Shell::CollectGarbage(Isolate* isolate) { #ifndef V8_SHARED +bool Shell::SerializeValue(Isolate* isolate, Handle value, + const ObjectList& to_transfer, + ObjectList* seen_objects, + SerializationData* out_data) { + DCHECK(out_data); + HandleScope scope(isolate); + Local context = isolate->GetCurrentContext(); + + if (value->IsUndefined()) { + out_data->WriteTag(kSerializationTagUndefined); + } else if (value->IsNull()) { + out_data->WriteTag(kSerializationTagNull); + } else if (value->IsTrue()) { + out_data->WriteTag(kSerializationTagTrue); + } else if (value->IsFalse()) { + out_data->WriteTag(kSerializationTagFalse); + } else if (value->IsNumber()) { + Handle num = Handle::Cast(value); + double value = num->Value(); + out_data->WriteTag(kSerializationTagNumber); + out_data->Write(value); + } else if (value->IsString()) { + v8::String::Utf8Value str(value); + out_data->WriteTag(kSerializationTagString); + out_data->Write(str.length()); + out_data->WriteMemory(*str, str.length()); + } else if (value->IsArray()) { + Handle array = Handle::Cast(value); + if (FindInObjectList(array, *seen_objects)) { + Throw(isolate, "Duplicated arrays not supported"); + return false; + } + seen_objects->Add(array); + out_data->WriteTag(kSerializationTagArray); + uint32_t length = array->Length(); + out_data->Write(length); + for (uint32_t i = 0; i < length; ++i) { + Local element_value; + if (array->Get(context, i).ToLocal(&element_value)) { + if (!SerializeValue(isolate, element_value, to_transfer, seen_objects, + out_data)) + return false; + } + } + } else if (value->IsArrayBuffer()) { + Handle array_buffer = Handle::Cast(value); + if (FindInObjectList(array_buffer, *seen_objects)) { + Throw(isolate, "Duplicated array buffers not supported"); + return false; + } + seen_objects->Add(array_buffer); + if (FindInObjectList(array_buffer, to_transfer)) { + // Transfer ArrayBuffer + if (!array_buffer->IsNeuterable()) { + Throw(isolate, "Attempting to transfer an un-neuterable ArrayBuffer"); + return false; + } + + ArrayBuffer::Contents contents = array_buffer->Externalize(); + array_buffer->Neuter(); + out_data->WriteArrayBufferContents(contents); + } else { + ArrayBuffer::Contents contents = array_buffer->GetContents(); + // Clone ArrayBuffer + if (contents.ByteLength() > i::kMaxUInt32) { + Throw(isolate, "ArrayBuffer is too big to clone"); + return false; + } + + int byte_length = static_cast(contents.ByteLength()); + out_data->WriteTag(kSerializationTagArrayBuffer); + out_data->Write(byte_length); + out_data->WriteMemory(contents.Data(), + static_cast(contents.ByteLength())); + } + } else if (value->IsSharedArrayBuffer()) { + Handle sab = Handle::Cast(value); + if (FindInObjectList(sab, *seen_objects)) { + Throw(isolate, "Duplicated shared array buffers not supported"); + return false; + } + seen_objects->Add(sab); + if (!FindInObjectList(sab, to_transfer)) { + Throw(isolate, "SharedArrayBuffer must be transferred"); + return false; + } + + SharedArrayBuffer::Contents contents = sab->Externalize(); + out_data->WriteSharedArrayBufferContents(contents); + externalized_shared_contents_.Add(contents); + } else if (value->IsObject()) { + Handle object = Handle::Cast(value); + if (FindInObjectList(object, *seen_objects)) { + Throw(isolate, "Duplicated objects not supported"); + return false; + } + seen_objects->Add(object); + Local property_names; + if (!object->GetOwnPropertyNames(context).ToLocal(&property_names)) { + Throw(isolate, "Unable to get property names"); + return false; + } + + uint32_t length = property_names->Length(); + out_data->WriteTag(kSerializationTagObject); + out_data->Write(length); + for (uint32_t i = 0; i < length; ++i) { + Handle name; + Handle property_value; + if (property_names->Get(context, i).ToLocal(&name) && + object->Get(context, name).ToLocal(&property_value)) { + if (!SerializeValue(isolate, name, to_transfer, seen_objects, out_data)) + return false; + if (!SerializeValue(isolate, property_value, to_transfer, seen_objects, + out_data)) + return false; + } + } + } else { + Throw(isolate, "Don't know how to serialize object"); + return false; + } + + return true; +} + + +MaybeLocal Shell::DeserializeValue(Isolate* isolate, + const SerializationData& data, + int* offset) { + DCHECK(offset); + EscapableHandleScope scope(isolate); + // This function should not use utility_context_ because it is running on a + // different thread. + Local result; + SerializationTag tag = data.ReadTag(offset); + + switch (tag) { + case kSerializationTagUndefined: + result = Undefined(isolate); + break; + case kSerializationTagNull: + result = Null(isolate); + break; + case kSerializationTagTrue: + result = True(isolate); + break; + case kSerializationTagFalse: + result = False(isolate); + break; + case kSerializationTagNumber: + result = Number::New(isolate, data.Read(offset)); + break; + case kSerializationTagString: { + int length = data.Read(offset); + static char s_buffer[128]; + char* p = s_buffer; + bool allocated = false; + if (length > static_cast(sizeof(s_buffer))) { + p = new char[length]; + allocated = true; + } + data.ReadMemory(p, length, offset); + MaybeLocal str = + String::NewFromUtf8(isolate, p, String::kNormalString, length); + if (!str.IsEmpty()) result = str.ToLocalChecked(); + if (allocated) delete[] p; + break; + } + case kSerializationTagArray: { + uint32_t length = data.Read(offset); + Handle array = Array::New(isolate, length); + for (uint32_t i = 0; i < length; ++i) { + Local element_value; + CHECK(DeserializeValue(isolate, data, offset).ToLocal(&element_value)); + array->Set(i, element_value); + } + result = array; + break; + } + case kSerializationTagObject: { + int length = data.Read(offset); + Handle object = Object::New(isolate); + for (int i = 0; i < length; ++i) { + Local property_name; + CHECK(DeserializeValue(isolate, data, offset).ToLocal(&property_name)); + DCHECK(property_name->IsString()); + Local property_value; + CHECK(DeserializeValue(isolate, data, offset).ToLocal(&property_value)); + object->Set(property_name, property_value); + } + result = object; + break; + } + case kSerializationTagArrayBuffer: { + int byte_length = data.Read(offset); + Handle array_buffer = ArrayBuffer::New(isolate, byte_length); + ArrayBuffer::Contents contents = array_buffer->GetContents(); + DCHECK(static_cast(byte_length) == contents.ByteLength()); + data.ReadMemory(contents.Data(), byte_length, offset); + result = array_buffer; + break; + } + case kSerializationTagTransferredArrayBuffer: { + ArrayBuffer::Contents contents; + data.ReadArrayBufferContents(&contents, offset); + result = + ArrayBuffer::New(isolate, contents.Data(), contents.ByteLength()); + break; + } + case kSerializationTagTransferredSharedArrayBuffer: { + SharedArrayBuffer::Contents contents; + data.ReadSharedArrayBufferContents(&contents, offset); + result = SharedArrayBuffer::New(isolate, contents.Data(), + contents.ByteLength()); + break; + } + default: + UNREACHABLE(); + } + + return scope.Escape(result); +} + + +void Shell::CleanupWorkers() { + for (int i = 0; i < workers_.length(); ++i) { + Worker* worker = workers_[i]; + worker->Terminate(); + delete worker; + } + workers_.Clear(); + + for (int i = 0; i < externalized_shared_contents_.length(); ++i) { + const SharedArrayBuffer::Contents& contents = + externalized_shared_contents_[i]; + Shell::array_buffer_allocator->Free(contents.Data(), contents.ByteLength()); + } + externalized_shared_contents_.Clear(); +} + + static void DumpHeapConstants(i::Isolate* isolate) { i::Heap* heap = isolate->heap(); @@ -1651,13 +2286,14 @@ int Shell::Main(int argc, char* argv[]) { SetFlagsFromString("--redirect-code-traces-to=code.asm"); int result = 0; Isolate::CreateParams create_params; - ShellArrayBufferAllocator array_buffer_allocator; + ShellArrayBufferAllocator shell_array_buffer_allocator; MockArrayBufferAllocator mock_arraybuffer_allocator; if (options.mock_arraybuffer_allocator) { - create_params.array_buffer_allocator = &mock_arraybuffer_allocator; + Shell::array_buffer_allocator = &mock_arraybuffer_allocator; } else { - create_params.array_buffer_allocator = &array_buffer_allocator; + Shell::array_buffer_allocator = &shell_array_buffer_allocator; } + create_params.array_buffer_allocator = Shell::array_buffer_allocator; #if !defined(V8_SHARED) && defined(ENABLE_GDB_JIT_INTERFACE) if (i::FLAG_gdbjit) { create_params.code_event_handler = i::GDBJITInterface::EventHandler; diff --git a/src/d8.h b/src/d8.h index 3b06059..23b66e4 100644 --- a/src/d8.h +++ b/src/d8.h @@ -8,6 +8,7 @@ #ifndef V8_SHARED #include "src/allocation.h" #include "src/hashmap.h" +#include "src/list.h" #include "src/smart-pointers.h" #include "src/v8.h" #else @@ -167,6 +168,108 @@ class SourceGroup { int end_offset_; }; +#ifndef V8_SHARED +enum SerializationTag { + kSerializationTagUndefined, + kSerializationTagNull, + kSerializationTagTrue, + kSerializationTagFalse, + kSerializationTagNumber, + kSerializationTagString, + kSerializationTagArray, + kSerializationTagObject, + kSerializationTagArrayBuffer, + kSerializationTagTransferredArrayBuffer, + kSerializationTagTransferredSharedArrayBuffer, +}; + + +class SerializationData { + public: + SerializationData() {} + ~SerializationData(); + + void WriteTag(SerializationTag tag); + void WriteMemory(const void* p, int length); + void WriteArrayBufferContents(const ArrayBuffer::Contents& contents); + void WriteSharedArrayBufferContents( + const SharedArrayBuffer::Contents& contents); + + template + void Write(const T& data) { + WriteMemory(&data, sizeof(data)); + } + + SerializationTag ReadTag(int* offset) const; + void ReadMemory(void* p, int length, int* offset) const; + void ReadArrayBufferContents(ArrayBuffer::Contents* contents, + int* offset) const; + void ReadSharedArrayBufferContents(SharedArrayBuffer::Contents* contents, + int* offset) const; + + template + T Read(int* offset) const { + T value; + ReadMemory(&value, sizeof(value), offset); + return value; + } + + private: + i::List data; + i::List array_buffer_contents; + i::List shared_array_buffer_contents; +}; + + +class SerializationDataQueue { + public: + void Enqueue(SerializationData* data); + bool Dequeue(SerializationData** data); + bool IsEmpty(); + void Clear(); + + private: + base::Mutex mutex_; + i::List data_; +}; + + +class Worker { + public: + Worker(); + ~Worker(); + + void StartExecuteInThread(Isolate* isolate, const char* function_string); + void PostMessage(SerializationData* data); + SerializationData* GetMessage(); + void Terminate(); + + private: + class WorkerThread : public base::Thread { + public: + explicit WorkerThread(Worker* worker) + : base::Thread(base::Thread::Options("WorkerThread")), + worker_(worker) {} + + virtual void Run() { worker_->ExecuteInThread(); } + + private: + Worker* worker_; + }; + + void ExecuteInThread(); + void Cleanup(); + static void PostMessageOut(const v8::FunctionCallbackInfo& args); + + base::Semaphore in_semaphore_; + base::Semaphore out_semaphore_; + SerializationDataQueue in_queue_; + SerializationDataQueue out_queue_; + base::Thread* thread_; + char* script_; +}; +#endif // !V8_SHARED + class ShellOptions { public: @@ -246,6 +349,17 @@ class Shell : public i::AllStatic { static void CollectGarbage(Isolate* isolate); #ifndef V8_SHARED + // TODO(binji): stupid implementation for now. Is there an easy way to hash an + // object for use in i::HashMap? By pointer? + typedef i::List> ObjectList; + static bool SerializeValue(Isolate* isolate, Handle value, + const ObjectList& to_transfer, + ObjectList* seen_objects, + SerializationData* out_data); + static MaybeLocal DeserializeValue(Isolate* isolate, + const SerializationData& data, + int* offset); + static void CleanupWorkers(); static Handle GetCompletions(Isolate* isolate, Handle text, Handle full); @@ -289,6 +403,11 @@ class Shell : public i::AllStatic { args.GetReturnValue().Set(ReadFromStdin(args.GetIsolate())); } static void Load(const v8::FunctionCallbackInfo& args); + static void WorkerNew(const v8::FunctionCallbackInfo& args); + static void WorkerPostMessage( + const v8::FunctionCallbackInfo& args); + static void WorkerGetMessage(const v8::FunctionCallbackInfo& args); + static void WorkerTerminate(const v8::FunctionCallbackInfo& args); // The OS object on the global object contains methods for performing // operating system calls: // @@ -328,6 +447,7 @@ class Shell : public i::AllStatic { static const char* kPrompt; static ShellOptions options; + static ArrayBuffer::Allocator* array_buffer_allocator; private: static Persistent evaluation_context_; @@ -341,6 +461,8 @@ class Shell : public i::AllStatic { static base::OS::MemoryMappedFile* counters_file_; static base::Mutex context_mutex_; static const base::TimeTicks kInitialTicks; + static i::List workers_; + static i::List externalized_shared_contents_; static Counter* GetCounter(const char* name, bool is_histogram); static void InstallUtilityScript(Isolate* isolate); diff --git a/test/mjsunit/d8-worker.js b/test/mjsunit/d8-worker.js new file mode 100644 index 0000000..8077917 --- /dev/null +++ b/test/mjsunit/d8-worker.js @@ -0,0 +1,131 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Test the Worker API of d8. This test only makes sense with d8. A Worker +// spawns a new OS thread and isolate, and runs it concurrently with the +// current running thread. + +function f() { + postMessage("Starting worker"); + // Set a global variable; should not be visible outside of the worker's + // context. + foo = 100; + + var c = 0; + onmessage = function(m) { + switch (c++) { + case 0: + if (m !== undefined) throw new Error("undefined"); + break; + case 1: + if (m !== null) throw new Error("null"); + break; + case 2: + if (m !== true) throw new Error("true"); + break; + case 3: + if (m !== false) throw new Error("false"); + break; + case 4: + if (m !== 100) throw new Error("Number"); + break; + case 5: + if (m !== "hi") throw new Error("String"); + break; + case 6: + if (JSON.stringify(m) !== '[4,true,"bye"]') throw new Error("Array"); + break; + case 7: + if (JSON.stringify(m) !== '{"a":1,"b":2.5,"c":"three"}') + throw new Error("Object"); + break; + case 8: + var ab = m; + var t = new Uint32Array(ab); + if (ab.byteLength !== 16) + throw new Error("ArrayBuffer clone byteLength"); + for (var i = 0; i < 4; ++i) + if (t[i] !== i) + throw new Error("ArrayBuffer clone value " + i); + break; + case 9: + var ab = m; + var t = new Uint32Array(ab); + if (ab.byteLength !== 32) + throw new Error("ArrayBuffer transfer byteLength"); + for (var i = 0; i < 8; ++i) + if (t[i] !== i) + throw new Error("ArrayBuffer transfer value " + i); + break; + } + + if (c == 10) { + postMessage("DONE"); + } + } +} + + +if (this.Worker) { + function createArrayBuffer(byteLength) { + var ab = new ArrayBuffer(byteLength); + var t = new Uint32Array(ab); + for (var i = 0; i < byteLength / 4; ++i) + t[i] = i; + return ab; + } + + var w = new Worker(f); + + assertEquals("Starting worker", w.getMessage()); + + w.postMessage(undefined); + w.postMessage(null); + w.postMessage(true); + w.postMessage(false); + w.postMessage(100); + w.postMessage("hi"); + w.postMessage([4, true, "bye"]); + w.postMessage({a: 1, b: 2.5, c: "three"}); + + // Clone ArrayBuffer + var ab1 = createArrayBuffer(16); + w.postMessage(ab1); + assertEquals(16, ab1.byteLength); // ArrayBuffer should not be neutered. + + // Transfer ArrayBuffer + var ab2 = createArrayBuffer(32); + w.postMessage(ab2, [ab2]); + assertEquals(0, ab2.byteLength); // ArrayBuffer should be neutered. + + assertEquals("undefined", typeof foo); + + // Read a message from the worker. + assertEquals("DONE", w.getMessage()); + + w.terminate(); +}