Commit 2232eb7e authored by Andreas Haas's avatar Andreas Haas Committed by Commit Bot

[v8][wasm] Replace desugaring of WebAssembly.instantiateStreaming

This is the blink side CL to refactor WebAssembly.instantiateStreaming
to make it spec compliant again. The design doc where the whole change
is discussed is available in the tracking bug. The tracking bug also
references prototype implementations of the whole change, which includes
the changes in this CL.

Note that most of the changes in this CL are copied from the existing
implementation which uses the old API.

I added a regression test but disable it until the V8 change landed.

Bug: chromium:860637

Change-Id: Iba75dc4e187aad5891084e5e7dcc3f759e86f706
Reviewed-on: https://chromium-review.googlesource.com/1081947
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577586}
parent a87a02de
...@@ -3754,7 +3754,8 @@ crbug.com/747751 [ Win ] http/tests/devtools/application-panel/resources-panel-r ...@@ -3754,7 +3754,8 @@ crbug.com/747751 [ Win ] http/tests/devtools/application-panel/resources-panel-r
crbug.com/689781 external/wpt/media-source/mediasource-duration.html [ Failure Pass ] crbug.com/689781 external/wpt/media-source/mediasource-duration.html [ Failure Pass ]
# When WebAssembly is exposed in V8 (soon), this test has the wrong number of expected Object.getOwnPropertyNames() for global object. crbug.com/860637 http/tests/wasm_streaming/regression860637.html [ Skip ]
crbug.com/860637 virtual/enable_wasm_streaming/http/tests/wasm_streaming/regression860637.html [ Skip ]
crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom125.html [ Failure Pass ] crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom125.html [ Failure Pass ]
crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Failure Pass ] crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Failure Pass ]
......
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="wasm_response_apis.js"></script>
<script src="../wasm/resources/wasm-constants.js"></script>
<script src="../wasm/resources/wasm-module-builder.js"></script>
<script>
promise_test(TestRegression837417, "Regression test");
</script>
...@@ -255,3 +255,12 @@ function TestStreamingInstantiateExistsInWorker() { ...@@ -255,3 +255,12 @@ function TestStreamingInstantiateExistsInWorker() {
worker.addEventListener('message', e => resolve(e.data)); worker.addEventListener('message', e => resolve(e.data));
return promise.then(exists => assert_true(exists)); return promise.then(exists => assert_true(exists));
} }
function TestRegression837417() {
let old_then = WebAssembly.Module.prototype.then;
WebAssembly.Module.prototype.then = resolve => resolve(String.fromCharCode(
null, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41));
return WebAssembly.instantiateStreaming(fetch(incrementer_url))
.then(pair => assert_equals(5, pair.instance.exports.increment(4)));
}
...@@ -20,6 +20,103 @@ namespace blink { ...@@ -20,6 +20,103 @@ namespace blink {
namespace { namespace {
// The |FetchDataLoader| for streaming compilation of WebAssembly code. The
// received bytes get forwarded to the V8 API class |WasmStreaming|.
class FetchDataLoaderForWasmStreaming final : public FetchDataLoader,
public BytesConsumer::Client {
USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderForWasmStreaming);
public:
FetchDataLoaderForWasmStreaming(ScriptState* script_state,
std::shared_ptr<v8::WasmStreaming> streaming)
: streaming_(std::move(streaming)), script_state_(script_state) {}
void Start(BytesConsumer* consumer,
FetchDataLoader::Client* client) override {
DCHECK(!consumer_);
DCHECK(!client_);
client_ = client;
consumer_ = consumer;
consumer_->SetClient(this);
OnStateChange();
}
void OnStateChange() override {
while (true) {
// |buffer| is owned by |consumer_|.
const char* buffer = nullptr;
size_t available = 0;
BytesConsumer::Result result = consumer_->BeginRead(&buffer, &available);
if (result == BytesConsumer::Result::kShouldWait)
return;
if (result == BytesConsumer::Result::kOk) {
if (available > 0) {
DCHECK_NE(buffer, nullptr);
streaming_->OnBytesReceived(reinterpret_cast<const uint8_t*>(buffer),
available);
}
result = consumer_->EndRead(available);
}
switch (result) {
case BytesConsumer::Result::kShouldWait:
NOTREACHED();
return;
case BytesConsumer::Result::kOk: {
break;
}
case BytesConsumer::Result::kDone: {
streaming_->Finish();
client_->DidFetchDataLoadedCustomFormat();
return;
}
case BytesConsumer::Result::kError: {
return AbortCompilation();
}
}
}
}
String DebugName() const override { return "FetchDataLoaderForWasmModule"; }
void Cancel() override {
consumer_->Cancel();
return AbortCompilation();
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(consumer_);
visitor->Trace(client_);
visitor->Trace(script_state_);
FetchDataLoader::Trace(visitor);
BytesConsumer::Client::Trace(visitor);
}
private:
// TODO(ahaas): replace with spec-ed error types, once spec clarifies
// what they are.
void AbortCompilation() {
if (script_state_->ContextIsValid()) {
ScriptState::Scope scope(script_state_);
streaming_->Abort(V8ThrowException::CreateTypeError(
script_state_->GetIsolate(), "Could not download wasm module"));
} else {
// We are not allowed to execute a script, which indicates that we should
// not reject the promise of the streaming compilation. By passing no
// abort reason, we indicate the V8 side that the promise should not get
// rejected.
streaming_->Abort(v8::Local<v8::Value>());
}
}
Member<BytesConsumer> consumer_;
Member<FetchDataLoader::Client> client_;
std::shared_ptr<v8::WasmStreaming> streaming_;
const Member<ScriptState> script_state_;
};
// TODO(ahaas): Remove |FetchDataLoaderAsWasmModule| once the
// |SetWasmCompileStreamingCallback| API is successfully replaced by the
// |SetWasmStreamingCallback| API.
class FetchDataLoaderAsWasmModule final : public FetchDataLoader, class FetchDataLoaderAsWasmModule final : public FetchDataLoader,
public BytesConsumer::Client { public BytesConsumer::Client {
USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsWasmModule); USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsWasmModule);
...@@ -42,7 +139,7 @@ class FetchDataLoaderAsWasmModule final : public FetchDataLoader, ...@@ -42,7 +139,7 @@ class FetchDataLoaderAsWasmModule final : public FetchDataLoader,
void OnStateChange() override { void OnStateChange() override {
while (true) { while (true) {
// {buffer} is owned by {m_consumer}. // |buffer| is owned by |consumer_|.
const char* buffer = nullptr; const char* buffer = nullptr;
size_t available = 0; size_t available = 0;
BytesConsumer::Result result = consumer_->BeginRead(&buffer, &available); BytesConsumer::Result result = consumer_->BeginRead(&buffer, &available);
...@@ -96,8 +193,8 @@ class FetchDataLoaderAsWasmModule final : public FetchDataLoader, ...@@ -96,8 +193,8 @@ class FetchDataLoaderAsWasmModule final : public FetchDataLoader,
// TODO(mtrofin): replace with spec-ed error types, once spec clarifies // TODO(mtrofin): replace with spec-ed error types, once spec clarifies
// what they are. // what they are.
void AbortCompilation() { void AbortCompilation() {
ScriptState::Scope scope(script_state_); if (script_state_->ContextIsValid()) {
if (!ExecutionContext::From(script_state_)->IsContextDestroyed()) { ScriptState::Scope scope(script_state_);
builder_.Abort(V8ThrowException::CreateTypeError( builder_.Abort(V8ThrowException::CreateTypeError(
script_state_->GetIsolate(), "Could not download wasm module")); script_state_->GetIsolate(), "Could not download wasm module"));
} else { } else {
...@@ -135,6 +232,97 @@ class WasmDataLoaderClient final ...@@ -135,6 +232,97 @@ class WasmDataLoaderClient final
} }
}; };
// ExceptionToAbortStreamingScope converts a possible exception to an abort
// message for WasmStreaming instead of throwing the exception.
//
// All exceptions which happen in the setup of WebAssembly streaming compilation
// have to be passed as an abort message to V8 so that V8 can reject the promise
// associated to the streaming compilation.
class ExceptionToAbortStreamingScope {
STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(ExceptionToAbortStreamingScope);
public:
ExceptionToAbortStreamingScope(std::shared_ptr<v8::WasmStreaming> streaming,
ExceptionState& exception_state)
: streaming_(streaming), exception_state_(exception_state) {}
~ExceptionToAbortStreamingScope() {
if (!exception_state_.HadException())
return;
streaming_->Abort(exception_state_.GetException());
exception_state_.ClearException();
}
private:
std::shared_ptr<v8::WasmStreaming> streaming_;
ExceptionState& exception_state_;
};
void StreamFromResponseCallback(
const v8::FunctionCallbackInfo<v8::Value>& args) {
ExceptionState exception_state(args.GetIsolate(),
ExceptionState::kExecutionContext,
"WebAssembly", "compile");
std::shared_ptr<v8::WasmStreaming> streaming =
v8::WasmStreaming::Unpack(args.GetIsolate(), args.Data());
ExceptionToAbortStreamingScope exception_scope(streaming, exception_state);
ScriptState* script_state = ScriptState::ForCurrentRealm(args);
if (!script_state->ContextIsValid()) {
// We do not have an execution context, we just abort streaming compilation
// immediately without error.
streaming->Abort(v8::Local<v8::Value>());
return;
}
Response* response =
V8Response::ToImplWithTypeCheck(args.GetIsolate(), args[0]);
if (!response) {
exception_state.ThrowTypeError(
"An argument must be provided, which must be a "
"Response or Promise<Response> object");
return;
}
if (!response->ok()) {
exception_state.ThrowTypeError("HTTP status code is not ok");
return;
}
if (response->MimeType() != "application/wasm") {
exception_state.ThrowTypeError(
"Incorrect response MIME type. Expected 'application/wasm'.");
return;
}
Body::BodyLocked body_locked = response->IsBodyLocked(exception_state);
if (body_locked == Body::BodyLocked::kBroken)
return;
if (body_locked == Body::BodyLocked::kLocked ||
response->IsBodyUsed(exception_state) == Body::BodyUsed::kUsed) {
DCHECK(!exception_state.HadException());
exception_state.ThrowTypeError(
"Cannot compile WebAssembly.Module from an already read Response");
return;
}
if (exception_state.HadException())
return;
if (!response->BodyBuffer()) {
exception_state.ThrowTypeError("Response object has a null body.");
return;
}
FetchDataLoaderForWasmStreaming* loader =
new FetchDataLoaderForWasmStreaming(script_state, streaming);
response->BodyBuffer()->StartLoading(loader, new WasmDataLoaderClient(),
exception_state);
}
// This callback may be entered as a promise is resolved, or directly // This callback may be entered as a promise is resolved, or directly
// from the overload callback. // from the overload callback.
// See // See
...@@ -147,7 +335,7 @@ void CompileFromResponseCallback( ...@@ -147,7 +335,7 @@ void CompileFromResponseCallback(
ExceptionToRejectPromiseScope reject_promise_scope(args, exception_state); ExceptionToRejectPromiseScope reject_promise_scope(args, exception_state);
ScriptState* script_state = ScriptState::ForCurrentRealm(args); ScriptState* script_state = ScriptState::ForCurrentRealm(args);
if (!ExecutionContext::From(script_state)) { if (!script_state->ContextIsValid()) {
V8SetReturnValue(args, ScriptPromise().V8Value()); V8SetReturnValue(args, ScriptPromise().V8Value());
return; return;
} }
...@@ -172,13 +360,18 @@ void CompileFromResponseCallback( ...@@ -172,13 +360,18 @@ void CompileFromResponseCallback(
return; return;
} }
if (response->IsBodyLocked(exception_state) == Body::BodyLocked::kLocked || Body::BodyLocked body_locked = response->IsBodyLocked(exception_state);
if (body_locked == Body::BodyLocked::kBroken)
return;
if (body_locked == Body::BodyLocked::kLocked ||
response->IsBodyUsed(exception_state) == Body::BodyUsed::kUsed) { response->IsBodyUsed(exception_state) == Body::BodyUsed::kUsed) {
DCHECK(!exception_state.HadException()); DCHECK(!exception_state.HadException());
exception_state.ThrowTypeError( exception_state.ThrowTypeError(
"Cannot compile WebAssembly.Module from an already read Response"); "Cannot compile WebAssembly.Module from an already read Response");
return; return;
} }
if (exception_state.HadException()) if (exception_state.HadException())
return; return;
...@@ -234,6 +427,7 @@ void WasmCompileStreamingImpl(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -234,6 +427,7 @@ void WasmCompileStreamingImpl(const v8::FunctionCallbackInfo<v8::Value>& args) {
void WasmResponseExtensions::Initialize(v8::Isolate* isolate) { void WasmResponseExtensions::Initialize(v8::Isolate* isolate) {
if (RuntimeEnabledFeatures::WebAssemblyStreamingEnabled()) { if (RuntimeEnabledFeatures::WebAssemblyStreamingEnabled()) {
isolate->SetWasmCompileStreamingCallback(WasmCompileStreamingImpl); isolate->SetWasmCompileStreamingCallback(WasmCompileStreamingImpl);
isolate->SetWasmStreamingCallback(StreamFromResponseCallback);
} }
} }
......
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