Commit c001a9ec authored by mtrofin's avatar mtrofin Committed by Commit bot

[wasm] Serialization/Deserialization of compiled module

Implementation of serialization/deserialization for compiled wasm
module.

BUG=v8:5072

Review-Url: https://codereview.chromium.org/2205973003
Cr-Commit-Position: refs/heads/master@{#38498}
parent 2cf2eef7
......@@ -11,7 +11,9 @@
#include "src/frames-inl.h"
#include "src/full-codegen/full-codegen.h"
#include "src/isolate-inl.h"
#include "src/snapshot/code-serializer.h"
#include "src/snapshot/natives.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
namespace internal {
......@@ -640,6 +642,43 @@ RUNTIME_FUNCTION(Runtime_SpeciesProtector) {
return isolate->heap()->ToBoolean(isolate->IsArraySpeciesLookupChainIntact());
}
// Take a compiled wasm module, serialize it and copy the buffer into an array
// buffer, which is then returned.
RUNTIME_FUNCTION(Runtime_SerializeWasmModule) {
HandleScope shs(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, module_obj, 0);
Handle<FixedArray> orig =
handle(FixedArray::cast(module_obj->GetInternalField(0)));
std::unique_ptr<ScriptData> data =
WasmCompiledModuleSerializer::SerializeWasmModule(isolate, orig);
void* buff = isolate->array_buffer_allocator()->Allocate(data->length());
Handle<JSArrayBuffer> ret = isolate->factory()->NewJSArrayBuffer();
JSArrayBuffer::Setup(ret, isolate, false, buff, data->length());
memcpy(buff, data->data(), data->length());
return *ret;
}
// Take an array buffer and attempt to reconstruct a compiled wasm module.
// Return undefined if unsuccessful.
RUNTIME_FUNCTION(Runtime_DeserializeWasmModule) {
HandleScope shs(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSArrayBuffer, buffer, 0);
Address mem_start = static_cast<Address>(buffer->backing_store());
int mem_size = static_cast<int>(buffer->byte_length()->Number());
ScriptData sc(mem_start, mem_size);
MaybeHandle<FixedArray> maybe_compiled_module =
WasmCompiledModuleSerializer::DeserializeWasmModule(isolate, &sc);
Handle<FixedArray> compiled_module;
if (!maybe_compiled_module.ToHandle(&compiled_module)) {
return isolate->heap()->undefined_value();
}
return *wasm::CreateCompiledModuleObject(isolate, compiled_module);
}
} // namespace internal
} // namespace v8
......@@ -880,7 +880,9 @@ namespace internal {
F(HasFixedFloat32Elements, 1, 1) \
F(HasFixedFloat64Elements, 1, 1) \
F(HasFixedUint8ClampedElements, 1, 1) \
F(SpeciesProtector, 0, 1)
F(SpeciesProtector, 0, 1) \
F(SerializeWasmModule, 1, 1) \
F(DeserializeWasmModule, 1, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \
......
......@@ -10,6 +10,7 @@
#include "src/log.h"
#include "src/macro-assembler.h"
#include "src/snapshot/deserializer.h"
#include "src/snapshot/snapshot.h"
#include "src/version.h"
namespace v8 {
......@@ -28,23 +29,30 @@ ScriptData* CodeSerializer::Serialize(Isolate* isolate,
}
// Serialize code object.
CodeSerializer cs(isolate, *source);
CodeSerializer cs(isolate, SerializedCodeData::SourceHash(source));
DisallowHeapAllocation no_gc;
Object** location = Handle<Object>::cast(info).location();
cs.VisitPointer(location);
cs.SerializeDeferredObjects();
cs.Pad();
SerializedCodeData data(cs.sink()->data(), &cs);
ScriptData* script_data = data.GetScriptData();
cs.reference_map()->AddAttachedReference(*source);
ScriptData* ret = cs.Serialize(info);
if (FLAG_profile_deserialization) {
double ms = timer.Elapsed().InMillisecondsF();
int length = script_data->length();
int length = ret->length();
PrintF("[Serializing to %d bytes took %0.3f ms]\n", length, ms);
}
return script_data;
return ret;
}
ScriptData* CodeSerializer::Serialize(Handle<HeapObject> obj) {
DisallowHeapAllocation no_gc;
VisitPointer(Handle<Object>::cast(obj).location());
SerializeDeferredObjects();
Pad();
SerializedCodeData data(sink()->data(), this);
return data.GetScriptData();
}
void CodeSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
......@@ -84,10 +92,8 @@ void CodeSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
DCHECK(code_object->has_reloc_info_for_serialization());
SerializeGeneric(code_object, how_to_code, where_to_point);
return;
case Code::WASM_FUNCTION:
case Code::WASM_TO_JS_FUNCTION:
case Code::JS_TO_WASM_FUNCTION:
UNREACHABLE();
default:
return SerializeCodeObject(code_object, how_to_code, where_to_point);
}
UNREACHABLE();
}
......@@ -156,30 +162,37 @@ MaybeHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
HandleScope scope(isolate);
std::unique_ptr<SerializedCodeData> scd(
SerializedCodeData::FromCachedData(isolate, cached_data, *source));
if (!scd) {
SerializedCodeData::SanityCheckResult sanity_check_result =
SerializedCodeData::CHECK_SUCCESS;
const SerializedCodeData scd = SerializedCodeData::FromCachedData(
isolate, cached_data, SerializedCodeData::SourceHash(source),
&sanity_check_result);
if (sanity_check_result != SerializedCodeData::CHECK_SUCCESS) {
if (FLAG_profile_deserialization) PrintF("[Cached code failed check]\n");
DCHECK(cached_data->rejected());
source->GetIsolate()->counters()->code_cache_reject_reason()->AddSample(
sanity_check_result);
return MaybeHandle<SharedFunctionInfo>();
}
Deserializer deserializer(scd.get());
Deserializer deserializer(&scd);
deserializer.AddAttachedObject(source);
Vector<const uint32_t> code_stub_keys = scd->CodeStubKeys();
Vector<const uint32_t> code_stub_keys = scd.CodeStubKeys();
for (int i = 0; i < code_stub_keys.length(); i++) {
deserializer.AddAttachedObject(
CodeStub::GetCode(isolate, code_stub_keys[i]).ToHandleChecked());
}
// Deserialize.
Handle<SharedFunctionInfo> result;
if (!deserializer.DeserializeCode(isolate).ToHandle(&result)) {
Handle<HeapObject> as_heap_object;
if (!deserializer.DeserializeObject(isolate).ToHandle(&as_heap_object)) {
// Deserializing may fail if the reservations cannot be fulfilled.
if (FLAG_profile_deserialization) PrintF("[Deserializing failed]\n");
return MaybeHandle<SharedFunctionInfo>();
}
Handle<SharedFunctionInfo> result =
Handle<SharedFunctionInfo>::cast(as_heap_object);
if (FLAG_profile_deserialization) {
double ms = timer.Elapsed().InMillisecondsF();
int length = cached_data->length();
......@@ -199,6 +212,40 @@ MaybeHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
return scope.CloseAndEscape(result);
}
std::unique_ptr<ScriptData> WasmCompiledModuleSerializer::SerializeWasmModule(
Isolate* isolate, Handle<FixedArray> compiled_module) {
WasmCompiledModuleSerializer wasm_cs(isolate, 0);
wasm_cs.reference_map()->AddAttachedReference(*isolate->native_context());
ScriptData* data = wasm_cs.Serialize(compiled_module);
return std::unique_ptr<ScriptData>(data);
}
MaybeHandle<FixedArray> WasmCompiledModuleSerializer::DeserializeWasmModule(
Isolate* isolate, ScriptData* data) {
SerializedCodeData::SanityCheckResult sanity_check_result =
SerializedCodeData::CHECK_SUCCESS;
MaybeHandle<FixedArray> nothing;
const SerializedCodeData scd = SerializedCodeData::FromCachedData(
isolate, data, 0, &sanity_check_result);
if (sanity_check_result != SerializedCodeData::CHECK_SUCCESS) {
return nothing;
}
Deserializer deserializer(&scd, true);
deserializer.AddAttachedObject(isolate->native_context());
Vector<const uint32_t> stub_keys = scd.CodeStubKeys();
for (int i = 0; i < stub_keys.length(); ++i) {
deserializer.AddAttachedObject(
CodeStub::GetCode(isolate, stub_keys[i]).ToHandleChecked());
}
MaybeHandle<HeapObject> obj = deserializer.DeserializeObject(isolate);
if (obj.is_null() || !obj.ToHandleChecked()->IsFixedArray()) return nothing;
return Handle<FixedArray>::cast(obj.ToHandleChecked());
}
class Checksum {
public:
explicit Checksum(Vector<const byte> payload) {
......@@ -260,7 +307,7 @@ SerializedCodeData::SerializedCodeData(const List<byte>* payload,
// Set header values.
SetMagicNumber(cs->isolate());
SetHeaderValue(kVersionHashOffset, Version::Hash());
SetHeaderValue(kSourceHashOffset, SourceHash(cs->source()));
SetHeaderValue(kSourceHashOffset, cs->source_hash());
SetHeaderValue(kCpuFeaturesOffset,
static_cast<uint32_t>(CpuFeatures::SupportedFeatures()));
SetHeaderValue(kFlagHashOffset, FlagList::Hash());
......@@ -288,7 +335,7 @@ SerializedCodeData::SerializedCodeData(const List<byte>* payload,
}
SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
Isolate* isolate, String* source) const {
Isolate* isolate, uint32_t expected_source_hash) const {
uint32_t magic_number = GetMagicNumber();
if (magic_number != ComputeMagicNumber(isolate)) return MAGIC_NUMBER_MISMATCH;
uint32_t version_hash = GetHeaderValue(kVersionHashOffset);
......@@ -298,7 +345,7 @@ SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
uint32_t c1 = GetHeaderValue(kChecksum1Offset);
uint32_t c2 = GetHeaderValue(kChecksum2Offset);
if (version_hash != Version::Hash()) return VERSION_MISMATCH;
if (source_hash != SourceHash(source)) return SOURCE_MISMATCH;
if (source_hash != expected_source_hash) return SOURCE_MISMATCH;
if (cpu_features != static_cast<uint32_t>(CpuFeatures::SupportedFeatures())) {
return CPU_FEATURES_MISMATCH;
}
......@@ -307,7 +354,7 @@ SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
return CHECK_SUCCESS;
}
uint32_t SerializedCodeData::SourceHash(String* source) const {
uint32_t SerializedCodeData::SourceHash(Handle<String> source) {
return source->length();
}
......@@ -350,17 +397,17 @@ Vector<const uint32_t> SerializedCodeData::CodeStubKeys() const {
SerializedCodeData::SerializedCodeData(ScriptData* data)
: SerializedData(const_cast<byte*>(data->data()), data->length()) {}
SerializedCodeData* SerializedCodeData::FromCachedData(Isolate* isolate,
ScriptData* cached_data,
String* source) {
const SerializedCodeData SerializedCodeData::FromCachedData(
Isolate* isolate, ScriptData* cached_data, uint32_t expected_source_hash,
SanityCheckResult* rejection_result) {
DisallowHeapAllocation no_gc;
SerializedCodeData* scd = new SerializedCodeData(cached_data);
SanityCheckResult r = scd->SanityCheck(isolate, source);
if (r == CHECK_SUCCESS) return scd;
cached_data->Reject();
source->GetIsolate()->counters()->code_cache_reject_reason()->AddSample(r);
delete scd;
return NULL;
SerializedCodeData scd(cached_data);
*rejection_result = scd.SanityCheck(isolate, expected_source_hash);
if (*rejection_result != CHECK_SUCCESS) {
cached_data->Reject();
return SerializedCodeData(nullptr, 0);
}
return scd;
}
} // namespace internal
......
......@@ -17,24 +17,29 @@ class CodeSerializer : public Serializer {
Handle<SharedFunctionInfo> info,
Handle<String> source);
ScriptData* Serialize(Handle<HeapObject> obj);
MUST_USE_RESULT static MaybeHandle<SharedFunctionInfo> Deserialize(
Isolate* isolate, ScriptData* cached_data, Handle<String> source);
String* source() const {
DCHECK(!AllowHeapAllocation::IsAllowed());
return source_;
}
const List<uint32_t>* stub_keys() const { return &stub_keys_; }
private:
CodeSerializer(Isolate* isolate, String* source)
: Serializer(isolate), source_(source) {
reference_map_.AddAttachedReference(source);
}
uint32_t source_hash() const { return source_hash_; }
protected:
explicit CodeSerializer(Isolate* isolate, uint32_t source_hash)
: Serializer(isolate), source_hash_(source_hash) {}
~CodeSerializer() override { OutputStatistics("CodeSerializer"); }
virtual void SerializeCodeObject(Code* code_object, HowToCode how_to_code,
WhereToPoint where_to_point) {
UNREACHABLE();
}
void SerializeGeneric(HeapObject* heap_object, HowToCode how_to_code,
WhereToPoint where_to_point);
private:
void SerializeObject(HeapObject* o, HowToCode how_to_code,
WhereToPoint where_to_point, int skip) override;
......@@ -42,22 +47,55 @@ class CodeSerializer : public Serializer {
WhereToPoint where_to_point);
void SerializeCodeStub(Code* code_stub, HowToCode how_to_code,
WhereToPoint where_to_point);
void SerializeGeneric(HeapObject* heap_object, HowToCode how_to_code,
WhereToPoint where_to_point);
DisallowHeapAllocation no_gc_;
String* source_;
uint32_t source_hash_;
List<uint32_t> stub_keys_;
DISALLOW_COPY_AND_ASSIGN(CodeSerializer);
};
class WasmCompiledModuleSerializer : public CodeSerializer {
public:
static std::unique_ptr<ScriptData> SerializeWasmModule(
Isolate* isolate, Handle<FixedArray> compiled_module);
static MaybeHandle<FixedArray> DeserializeWasmModule(Isolate* isolate,
ScriptData* data);
protected:
void SerializeCodeObject(Code* code_object, HowToCode how_to_code,
WhereToPoint where_to_point) override {
Code::Kind kind = code_object->kind();
if (kind == Code::WASM_FUNCTION || kind == Code::WASM_TO_JS_FUNCTION ||
kind == Code::JS_TO_WASM_FUNCTION) {
SerializeGeneric(code_object, how_to_code, where_to_point);
} else {
UNREACHABLE();
}
}
private:
WasmCompiledModuleSerializer(Isolate* isolate, uint32_t source_hash)
: CodeSerializer(isolate, source_hash) {}
DISALLOW_COPY_AND_ASSIGN(WasmCompiledModuleSerializer);
};
// Wrapper around ScriptData to provide code-serializer-specific functionality.
class SerializedCodeData : public SerializedData {
public:
enum SanityCheckResult {
CHECK_SUCCESS = 0,
MAGIC_NUMBER_MISMATCH = 1,
VERSION_MISMATCH = 2,
SOURCE_MISMATCH = 3,
CPU_FEATURES_MISMATCH = 4,
FLAGS_MISMATCH = 5,
CHECKSUM_MISMATCH = 6
};
// Used when consuming.
static SerializedCodeData* FromCachedData(Isolate* isolate,
ScriptData* cached_data,
String* source);
static const SerializedCodeData FromCachedData(
Isolate* isolate, ScriptData* cached_data, uint32_t expected_source_hash,
SanityCheckResult* rejection_result);
// Used when producing.
SerializedCodeData(const List<byte>* payload, const CodeSerializer* cs);
......@@ -70,23 +108,15 @@ class SerializedCodeData : public SerializedData {
Vector<const uint32_t> CodeStubKeys() const;
static uint32_t SourceHash(Handle<String> source);
private:
explicit SerializedCodeData(ScriptData* data);
SerializedCodeData(const byte* data, int size)
: SerializedData(const_cast<byte*>(data), size) {}
enum SanityCheckResult {
CHECK_SUCCESS = 0,
MAGIC_NUMBER_MISMATCH = 1,
VERSION_MISMATCH = 2,
SOURCE_MISMATCH = 3,
CPU_FEATURES_MISMATCH = 4,
FLAGS_MISMATCH = 5,
CHECKSUM_MISMATCH = 6
};
SanityCheckResult SanityCheck(Isolate* isolate, String* source) const;
uint32_t SourceHash(String* source) const;
SanityCheckResult SanityCheck(Isolate* isolate,
uint32_t expected_source_hash) const;
// The data header consists of uint32_t-sized entries:
// [0] magic number and external reference count
// [1] version hash
......
......@@ -136,22 +136,21 @@ MaybeHandle<Object> Deserializer::DeserializePartial(
return Handle<Object>(root, isolate);
}
MaybeHandle<SharedFunctionInfo> Deserializer::DeserializeCode(
Isolate* isolate) {
MaybeHandle<HeapObject> Deserializer::DeserializeObject(Isolate* isolate) {
Initialize(isolate);
if (!ReserveSpace()) {
return Handle<SharedFunctionInfo>();
return MaybeHandle<HeapObject>();
} else {
deserializing_user_code_ = true;
HandleScope scope(isolate);
Handle<SharedFunctionInfo> result;
Handle<HeapObject> result;
{
DisallowHeapAllocation no_gc;
Object* root;
VisitPointer(&root);
DeserializeDeferredObjects();
FlushICacheForNewCodeObjectsAndRecordEmbeddedObjects();
result = Handle<SharedFunctionInfo>(SharedFunctionInfo::cast(root));
result = Handle<HeapObject>(HeapObject::cast(root));
isolate->heap()->RegisterReservationsForBlackAllocation(reservations_);
}
CommitPostProcessedObjects(isolate);
......
......@@ -30,13 +30,13 @@ class Deserializer : public SerializerDeserializer {
public:
// Create a deserializer from a snapshot byte source.
template <class Data>
explicit Deserializer(Data* data)
explicit Deserializer(Data* data, bool deserializing_user_code = false)
: isolate_(NULL),
source_(data->Payload()),
magic_number_(data->GetMagicNumber()),
external_reference_table_(NULL),
deserialized_large_objects_(0),
deserializing_user_code_(false),
deserializing_user_code_(deserializing_user_code),
next_alignment_(kWordAligned) {
DecodeReservation(data->Reservations());
}
......@@ -50,8 +50,8 @@ class Deserializer : public SerializerDeserializer {
MaybeHandle<Object> DeserializePartial(Isolate* isolate,
Handle<JSGlobalProxy> global_proxy);
// Deserialize a shared function info. Fail gracefully.
MaybeHandle<SharedFunctionInfo> DeserializeCode(Isolate* isolate);
// Deserialize an object graph. Fail gracefully.
MaybeHandle<HeapObject> DeserializeObject(Isolate* isolate);
// Add an object to back an attached reference. The order to add objects must
// mirror the order they are added in the serializer.
......
......@@ -318,17 +318,9 @@ static i::MaybeHandle<i::JSObject> CreateModuleObject(
i::MaybeHandle<i::FixedArray> compiled_module =
decoded_module->CompileFunctions(i_isolate, thrower);
if (compiled_module.is_null()) return nothing;
Local<Context> context = isolate->GetCurrentContext();
i::Handle<i::Context> i_context = Utils::OpenHandle(*context);
i::Handle<i::JSFunction> module_cons(i_context->wasm_module_constructor());
i::Handle<i::JSObject> module_obj =
i_isolate->factory()->NewJSObject(module_cons);
module_obj->SetInternalField(0, *compiled_module.ToHandleChecked());
i::Handle<i::Object> module_ref = Utils::OpenHandle(*source);
i::Handle<i::Symbol> module_sym(i_context->wasm_module_sym());
i::Object::SetProperty(module_obj, module_sym, module_ref, i::STRICT).Check();
return module_obj;
return i::wasm::CreateCompiledModuleObject(i_isolate,
compiled_module.ToHandleChecked());
}
void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
......
......@@ -5,12 +5,14 @@
#include <memory>
#include "src/base/atomic-utils.h"
#include "src/code-stubs.h"
#include "src/macro-assembler.h"
#include "src/objects.h"
#include "src/property-descriptor.h"
#include "src/v8.h"
#include "src/simulator.h"
#include "src/snapshot/snapshot.h"
#include "src/v8.h"
#include "src/wasm/ast-decoder.h"
#include "src/wasm/module-decoder.h"
......@@ -1571,6 +1573,17 @@ int GetNumberOfFunctions(JSObject* wasm) {
return ByteArray::cast(func_names_obj)->get_int(0);
}
Handle<JSObject> CreateCompiledModuleObject(
Isolate* isolate, Handle<FixedArray> compiled_module) {
Handle<JSFunction> module_cons(
isolate->native_context()->wasm_module_constructor());
Handle<JSObject> module_obj = isolate->factory()->NewJSObject(module_cons);
module_obj->SetInternalField(0, *compiled_module);
Handle<Symbol> module_sym(isolate->native_context()->wasm_module_sym());
Object::SetProperty(module_obj, module_sym, module_obj, STRICT).Check();
return module_obj;
}
namespace testing {
int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
......
......@@ -9,6 +9,8 @@
#include "src/api.h"
#include "src/handles.h"
#include "src/parsing/preparse-data.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-result.h"
......@@ -396,6 +398,9 @@ Handle<FixedArray> BuildFunctionTable(Isolate* isolate, uint32_t index,
void PopulateFunctionTable(Handle<FixedArray> table, uint32_t table_size,
const std::vector<Handle<Code>>* code_table);
Handle<JSObject> CreateCompiledModuleObject(Isolate* isolate,
Handle<FixedArray> compiled_module);
namespace testing {
// Decode, verify, and run the function labeled "main" in the
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-wasm --allow-natives-syntax --expose-gc
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
(function SerializeAndDeserializeModule() {
var builder = new WasmModuleBuilder();
builder.addMemory(1,1, true);
var kSig_v_i = makeSig([kAstI32], []);
var signature = builder.addType(kSig_v_i);
builder.addImport("some_value", kSig_i);
builder.addImport("writer", signature);
builder.addFunction("main", kSig_i_i)
.addBody([
kExprI32Const, 1,
kExprGetLocal, 0,
kExprI32LoadMem, 0, 0,
kExprCallIndirect, kArity1, signature,
kExprGetLocal,0,
kExprI32LoadMem,0, 0,
kExprCallImport, kArity0, 0,
kExprI32Add
]).exportFunc();
// writer(mem[i]);
// return mem[i] + some_value();
builder.addFunction("_wrap_writer", signature)
.addBody([
kExprGetLocal, 0,
kExprCallImport, kArity1, 1]);
builder.appendToTable([0, 1]);
var module = new WebAssembly.Module(builder.toBuffer());
var buff = %SerializeWasmModule(module);
module = null;
gc();
module = %DeserializeWasmModule(buff);
var mem_1 = new ArrayBuffer(4);
var view_1 = new Int32Array(mem_1);
view_1[0] = 42;
var outval_1;
var i1 = new WebAssembly.Instance(module, {some_value: () => 1,
writer: (x)=>outval_1 = x }, mem_1);
assertEquals(43, i1.exports.main(0));
assertEquals(42, outval_1);
})();
(function DeserializeInvalidObject() {
var invalid_buffer = new ArrayBuffer(10);
module = %DeserializeWasmModule(invalid_buffer);
assertEquals(module, undefined);
})();
(function RelationBetweenModuleAndClone() {
let builder = new WasmModuleBuilder();
builder.addFunction("main", kSig_i)
.addBody([kExprI8Const, 42])
.exportFunc();
var compiled_module = new WebAssembly.Module(builder.toBuffer());
var serialized = %SerializeWasmModule(compiled_module);
var clone = %DeserializeWasmModule(serialized);
assertNotNull(clone);
assertFalse(clone == undefined);
assertFalse(clone == compiled_module);
assertEquals(clone.constructor, compiled_module.constructor);
})()
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