| 1 | /* |
| 2 | * Copyright (C) 2016-2019 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "JSWebAssemblyInstance.h" |
| 28 | |
| 29 | #if ENABLE(WEBASSEMBLY) |
| 30 | |
| 31 | #include "AbstractModuleRecord.h" |
| 32 | #include "JSCInlines.h" |
| 33 | #include "JSModuleEnvironment.h" |
| 34 | #include "JSModuleNamespaceObject.h" |
| 35 | #include "JSWebAssemblyHelpers.h" |
| 36 | #include "JSWebAssemblyLinkError.h" |
| 37 | #include "JSWebAssemblyMemory.h" |
| 38 | #include "JSWebAssemblyModule.h" |
| 39 | #include "WebAssemblyModuleRecord.h" |
| 40 | #include "WebAssemblyToJSCallee.h" |
| 41 | #include <wtf/StdLibExtras.h> |
| 42 | |
| 43 | namespace JSC { |
| 44 | |
| 45 | const ClassInfo JSWebAssemblyInstance::s_info = { "WebAssembly.Instance" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebAssemblyInstance) }; |
| 46 | |
| 47 | Structure* JSWebAssemblyInstance::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) |
| 48 | { |
| 49 | return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); |
| 50 | } |
| 51 | |
| 52 | JSWebAssemblyInstance::JSWebAssemblyInstance(VM& vm, Structure* structure, Ref<Wasm::Instance>&& instance) |
| 53 | : Base(vm, structure) |
| 54 | , m_instance(WTFMove(instance)) |
| 55 | { |
| 56 | for (unsigned i = 0; i < this->instance().numImportFunctions(); ++i) |
| 57 | new (this->instance().importFunction<WriteBarrier<JSObject>>(i)) WriteBarrier<JSObject>(); |
| 58 | } |
| 59 | |
| 60 | void JSWebAssemblyInstance::finishCreation(VM& vm, JSWebAssemblyModule* module, JSModuleNamespaceObject* moduleNamespaceObject) |
| 61 | { |
| 62 | Base::finishCreation(vm); |
| 63 | ASSERT(inherits(vm, info())); |
| 64 | |
| 65 | m_module.set(vm, this, module); |
| 66 | m_moduleNamespaceObject.set(vm, this, moduleNamespaceObject); |
| 67 | m_callee.set(vm, this, module->callee()); |
| 68 | |
| 69 | vm.heap.reportExtraMemoryAllocated(m_instance->extraMemoryAllocated()); |
| 70 | } |
| 71 | |
| 72 | void JSWebAssemblyInstance::destroy(JSCell* cell) |
| 73 | { |
| 74 | static_cast<JSWebAssemblyInstance*>(cell)->JSWebAssemblyInstance::~JSWebAssemblyInstance(); |
| 75 | } |
| 76 | |
| 77 | void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor) |
| 78 | { |
| 79 | auto* thisObject = jsCast<JSWebAssemblyInstance*>(cell); |
| 80 | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
| 81 | |
| 82 | Base::visitChildren(thisObject, visitor); |
| 83 | visitor.append(thisObject->m_module); |
| 84 | visitor.append(thisObject->m_codeBlock); |
| 85 | visitor.append(thisObject->m_moduleNamespaceObject); |
| 86 | visitor.append(thisObject->m_memory); |
| 87 | visitor.append(thisObject->m_table); |
| 88 | visitor.append(thisObject->m_callee); |
| 89 | visitor.reportExtraMemoryVisited(thisObject->m_instance->extraMemoryAllocated()); |
| 90 | for (unsigned i = 0; i < thisObject->instance().numImportFunctions(); ++i) |
| 91 | visitor.append(*thisObject->instance().importFunction<WriteBarrier<JSObject>>(i)); // This also keeps the functions' JSWebAssemblyInstance alive. |
| 92 | } |
| 93 | |
| 94 | void JSWebAssemblyInstance::finalizeCreation(VM& vm, ExecState* exec, Ref<Wasm::CodeBlock>&& wasmCodeBlock, JSObject* importObject, Wasm::CreationMode creationMode) |
| 95 | { |
| 96 | m_instance->finalizeCreation(this, wasmCodeBlock.copyRef()); |
| 97 | |
| 98 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 99 | |
| 100 | if (!wasmCodeBlock->runnable()) { |
| 101 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), wasmCodeBlock->errorMessage())); |
| 102 | return; |
| 103 | } |
| 104 | |
| 105 | RELEASE_ASSERT(wasmCodeBlock->isSafeToRun(memoryMode())); |
| 106 | JSWebAssemblyCodeBlock* jsCodeBlock = m_module->codeBlock(memoryMode()); |
| 107 | if (jsCodeBlock) { |
| 108 | // A CodeBlock might have already been compiled. If so, it means |
| 109 | // that the CodeBlock we are trying to compile must be the same |
| 110 | // because we will never compile a CodeBlock again once it's |
| 111 | // runnable. |
| 112 | ASSERT(&jsCodeBlock->codeBlock() == wasmCodeBlock.ptr()); |
| 113 | m_codeBlock.set(vm, this, jsCodeBlock); |
| 114 | } else { |
| 115 | jsCodeBlock = JSWebAssemblyCodeBlock::create(vm, WTFMove(wasmCodeBlock), module()->module().moduleInformation()); |
| 116 | if (UNLIKELY(!jsCodeBlock->runnable())) { |
| 117 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), jsCodeBlock->errorMessage())); |
| 118 | return; |
| 119 | } |
| 120 | m_codeBlock.set(vm, this, jsCodeBlock); |
| 121 | m_module->setCodeBlock(vm, memoryMode(), jsCodeBlock); |
| 122 | } |
| 123 | |
| 124 | for (unsigned importFunctionNum = 0; importFunctionNum < instance().numImportFunctions(); ++importFunctionNum) { |
| 125 | auto* info = instance().importFunctionInfo(importFunctionNum); |
| 126 | info->wasmToEmbedderStub = m_codeBlock->wasmToEmbedderStub(importFunctionNum); |
| 127 | } |
| 128 | |
| 129 | auto* moduleRecord = jsCast<WebAssemblyModuleRecord*>(m_moduleNamespaceObject->moduleRecord()); |
| 130 | moduleRecord->prepareLink(vm, this); |
| 131 | |
| 132 | if (creationMode == Wasm::CreationMode::FromJS) { |
| 133 | moduleRecord->link(exec, jsNull(), importObject, creationMode); |
| 134 | RETURN_IF_EXCEPTION(scope, void()); |
| 135 | |
| 136 | JSValue startResult = moduleRecord->evaluate(exec); |
| 137 | UNUSED_PARAM(startResult); |
| 138 | RETURN_IF_EXCEPTION(scope, void()); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | Identifier JSWebAssemblyInstance::createPrivateModuleKey() |
| 143 | { |
| 144 | return Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyInstance" )); |
| 145 | } |
| 146 | |
| 147 | JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, ExecState* exec, const Identifier& moduleKey, JSWebAssemblyModule* jsModule, JSObject* importObject, Structure* instanceStructure, Ref<Wasm::Module>&& module, Wasm::CreationMode creationMode) |
| 148 | { |
| 149 | auto throwScope = DECLARE_THROW_SCOPE(vm); |
| 150 | auto* globalObject = exec->lexicalGlobalObject(); |
| 151 | |
| 152 | const Wasm::ModuleInformation& moduleInformation = jsModule->moduleInformation(); |
| 153 | |
| 154 | auto exception = [&] (JSObject* error) { |
| 155 | throwException(exec, throwScope, error); |
| 156 | return nullptr; |
| 157 | }; |
| 158 | |
| 159 | if (!globalObject->webAssemblyEnabled()) |
| 160 | return exception(createEvalError(exec, globalObject->webAssemblyDisabledErrorMessage())); |
| 161 | |
| 162 | auto importFailMessage = [&] (const Wasm::Import& import, const char* before, const char* after) { |
| 163 | return makeString(before, " " , String::fromUTF8(import.module), ":" , String::fromUTF8(import.field), " " , after); |
| 164 | }; |
| 165 | |
| 166 | WebAssemblyModuleRecord* moduleRecord = WebAssemblyModuleRecord::create(exec, vm, globalObject->webAssemblyModuleRecordStructure(), moduleKey, moduleInformation); |
| 167 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 168 | |
| 169 | JSModuleNamespaceObject* moduleNamespace = moduleRecord->getModuleNamespace(exec); |
| 170 | |
| 171 | auto storeTopCallFrame = [&vm] (void* topCallFrame) { |
| 172 | vm.topCallFrame = bitwise_cast<ExecState*>(topCallFrame); |
| 173 | }; |
| 174 | |
| 175 | // FIXME: These objects could be pretty big we should try to throw OOM here. |
| 176 | auto* jsInstance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap)) JSWebAssemblyInstance(vm, instanceStructure, |
| 177 | Wasm::Instance::create(&vm.wasmContext, WTFMove(module), &vm.topEntryFrame, vm.addressOfSoftStackLimit(), WTFMove(storeTopCallFrame))); |
| 178 | jsInstance->finishCreation(vm, jsModule, moduleNamespace); |
| 179 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 180 | |
| 181 | // Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively. |
| 182 | // Let imports be an initially-empty list of external values. |
| 183 | bool hasMemoryImport = false; |
| 184 | |
| 185 | if (creationMode == Wasm::CreationMode::FromJS) { |
| 186 | // If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown. |
| 187 | if (moduleInformation.imports.size() && !importObject) |
| 188 | return exception(createTypeError(exec, "can't make WebAssembly.Instance because there is no imports Object and the WebAssembly.Module requires imports"_s )); |
| 189 | } |
| 190 | |
| 191 | // For each import i in module.imports: |
| 192 | for (auto& import : moduleInformation.imports) { |
| 193 | Identifier moduleName = Identifier::fromString(&vm, String::fromUTF8(import.module)); |
| 194 | Identifier fieldName = Identifier::fromString(&vm, String::fromUTF8(import.field)); |
| 195 | moduleRecord->appendRequestedModule(moduleName); |
| 196 | moduleRecord->addImportEntry(WebAssemblyModuleRecord::ImportEntry { |
| 197 | WebAssemblyModuleRecord::ImportEntryType::Single, |
| 198 | moduleName, |
| 199 | fieldName, |
| 200 | Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyImportName" )), |
| 201 | }); |
| 202 | |
| 203 | // Skip Wasm::ExternalKind::Function validation here. It will be done in WebAssemblyModuleRecord::link. |
| 204 | // Eventually we will move all the linking code here to WebAssemblyModuleRecord::link. |
| 205 | switch (import.kind) { |
| 206 | case Wasm::ExternalKind::Function: |
| 207 | case Wasm::ExternalKind::Global: |
| 208 | case Wasm::ExternalKind::Table: |
| 209 | continue; |
| 210 | case Wasm::ExternalKind::Memory: |
| 211 | break; |
| 212 | } |
| 213 | |
| 214 | JSValue value; |
| 215 | if (creationMode == Wasm::CreationMode::FromJS) { |
| 216 | // 1. Let o be the resultant value of performing Get(importObject, i.module_name). |
| 217 | JSValue importModuleValue = importObject->get(exec, moduleName); |
| 218 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 219 | // 2. If Type(o) is not Object, throw a TypeError. |
| 220 | if (!importModuleValue.isObject()) |
| 221 | return exception(createTypeError(exec, importFailMessage(import, "import" , "must be an object" ), defaultSourceAppender, runtimeTypeForValue(vm, importModuleValue))); |
| 222 | |
| 223 | // 3. Let v be the value of performing Get(o, i.item_name) |
| 224 | JSObject* object = jsCast<JSObject*>(importModuleValue); |
| 225 | value = object->get(exec, fieldName); |
| 226 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 227 | } |
| 228 | if (!value) |
| 229 | value = jsUndefined(); |
| 230 | |
| 231 | switch (import.kind) { |
| 232 | case Wasm::ExternalKind::Function: |
| 233 | case Wasm::ExternalKind::Global: |
| 234 | case Wasm::ExternalKind::Table: |
| 235 | break; |
| 236 | |
| 237 | case Wasm::ExternalKind::Memory: { |
| 238 | // 6. If i is a memory import: |
| 239 | RELEASE_ASSERT(!hasMemoryImport); // This should be guaranteed by a validation failure. |
| 240 | RELEASE_ASSERT(moduleInformation.memory); |
| 241 | hasMemoryImport = true; |
| 242 | JSWebAssemblyMemory* memory = jsDynamicCast<JSWebAssemblyMemory*>(vm, value); |
| 243 | // i. If v is not a WebAssembly.Memory object, throw a WebAssembly.LinkError. |
| 244 | if (!memory) |
| 245 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "is not an instance of WebAssembly.Memory" ))); |
| 246 | |
| 247 | Wasm::PageCount declaredInitial = moduleInformation.memory.initial(); |
| 248 | Wasm::PageCount importedInitial = memory->memory().initial(); |
| 249 | if (importedInitial < declaredInitial) |
| 250 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided an 'initial' that is smaller than the module's declared 'initial' import memory size" ))); |
| 251 | |
| 252 | if (Wasm::PageCount declaredMaximum = moduleInformation.memory.maximum()) { |
| 253 | Wasm::PageCount importedMaximum = memory->memory().maximum(); |
| 254 | if (!importedMaximum) |
| 255 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "did not have a 'maximum' but the module requires that it does" ))); |
| 256 | |
| 257 | if (importedMaximum > declaredMaximum) |
| 258 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided a 'maximum' that is larger than the module's declared 'maximum' import memory size" ))); |
| 259 | } |
| 260 | |
| 261 | // ii. Append v to memories. |
| 262 | // iii. Append v.[[Memory]] to imports. |
| 263 | jsInstance->setMemory(vm, memory); |
| 264 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 265 | break; |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | ASSERT(moduleRecord->importEntries().size() == moduleInformation.imports.size()); |
| 270 | |
| 271 | { |
| 272 | if (!!moduleInformation.memory && moduleInformation.memory.isImport()) { |
| 273 | // We should either have a Memory import or we should have thrown an exception. |
| 274 | RELEASE_ASSERT(hasMemoryImport); |
| 275 | } |
| 276 | |
| 277 | if (moduleInformation.memory && !hasMemoryImport) { |
| 278 | // We create a memory when it's a memory definition. |
| 279 | RELEASE_ASSERT(!moduleInformation.memory.isImport()); |
| 280 | |
| 281 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
| 282 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 283 | |
| 284 | RefPtr<Wasm::Memory> memory = Wasm::Memory::tryCreate(moduleInformation.memory.initial(), moduleInformation.memory.maximum(), |
| 285 | [&vm] (Wasm::Memory::NotifyPressure) { vm.heap.collectAsync(CollectionScope::Full); }, |
| 286 | [&vm] (Wasm::Memory::SyncTryToReclaim) { vm.heap.collectSync(CollectionScope::Full); }, |
| 287 | [&vm, jsMemory] (Wasm::Memory::GrowSuccess, Wasm::PageCount oldPageCount, Wasm::PageCount newPageCount) { jsMemory->growSuccessCallback(vm, oldPageCount, newPageCount); }); |
| 288 | if (!memory) |
| 289 | return exception(createOutOfMemoryError(exec)); |
| 290 | |
| 291 | jsMemory->adopt(memory.releaseNonNull()); |
| 292 | jsInstance->setMemory(vm, jsMemory); |
| 293 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | if (!jsInstance->memory()) { |
| 298 | // Make sure we have a dummy memory, so that wasm -> wasm thunks avoid checking for a nullptr Memory when trying to set pinned registers. |
| 299 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
| 300 | jsMemory->adopt(Wasm::Memory::create()); |
| 301 | jsInstance->setMemory(vm, jsMemory); |
| 302 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
| 303 | } |
| 304 | |
| 305 | return jsInstance; |
| 306 | } |
| 307 | |
| 308 | } // namespace JSC |
| 309 | |
| 310 | #endif // ENABLE(WEBASSEMBLY) |
| 311 | |