Commit 380bc71f authored by mtrofin's avatar mtrofin Committed by Commit bot

[wasm] response-based compile APIs

Spec available at: https://github.com/WebAssembly/design/blob/master/Web.md#webassemblycompile

This CL introduces the WebAssembly.compile overload in Blink.

Notably, we do not currently check the mime type (chromium:707149).

The V8 side is still under development. We currently exercise a skeleton API with a
trivial implementation. Once the full implementation is implemented, we will enhance
the blink side of the feature - for instance, we can introduce back pressure
information.

BUG=chromium:697028

Review-Url: https://codereview.chromium.org/2780693003
Cr-Commit-Position: refs/heads/master@{#462917}
parent 8927c43c
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="wasm_response_apis.js"></script>
<script>
promise_test(TestStreamedCompile, "test streamed compile");
promise_test(TestShortFormStreamedCompile, "test streamed compile with promise parameter");
promise_test(NegativeTestStreamedCompilePromise, "promise must produce a Response");
promise_test(BlankResponse, "blank response");
promise_test(FromArrayBuffer, "from array buffer");
promise_test(FromInvalidArrayBuffer, "from an invalid array buffer");
</script>
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function TestStreamedCompile() {
return fetch('incrementer.wasm')
.then(WebAssembly.compile)
.then(m => new WebAssembly.Instance(m))
.then(i => assert_equals(5, i.exports.increment(4)));
}
function TestShortFormStreamedCompile() {
return WebAssembly.compile(fetch('incrementer.wasm'))
.then(m => new WebAssembly.Instance(m))
.then(i => assert_equals(5, i.exports.increment(4)));
}
function NegativeTestStreamedCompilePromise() {
return WebAssembly.compile(new Promise((resolve, reject)=>{resolve(5);}))
.then(assert_unreached,
e => assert_true(e instanceof TypeError));
}
function BlankResponse() {
return WebAssembly.compile(new Response())
.then(assert_unreached,
e => assert_true(e instanceof TypeError));
}
function FromArrayBuffer() {
return fetch('incrementer.wasm')
.then(r => r.arrayBuffer())
.then(arr => new Response(arr))
.then(WebAssembly.compile)
.then(m => new WebAssembly.Instance(m))
.then(i => assert_equals(6, i.exports.increment(5)));
}
function FromInvalidArrayBuffer() {
var arr = new ArrayBuffer(10);
var view = new Uint8Array(arr);
for (var i = 0; i < view.length; ++i) view[i] = i;
return WebAssembly.compile(new Response(arr))
.then(assert_unreached,
e => assert_true(e instanceof Error));
}
......@@ -7,6 +7,7 @@
#include "bindings/core/v8/V8PerIsolateData.h"
#include "bindings/modules/v8/ConditionalFeaturesForModules.h"
#include "bindings/modules/v8/SerializedScriptValueForModulesFactory.h"
#include "bindings/modules/v8/wasm/WasmResponseExtensions.h"
namespace blink {
......@@ -19,6 +20,7 @@ void ModuleBindingsInitializer::init() {
initPartialInterfacesInModules();
SerializedScriptValueFactory::initialize(
new SerializedScriptValueForModulesFactory);
WasmResponseExtensions::initialize(V8PerIsolateData::mainThreadIsolate());
}
} // namespace blink
......@@ -22,6 +22,8 @@ bindings_modules_v8_files =
"V8BindingForModules.cpp",
"V8BindingForModules.h",
"V8ServiceWorkerMessageEventInternal.h",
"wasm/WasmResponseExtensions.cpp",
"wasm/WasmResponseExtensions.h",
"WebGLAny.cpp",
"WebGLAny.h",
],
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings/modules/v8/wasm/WasmResponseExtensions.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/modules/v8/V8Response.h"
#include "modules/fetch/BodyStreamBuffer.h"
#include "modules/fetch/FetchDataLoader.h"
#include "platform/heap/Handle.h"
#include "wtf/RefPtr.h"
namespace blink {
namespace {
class FetchDataLoaderAsWasmModule final : public FetchDataLoader,
public BytesConsumer::Client {
USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsWasmModule);
public:
FetchDataLoaderAsWasmModule(ScriptPromiseResolver* resolver,
ScriptState* scriptState)
: m_resolver(resolver),
m_builder(scriptState->isolate()),
m_scriptState(scriptState) {}
void start(BytesConsumer* consumer,
FetchDataLoader::Client* client) override {
DCHECK(!m_consumer);
DCHECK(!m_client);
m_client = client;
m_consumer = consumer;
m_consumer->setClient(this);
onStateChange();
}
void onStateChange() override {
while (true) {
// {buffer} is owned by {m_consumer}.
const char* buffer = nullptr;
size_t available = 0;
BytesConsumer::Result result = m_consumer->beginRead(&buffer, &available);
if (result == BytesConsumer::Result::ShouldWait)
return;
if (result == BytesConsumer::Result::Ok) {
if (available > 0) {
DCHECK_NE(buffer, nullptr);
m_builder.OnBytesReceived(reinterpret_cast<const uint8_t*>(buffer),
available);
}
result = m_consumer->endRead(available);
}
switch (result) {
case BytesConsumer::Result::ShouldWait:
NOTREACHED();
return;
case BytesConsumer::Result::Ok: {
break;
}
case BytesConsumer::Result::Done: {
v8::Isolate* isolate = m_scriptState->isolate();
ScriptState::Scope scope(m_scriptState.get());
{
// The TryCatch destructor will clear the exception. We
// scope the block here to ensure tight control over the
// lifetime of the exception.
v8::TryCatch trycatch(isolate);
v8::Local<v8::WasmCompiledModule> module;
if (m_builder.Finish().ToLocal(&module)) {
DCHECK(!trycatch.HasCaught());
ScriptValue scriptValue(m_scriptState.get(), module);
m_resolver->resolve(scriptValue);
} else {
DCHECK(trycatch.HasCaught());
m_resolver->reject(trycatch.Exception());
}
}
m_client->didFetchDataLoadedCustomFormat();
return;
}
case BytesConsumer::Result::Error: {
// TODO(mtrofin): do we need an abort on the wasm side?
// Something like "m_outStream->abort()" maybe?
return rejectPromise();
}
}
}
}
void cancel() override {
m_consumer->cancel();
return rejectPromise();
}
DEFINE_INLINE_TRACE() {
visitor->trace(m_consumer);
visitor->trace(m_resolver);
visitor->trace(m_client);
FetchDataLoader::trace(visitor);
BytesConsumer::Client::trace(visitor);
}
private:
// TODO(mtrofin): replace with spec-ed error types, once spec clarifies
// what they are.
void rejectPromise() {
m_resolver->reject(V8ThrowException::createTypeError(
m_scriptState->isolate(), "Could not download wasm module"));
}
Member<BytesConsumer> m_consumer;
Member<ScriptPromiseResolver> m_resolver;
Member<FetchDataLoader::Client> m_client;
v8::WasmModuleObjectBuilder m_builder;
const RefPtr<ScriptState> m_scriptState;
};
// TODO(mtrofin): WasmDataLoaderClient is necessary so we may provide an
// argument to BodyStreamBuffer::startLoading, however, it fulfills
// a very small role. Consider refactoring to avoid it.
class WasmDataLoaderClient final
: public GarbageCollectedFinalized<WasmDataLoaderClient>,
public FetchDataLoader::Client {
WTF_MAKE_NONCOPYABLE(WasmDataLoaderClient);
USING_GARBAGE_COLLECTED_MIXIN(WasmDataLoaderClient);
public:
explicit WasmDataLoaderClient() {}
void didFetchDataLoadedCustomFormat() override {}
void didFetchDataLoadFailed() override { NOTREACHED(); }
};
// This callback may be entered as a promise is resolved, or directly
// from the overload callback.
// See
// https://github.com/WebAssembly/design/blob/master/Web.md#webassemblycompile
void compileFromResponseCallback(
const v8::FunctionCallbackInfo<v8::Value>& args) {
ExceptionState exceptionState(args.GetIsolate(),
ExceptionState::ExecutionContext, "WebAssembly",
"compile");
ExceptionToRejectPromiseScope rejectPromiseScope(args, exceptionState);
ScriptState* scriptState = ScriptState::forReceiverObject(args);
if (!scriptState->getExecutionContext()) {
v8SetReturnValue(args, ScriptPromise().v8Value());
return;
}
if (args.Length() < 1 || !args[0]->IsObject() ||
!V8Response::hasInstance(args[0], args.GetIsolate())) {
v8SetReturnValue(
args, ScriptPromise::reject(
scriptState, V8ThrowException::createTypeError(
scriptState->isolate(),
"Promise argument must be called with a "
"Promise<Response> object"))
.v8Value());
return;
}
Response* response = V8Response::toImpl(v8::Local<v8::Object>::Cast(args[0]));
ScriptPromise promise;
if (response->isBodyLocked() || response->bodyUsed()) {
promise = ScriptPromise::reject(
scriptState,
V8ThrowException::createTypeError(
scriptState->isolate(),
"Cannot compile WebAssembly.Module from an already read Response"));
} else {
ScriptPromiseResolver* resolver =
ScriptPromiseResolver::create(scriptState);
if (response->bodyBuffer()) {
promise = resolver->promise();
response->bodyBuffer()->startLoading(
new FetchDataLoaderAsWasmModule(resolver, scriptState),
new WasmDataLoaderClient());
} else {
promise = ScriptPromise::reject(
scriptState,
V8ThrowException::createTypeError(
scriptState->isolate(), "Response object has a null body."));
}
}
v8SetReturnValue(args, promise.v8Value());
}
// See https://crbug.com/708238 for tracking avoiding the hand-generated code.
bool wasmCompileOverload(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() < 1 || !args[0]->IsObject())
return false;
if (!args[0]->IsPromise() &&
!V8Response::hasInstance(args[0], args.GetIsolate()))
return false;
v8::Isolate* isolate = args.GetIsolate();
ScriptState* scriptState = ScriptState::forReceiverObject(args);
v8::Local<v8::Function> compileCallback =
v8::Function::New(isolate, compileFromResponseCallback);
ScriptPromiseResolver* scriptPromiseResolver =
ScriptPromiseResolver::create(scriptState);
// treat either case of parameter as
// Promise.resolve(parameter)
// as per https://www.w3.org/2001/tag/doc/promises-guide#resolve-arguments
// Ending with:
// return Promise.resolve(parameter).then(compileCallback);
ScriptPromise parameterAsPromise = scriptPromiseResolver->promise();
v8SetReturnValue(args, ScriptPromise::cast(scriptState, args[0])
.then(compileCallback)
.v8Value());
// resolve the first parameter promise.
scriptPromiseResolver->resolve(ScriptValue::from(scriptState, args[0]));
return true;
}
} // namespace
void WasmResponseExtensions::initialize(v8::Isolate* isolate) {
isolate->SetWasmCompileCallback(wasmCompileOverload);
}
} // namespace blink
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WasmResponseExtensions_h
#define WasmResponseExtensions_h
#include "modules/ModulesExport.h"
#include "v8/include/v8.h"
#include "wtf/Allocator.h"
namespace blink {
// Injects Web Platform - specific overloads for WebAssembly APIs.
// See https://github.com/WebAssembly/design/blob/master/Web.md
class MODULES_EXPORT WasmResponseExtensions {
STATIC_ONLY(WasmResponseExtensions);
public:
static void initialize(v8::Isolate*);
};
} // namespace blink
#endif // WasmResponseextensions_h
......@@ -56,6 +56,11 @@ class BodyStreamBuffer::LoaderClient final
m_client->didFetchDataLoadedStream();
}
void didFetchDataLoadedCustomFormat() override {
m_buffer->endLoading();
m_client->didFetchDataLoadedCustomFormat();
}
void didFetchDataLoadFailed() override {
m_buffer->endLoading();
m_client->didFetchDataLoadFailed();
......
......@@ -35,17 +35,19 @@ class MODULES_EXPORT FetchDataLoader
// The method corresponding to createLoaderAs... is called on success.
virtual void didFetchDataLoadedBlobHandle(PassRefPtr<BlobDataHandle>) {
ASSERT_NOT_REACHED();
NOTREACHED();
}
virtual void didFetchDataLoadedArrayBuffer(DOMArrayBuffer*) {
ASSERT_NOT_REACHED();
}
virtual void didFetchDataLoadedString(const String&) {
ASSERT_NOT_REACHED();
NOTREACHED();
}
virtual void didFetchDataLoadedString(const String&) { NOTREACHED(); }
// This is called after all data are read from |handle| and written
// to |outStream|, and |outStream| is closed or aborted.
virtual void didFetchDataLoadedStream() { ASSERT_NOT_REACHED(); }
virtual void didFetchDataLoadedStream() { NOTREACHED(); }
// This function is called when a "custom" FetchDataLoader (none of the
// ones listed above) finishes loading.
virtual void didFetchDataLoadedCustomFormat() { NOTREACHED(); }
virtual void didFetchDataLoadFailed() = 0;
......@@ -55,8 +57,7 @@ class MODULES_EXPORT FetchDataLoader
static FetchDataLoader* createLoaderAsBlobHandle(const String& mimeType);
static FetchDataLoader* createLoaderAsArrayBuffer();
static FetchDataLoader* createLoaderAsString();
static FetchDataLoader* createLoaderAsStream(Stream* outStream);
static FetchDataLoader* createLoaderAsStream(Stream*);
virtual ~FetchDataLoader() {}
// |consumer| must not have a client when called.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment