/* * Copyright (C) 2019-2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. */ #include "config.h" #include "WasmSlowPaths.h" #if ENABLE(WEBASSEMBLY) #include "BytecodeStructs.h" #include "LLIntData.h" #include "WasmBBQPlan.h" #include "WasmCallee.h" #include "WasmFunctionCodeBlock.h" #include "WasmInstance.h" #include "WasmModuleInformation.h" #include "WasmOMGForOSREntryPlan.h" #include "WasmOMGPlan.h" #include "WasmOperations.h" #include "WasmSignatureInlines.h" #include "WasmWorklist.h" namespace JSC { namespace LLInt { #define WASM_RETURN_TWO(first, second) do { \ return encodeResult(first, second); \ } while (false) #define WASM_END_IMPL() WASM_RETURN_TWO(pc, 0) #define WASM_THROW(exceptionType) do { \ callFrame->setArgumentCountIncludingThis(static_cast(exceptionType)); \ WASM_RETURN_TWO(LLInt::wasmExceptionInstructions(), 0); \ } while (false) #define WASM_END() do { \ WASM_END_IMPL(); \ } while (false) #define WASM_RETURN(value) do { \ callFrame->uncheckedR(instruction.m_dst) = static_cast(value); \ WASM_END_IMPL(); \ } while (false) #define WASM_CALL_RETURN(targetInstance, callTarget, callTargetTag) do { \ WASM_RETURN_TWO((retagCodePtr(callTarget)), targetInstance); \ } while (false) #define CODE_BLOCK() \ bitwise_cast(callFrame->codeBlock()) #define READ(virtualRegister) \ (virtualRegister.isConstant() \ ? JSValue::decode(CODE_BLOCK()->getConstant(virtualRegister)) \ : callFrame->r(virtualRegister)) enum class RequiredWasmJIT { Any, OMG }; inline bool shouldJIT(Wasm::FunctionCodeBlock* codeBlock, RequiredWasmJIT requiredJIT = RequiredWasmJIT::Any) { if (requiredJIT == RequiredWasmJIT::OMG) { if (!Options::useOMGJIT()) return false; } else { if (Options::wasmLLIntTiersUpToBBQ() && !Options::useBBQJIT()) return false; if (!Options::wasmLLIntTiersUpToBBQ() && !Options::useOMGJIT()) return false; } if (!Options::wasmFunctionIndexRangeToCompile().isInRange(codeBlock->functionIndex())) return false; return true; } inline bool jitCompileAndSetHeuristics(Wasm::LLIntCallee* callee, Wasm::FunctionCodeBlock* codeBlock, Wasm::Instance* instance) { Wasm::LLIntTierUpCounter& tierUpCounter = codeBlock->tierUpCounter(); if (!tierUpCounter.checkIfOptimizationThresholdReached()) { dataLogLnIf(Options::verboseOSR(), " JIT threshold should be lifted."); return false; } if (callee->replacement()) { dataLogLnIf(Options::verboseOSR(), " Code was already compiled."); tierUpCounter.optimizeSoon(); return true; } bool compile = false; { auto locker = holdLock(tierUpCounter.m_lock); switch (tierUpCounter.m_compilationStatus) { case Wasm::LLIntTierUpCounter::CompilationStatus::NotCompiled: compile = true; tierUpCounter.m_compilationStatus = Wasm::LLIntTierUpCounter::CompilationStatus::Compiling; break; case Wasm::LLIntTierUpCounter::CompilationStatus::Compiling: tierUpCounter.optimizeAfterWarmUp(); break; case Wasm::LLIntTierUpCounter::CompilationStatus::Compiled: break; } } if (compile) { uint32_t functionIndex = codeBlock->functionIndex(); RefPtr plan; if (Options::wasmLLIntTiersUpToBBQ()) plan = adoptRef(*new Wasm::BBQPlan(instance->context(), makeRef(const_cast(instance->module().moduleInformation())), functionIndex, instance->codeBlock(), Wasm::Plan::dontFinalize())); else plan = adoptRef(*new Wasm::OMGPlan(instance->context(), Ref(instance->module()), functionIndex, instance->memory()->mode(), Wasm::Plan::dontFinalize())); Wasm::ensureWorklist().enqueue(makeRef(*plan)); if (UNLIKELY(!Options::useConcurrentJIT())) plan->waitForCompletion(); else tierUpCounter.optimizeAfterWarmUp(); } return !!callee->replacement(); } WASM_SLOW_PATH_DECL(prologue_osr) { UNUSED_PARAM(pc); Wasm::LLIntCallee* callee = static_cast(callFrame->callee().asWasmCallee()); Wasm::FunctionCodeBlock* codeBlock = CODE_BLOCK(); if (!shouldJIT(codeBlock)) { codeBlock->tierUpCounter().deferIndefinitely(); WASM_RETURN_TWO(nullptr, nullptr); } if (!Options::useWasmLLIntPrologueOSR()) WASM_RETURN_TWO(nullptr, nullptr); dataLogLnIf(Options::verboseOSR(), *callee, ": Entered prologue_osr with tierUpCounter = ", codeBlock->tierUpCounter()); if (!jitCompileAndSetHeuristics(callee, codeBlock, instance)) WASM_RETURN_TWO(nullptr, nullptr); WASM_RETURN_TWO(callee->replacement()->entrypoint().executableAddress(), nullptr); } WASM_SLOW_PATH_DECL(loop_osr) { Wasm::LLIntCallee* callee = static_cast(callFrame->callee().asWasmCallee()); Wasm::FunctionCodeBlock* codeBlock = CODE_BLOCK(); Wasm::LLIntTierUpCounter& tierUpCounter = codeBlock->tierUpCounter(); if (!Options::useWebAssemblyOSR() || !Options::useWasmLLIntLoopOSR() || !shouldJIT(codeBlock, RequiredWasmJIT::OMG)) { slow_path_wasm_prologue_osr(callFrame, pc, instance); WASM_RETURN_TWO(nullptr, nullptr); } dataLogLnIf(Options::verboseOSR(), *callee, ": Entered loop_osr with tierUpCounter = ", codeBlock->tierUpCounter()); unsigned loopOSREntryBytecodeOffset = codeBlock->bytecodeOffset(pc); const auto& osrEntryData = tierUpCounter.osrEntryDataForLoop(loopOSREntryBytecodeOffset); if (!tierUpCounter.checkIfOptimizationThresholdReached()) { dataLogLnIf(Options::verboseOSR(), " JIT threshold should be lifted."); WASM_RETURN_TWO(nullptr, nullptr); } const auto doOSREntry = [&] { Wasm::OMGForOSREntryCallee* osrEntryCallee = callee->osrEntryCallee(); if (osrEntryCallee->loopIndex() != osrEntryData.loopIndex) WASM_RETURN_TWO(nullptr, nullptr); size_t osrEntryScratchBufferSize = osrEntryCallee->osrEntryScratchBufferSize(); RELEASE_ASSERT(osrEntryScratchBufferSize == osrEntryData.values.size()); uint64_t* buffer = instance->context()->scratchBufferForSize(osrEntryScratchBufferSize); if (!buffer) WASM_RETURN_TWO(nullptr, nullptr); uint32_t index = 0; for (VirtualRegister reg : osrEntryData.values) buffer[index++] = READ(reg).encodedJSValue(); WASM_RETURN_TWO(buffer, osrEntryCallee->entrypoint().executableAddress()); }; if (callee->osrEntryCallee()) return doOSREntry(); bool compile = false; { auto locker = holdLock(tierUpCounter.m_lock); switch (tierUpCounter.m_loopCompilationStatus) { case Wasm::LLIntTierUpCounter::CompilationStatus::NotCompiled: compile = true; tierUpCounter.m_loopCompilationStatus = Wasm::LLIntTierUpCounter::CompilationStatus::Compiling; break; case Wasm::LLIntTierUpCounter::CompilationStatus::Compiling: tierUpCounter.optimizeAfterWarmUp(); break; case Wasm::LLIntTierUpCounter::CompilationStatus::Compiled: break; } } if (compile) { Ref plan = adoptRef(*static_cast(new Wasm::OMGForOSREntryPlan(instance->context(), Ref(instance->module()), Ref(*callee), codeBlock->functionIndex(), osrEntryData.loopIndex, instance->memory()->mode(), Wasm::Plan::dontFinalize()))); Wasm::ensureWorklist().enqueue(plan.copyRef()); if (UNLIKELY(!Options::useConcurrentJIT())) plan->waitForCompletion(); else tierUpCounter.optimizeAfterWarmUp(); } if (callee->osrEntryCallee()) return doOSREntry(); WASM_RETURN_TWO(nullptr, nullptr); } WASM_SLOW_PATH_DECL(epilogue_osr) { Wasm::LLIntCallee* callee = static_cast(callFrame->callee().asWasmCallee()); Wasm::FunctionCodeBlock* codeBlock = CODE_BLOCK(); if (!shouldJIT(codeBlock)) { codeBlock->tierUpCounter().deferIndefinitely(); WASM_END_IMPL(); } if (!Options::useWasmLLIntEpilogueOSR()) WASM_END_IMPL(); dataLogLnIf(Options::verboseOSR(), *callee, ": Entered epilogue_osr with tierUpCounter = ", codeBlock->tierUpCounter()); jitCompileAndSetHeuristics(callee, codeBlock, instance); WASM_END_IMPL(); } WASM_SLOW_PATH_DECL(trace) { UNUSED_PARAM(instance); if (!Options::traceLLIntExecution()) WASM_END_IMPL(); WasmOpcodeID opcodeID = pc->opcodeID(); dataLogF("<%p> %p / %p: executing bc#%zu, %s, pc = %p\n", &Thread::current(), callFrame->codeBlock(), callFrame, static_cast(CODE_BLOCK()->bytecodeOffset(pc)), pc->name(), pc); if (opcodeID == wasm_enter) { dataLogF("Frame will eventually return to %p\n", callFrame->returnPC().value()); *removeCodePtrTag(callFrame->returnPC().value()); } if (opcodeID == wasm_ret) { dataLogF("Will be returning to %p\n", callFrame->returnPC().value()); dataLogF("The new cfr will be %p\n", callFrame->callerFrame()); } WASM_END_IMPL(); } WASM_SLOW_PATH_DECL(out_of_line_jump_target) { UNUSED_PARAM(instance); pc = CODE_BLOCK()->outOfLineJumpTarget(pc); WASM_END_IMPL(); } WASM_SLOW_PATH_DECL(ref_func) { auto instruction = pc->as(); WASM_RETURN(Wasm::operationWasmRefFunc(instance, instruction.m_functionIndex)); } WASM_SLOW_PATH_DECL(table_get) { auto instruction = pc->as(); int32_t index = READ(instruction.m_index).unboxedInt32(); EncodedJSValue result = Wasm::operationGetWasmTableElement(instance, instruction.m_tableIndex, index); if (!result) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_RETURN(result); } WASM_SLOW_PATH_DECL(table_set) { auto instruction = pc->as(); uint32_t index = READ(instruction.m_index).unboxedUInt32(); EncodedJSValue value = READ(instruction.m_value).encodedJSValue(); if (!Wasm::operationSetWasmTableElement(instance, instruction.m_tableIndex, index, value)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_END(); } WASM_SLOW_PATH_DECL(table_init) { auto instruction = pc->as(); uint32_t dstOffset = READ(instruction.m_dstOffset).unboxedUInt32(); uint32_t srcOffset = READ(instruction.m_srcOffset).unboxedUInt32(); uint32_t length = READ(instruction.m_length).unboxedUInt32(); if (!Wasm::operationWasmTableInit(instance, instruction.m_elementIndex, instruction.m_tableIndex, dstOffset, srcOffset, length)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_END(); } WASM_SLOW_PATH_DECL(elem_drop) { UNUSED_PARAM(callFrame); auto instruction = pc->as(); Wasm::operationWasmElemDrop(instance, instruction.m_elementIndex); WASM_END(); } WASM_SLOW_PATH_DECL(table_size) { auto instruction = pc->as(); WASM_RETURN(Wasm::operationGetWasmTableSize(instance, instruction.m_tableIndex)); } WASM_SLOW_PATH_DECL(table_fill) { auto instruction = pc->as(); uint32_t offset = READ(instruction.m_offset).unboxedUInt32(); EncodedJSValue fill = READ(instruction.m_fill).encodedJSValue(); uint32_t size = READ(instruction.m_size).unboxedUInt32(); if (!Wasm::operationWasmTableFill(instance, instruction.m_tableIndex, offset, fill, size)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_END(); } WASM_SLOW_PATH_DECL(table_copy) { auto instruction = pc->as(); int32_t dstOffset = READ(instruction.m_dstOffset).unboxedInt32(); int32_t srcOffset = READ(instruction.m_srcOffset).unboxedInt32(); int32_t length = READ(instruction.m_length).unboxedInt32(); if (!Wasm::operationWasmTableCopy(instance, instruction.m_dstTableIndex, instruction.m_srcTableIndex, dstOffset, srcOffset, length)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_END(); } WASM_SLOW_PATH_DECL(table_grow) { auto instruction = pc->as(); EncodedJSValue fill = READ(instruction.m_fill).encodedJSValue(); uint32_t size = READ(instruction.m_size).unboxedUInt32(); WASM_RETURN(Wasm::operationWasmTableGrow(instance, instruction.m_tableIndex, fill, size)); } WASM_SLOW_PATH_DECL(grow_memory) { auto instruction = pc->as(); int32_t delta = READ(instruction.m_delta).unboxedInt32(); WASM_RETURN(Wasm::operationGrowMemory(callFrame, instance, delta)); } WASM_SLOW_PATH_DECL(memory_fill) { auto instruction = pc->as(); uint32_t dstAddress = READ(instruction.m_dstAddress).unboxedUInt32(); uint32_t targetValue = READ(instruction.m_targetValue).unboxedUInt32(); uint32_t count = READ(instruction.m_count).unboxedUInt32(); if (!Wasm::operationWasmMemoryFill(instance, dstAddress, targetValue, count)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsTableAccess); WASM_END(); } WASM_SLOW_PATH_DECL(memory_copy) { auto instruction = pc->as(); uint32_t dstAddress = READ(instruction.m_dstAddress).unboxedUInt32(); uint32_t srcAddress = READ(instruction.m_srcAddress).unboxedUInt32(); uint32_t count = READ(instruction.m_count).unboxedUInt32(); if (!Wasm::operationWasmMemoryCopy(instance, dstAddress, srcAddress, count)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess); WASM_END(); } WASM_SLOW_PATH_DECL(memory_init) { auto instruction = pc->as(); uint32_t dstAddress = READ(instruction.m_dstAddress).unboxedUInt32(); uint32_t srcAddress = READ(instruction.m_srcAddress).unboxedUInt32(); uint32_t length = READ(instruction.m_length).unboxedUInt32(); if (!Wasm::operationWasmMemoryInit(instance, instruction.m_dataSegmentIndex, dstAddress, srcAddress, length)) WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess); WASM_END(); } WASM_SLOW_PATH_DECL(data_drop) { UNUSED_PARAM(callFrame); auto instruction = pc->as(); Wasm::operationWasmDataDrop(instance, instruction.m_dataSegmentIndex); WASM_END(); } inline SlowPathReturnType doWasmCall(Wasm::Instance* instance, unsigned functionIndex) { uint32_t importFunctionCount = instance->module().moduleInformation().importFunctionCount(); MacroAssemblerCodePtr codePtr; if (functionIndex < importFunctionCount) { Wasm::Instance::ImportFunctionInfo* functionInfo = instance->importFunctionInfo(functionIndex); if (functionInfo->targetInstance) { // target is a wasm function from a different instance codePtr = instance->codeBlock()->wasmToWasmExitStub(functionIndex); } else { // target is JS codePtr = functionInfo->wasmToEmbedderStub; } } else { // Target is a wasm function within the same instance codePtr = *instance->codeBlock()->entrypointLoadLocationFromFunctionIndexSpace(functionIndex); } WASM_CALL_RETURN(instance, codePtr.executableAddress(), WasmEntryPtrTag); } WASM_SLOW_PATH_DECL(call) { UNUSED_PARAM(callFrame); auto instruction = pc->as(); return doWasmCall(instance, instruction.m_functionIndex); } WASM_SLOW_PATH_DECL(call_no_tls) { UNUSED_PARAM(callFrame); auto instruction = pc->as(); return doWasmCall(instance, instruction.m_functionIndex); } inline SlowPathReturnType doWasmCallIndirect(CallFrame* callFrame, Wasm::Instance* instance, unsigned functionIndex, unsigned tableIndex, unsigned signatureIndex) { Wasm::FuncRefTable* table = instance->table(tableIndex)->asFuncrefTable(); if (functionIndex >= table->length()) WASM_THROW(Wasm::ExceptionType::OutOfBoundsCallIndirect); Wasm::Instance* targetInstance = table->instance(functionIndex); const Wasm::WasmToWasmImportableFunction& function = table->function(functionIndex); if (function.signatureIndex == Wasm::Signature::invalidIndex) WASM_THROW(Wasm::ExceptionType::NullTableEntry); const Wasm::Signature& callSignature = CODE_BLOCK()->signature(signatureIndex); if (function.signatureIndex != Wasm::SignatureInformation::get(callSignature)) WASM_THROW(Wasm::ExceptionType::BadSignature); if (targetInstance != instance) targetInstance->setCachedStackLimit(instance->cachedStackLimit()); WASM_CALL_RETURN(targetInstance, function.entrypointLoadLocation->executableAddress(), WasmEntryPtrTag); } WASM_SLOW_PATH_DECL(call_indirect) { auto instruction = pc->as(); unsigned functionIndex = READ(instruction.m_functionIndex).unboxedInt32(); return doWasmCallIndirect(callFrame, instance, functionIndex, instruction.m_tableIndex, instruction.m_signatureIndex); } WASM_SLOW_PATH_DECL(call_indirect_no_tls) { auto instruction = pc->as(); unsigned functionIndex = READ(instruction.m_functionIndex).unboxedInt32(); return doWasmCallIndirect(callFrame, instance, functionIndex, instruction.m_tableIndex, instruction.m_signatureIndex); } WASM_SLOW_PATH_DECL(set_global_ref) { auto instruction = pc->as(); instance->setGlobal(instruction.m_globalIndex, READ(instruction.m_value).jsValue()); WASM_END_IMPL(); } WASM_SLOW_PATH_DECL(set_global_ref_portable_binding) { auto instruction = pc->as(); instance->setGlobal(instruction.m_globalIndex, READ(instruction.m_value).jsValue()); WASM_END_IMPL(); } WASM_SLOW_PATH_DECL(memory_atomic_wait32) { auto instruction = pc->as(); unsigned base = READ(instruction.m_pointer).unboxedInt32(); unsigned offset = instruction.m_offset; uint32_t value = READ(instruction.m_value).unboxedInt32(); int64_t timeout = READ(instruction.m_timeout).unboxedInt64(); int32_t result = Wasm::operationMemoryAtomicWait32(instance, base, offset, value, timeout); if (result < 0) WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess); WASM_RETURN(result); } WASM_SLOW_PATH_DECL(memory_atomic_wait64) { auto instruction = pc->as(); unsigned base = READ(instruction.m_pointer).unboxedInt32(); unsigned offset = instruction.m_offset; uint64_t value = READ(instruction.m_value).unboxedInt64(); int64_t timeout = READ(instruction.m_timeout).unboxedInt64(); int32_t result = Wasm::operationMemoryAtomicWait64(instance, base, offset, value, timeout); if (result < 0) WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess); WASM_RETURN(result); } WASM_SLOW_PATH_DECL(memory_atomic_notify) { auto instruction = pc->as(); unsigned base = READ(instruction.m_pointer).unboxedInt32(); unsigned offset = instruction.m_offset; int32_t count = READ(instruction.m_count).unboxedInt32(); int32_t result = Wasm::operationMemoryAtomicNotify(instance, base, offset, count); if (result < 0) WASM_THROW(Wasm::ExceptionType::OutOfBoundsMemoryAccess); WASM_RETURN(result); } extern "C" SlowPathReturnType slow_path_wasm_throw_exception(CallFrame* callFrame, const Instruction* pc, Wasm::Instance* instance, Wasm::ExceptionType exceptionType) { UNUSED_PARAM(pc); WASM_RETURN_TWO(operationWasmToJSException(callFrame, exceptionType, instance), nullptr); } extern "C" SlowPathReturnType slow_path_wasm_popcount(const Instruction* pc, uint32_t x) { void* result = bitwise_cast(static_cast(__builtin_popcount(x))); WASM_RETURN_TWO(pc, result); } extern "C" SlowPathReturnType slow_path_wasm_popcountll(const Instruction* pc, uint64_t x) { void* result = bitwise_cast(static_cast(__builtin_popcountll(x))); WASM_RETURN_TWO(pc, result); } } } // namespace JSC::LLInt #endif // ENABLE(WEBASSEMBLY)