/* * Copyright (C) 2011 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 "DFGJITCompiler.h" #if ENABLE(DFG_JIT) #include "CodeBlock.h" #include "DFGJITCodeGenerator.h" #include "DFGNonSpeculativeJIT.h" #include "DFGOperations.h" #include "DFGRegisterBank.h" #include "DFGSpeculativeJIT.h" #include "JSGlobalData.h" #include "LinkBuffer.h" namespace JSC { namespace DFG { // This method used to fill a numeric value to a FPR when linking speculative -> non-speculative. void JITCompiler::fillNumericToDouble(NodeIndex nodeIndex, FPRReg fpr, GPRReg temporary) { Node& node = graph()[nodeIndex]; MacroAssembler::RegisterID tempReg = gprToRegisterID(temporary); if (node.isConstant()) { ASSERT(node.op == DoubleConstant); move(MacroAssembler::ImmPtr(reinterpret_cast(reinterpretDoubleToIntptr(valueOfDoubleConstant(nodeIndex)))), tempReg); movePtrToDouble(tempReg, fprToRegisterID(fpr)); } else { loadPtr(addressFor(node.virtualRegister), tempReg); Jump isInteger = branchPtr(MacroAssembler::AboveOrEqual, tempReg, tagTypeNumberRegister); jitAssertIsJSDouble(gpr0); addPtr(tagTypeNumberRegister, tempReg); movePtrToDouble(tempReg, fprToRegisterID(fpr)); Jump hasUnboxedDouble = jump(); isInteger.link(this); convertInt32ToDouble(tempReg, fprToRegisterID(fpr)); hasUnboxedDouble.link(this); } } // This method used to fill an integer value to a GPR when linking speculative -> non-speculative. void JITCompiler::fillInt32ToInteger(NodeIndex nodeIndex, GPRReg gpr) { Node& node = graph()[nodeIndex]; if (node.isConstant()) { ASSERT(node.op == Int32Constant); move(MacroAssembler::Imm32(valueOfInt32Constant(nodeIndex)), gprToRegisterID(gpr)); } else { #if DFG_JIT_ASSERT // Redundant load, just so we can check the tag! loadPtr(addressFor(node.virtualRegister), gprToRegisterID(gpr)); jitAssertIsJSInt32(gpr); #endif load32(addressFor(node.virtualRegister), gprToRegisterID(gpr)); } } // This method used to fill a JSValue to a GPR when linking speculative -> non-speculative. void JITCompiler::fillToJS(NodeIndex nodeIndex, GPRReg gpr) { Node& node = graph()[nodeIndex]; if (node.isConstant()) { if (isInt32Constant(nodeIndex)) { JSValue jsValue = jsNumber(valueOfInt32Constant(nodeIndex)); move(MacroAssembler::ImmPtr(JSValue::encode(jsValue)), gprToRegisterID(gpr)); } else if (isDoubleConstant(nodeIndex)) { JSValue jsValue(JSValue::EncodeAsDouble, valueOfDoubleConstant(nodeIndex)); move(MacroAssembler::ImmPtr(JSValue::encode(jsValue)), gprToRegisterID(gpr)); } else { ASSERT(isJSConstant(nodeIndex)); JSValue jsValue = valueOfJSConstant(nodeIndex); move(MacroAssembler::ImmPtr(JSValue::encode(jsValue)), gprToRegisterID(gpr)); } return; } loadPtr(addressFor(node.virtualRegister), gprToRegisterID(gpr)); } void JITCompiler::jumpFromSpeculativeToNonSpeculative(const SpeculationCheck& check, const EntryLocation& entry, SpeculationRecovery* recovery) { ASSERT(check.m_nodeIndex == entry.m_nodeIndex); // Link the jump from the Speculative path to here. check.m_check.link(this); // Does this speculation check require any additional recovery to be performed, // to restore any state that has been overwritten before we enter back in to the // non-speculative path. if (recovery) { // The only additional recovery we currently support is for integer add operation ASSERT(recovery->type() == SpeculativeAdd); // Revert the add. sub32(gprToRegisterID(recovery->src()), gprToRegisterID(recovery->dest())); } // FIXME: - This is hideously inefficient! // Where a value is live in a register in the speculative path, and is required in a register // on the non-speculative path, we should not need to be spilling it and reloading (we may // need to spill anyway, if the value is marked as spilled on the non-speculative path). // This may also be spilling values that don't need spilling, e.g. are already spilled, // are constants, or are arguments. // Spill all GPRs in use by the speculative path. for (GPRReg gpr = gpr0; gpr < numberOfGPRs; next(gpr)) { NodeIndex nodeIndex = check.m_gprInfo[gpr].nodeIndex; if (nodeIndex == NoNode) continue; DataFormat dataFormat = check.m_gprInfo[gpr].format; VirtualRegister virtualRegister = graph()[nodeIndex].virtualRegister; ASSERT(dataFormat == DataFormatInteger || DataFormatCell || dataFormat & DataFormatJS); if (dataFormat == DataFormatInteger) orPtr(tagTypeNumberRegister, gprToRegisterID(gpr)); storePtr(gprToRegisterID(gpr), addressFor(virtualRegister)); } // Spill all FPRs in use by the speculative path. for (FPRReg fpr = fpr0; fpr < numberOfFPRs; next(fpr)) { NodeIndex nodeIndex = check.m_fprInfo[fpr]; if (nodeIndex == NoNode) continue; VirtualRegister virtualRegister = graph()[nodeIndex].virtualRegister; moveDoubleToPtr(fprToRegisterID(fpr), regT0); subPtr(tagTypeNumberRegister, regT0); storePtr(regT0, addressFor(virtualRegister)); } // Fill all FPRs in use by the non-speculative path. for (FPRReg fpr = fpr0; fpr < numberOfFPRs; next(fpr)) { NodeIndex nodeIndex = entry.m_fprInfo[fpr]; if (nodeIndex == NoNode) continue; fillNumericToDouble(nodeIndex, fpr, gpr0); } // Fill all GPRs in use by the non-speculative path. for (GPRReg gpr = gpr0; gpr < numberOfGPRs; next(gpr)) { NodeIndex nodeIndex = entry.m_gprInfo[gpr].nodeIndex; if (nodeIndex == NoNode) continue; DataFormat dataFormat = entry.m_gprInfo[gpr].format; if (dataFormat == DataFormatInteger) fillInt32ToInteger(nodeIndex, gpr); else { ASSERT(dataFormat & DataFormatJS || dataFormat == DataFormatCell); // Treat cell as JSValue for now! fillToJS(nodeIndex, gpr); // FIXME: For subtypes of DataFormatJS, should jitAssert the subtype? } } // Jump into the non-speculative path. jump(entry.m_entry); } void JITCompiler::linkSpeculationChecks(SpeculativeJIT& speculative, NonSpeculativeJIT& nonSpeculative) { // Iterators to walk over the set of bail outs & corresponding entry points. SpeculationCheckVector::Iterator checksIter = speculative.speculationChecks().begin(); SpeculationCheckVector::Iterator checksEnd = speculative.speculationChecks().end(); NonSpeculativeJIT::EntryLocationVector::Iterator entriesIter = nonSpeculative.entryLocations().begin(); NonSpeculativeJIT::EntryLocationVector::Iterator entriesEnd = nonSpeculative.entryLocations().end(); // Iterate over the speculation checks. while (checksIter != checksEnd) { // For every bail out from the speculative path, we must have provided an entry point // into the non-speculative one. ASSERT(checksIter->m_nodeIndex == entriesIter->m_nodeIndex); // There may be multiple bail outs that map to the same entry point! do { ASSERT(checksIter != checksEnd); ASSERT(entriesIter != entriesEnd); // Plant code to link this speculation failure. const SpeculationCheck& check = *checksIter; const EntryLocation& entry = *entriesIter; jumpFromSpeculativeToNonSpeculative(check, entry, speculative.speculationRecovery(check.m_recoveryIndex)); ++checksIter; } while (checksIter != checksEnd && checksIter->m_nodeIndex == entriesIter->m_nodeIndex); ++entriesIter; } // FIXME: https://bugs.webkit.org/show_bug.cgi?id=56289 ASSERT(!(checksIter != checksEnd)); ASSERT(!(entriesIter != entriesEnd)); } void JITCompiler::compileFunction(JITCode& entry, MacroAssemblerCodePtr& entryWithArityCheck) { // === Stage 1 - Function header code generation === // // This code currently matches the old JIT. In the function header we need to // pop the return address (since we do not allow any recursion on the machine // stack), and perform a fast register file check. // This is the main entry point, without performing an arity check. // FIXME: https://bugs.webkit.org/show_bug.cgi?id=56292 // We'll need to convert the remaining cti_ style calls (specifically the register file // check) which will be dependent on stack layout. (We'd need to account for this in // both normal return code and when jumping to an exception handler). preserveReturnAddressAfterCall(regT2); emitPutToCallFrameHeader(regT2, RegisterFile::ReturnPC); // If we needed to perform an arity check we will already have moved the return address, // so enter after this. Label fromArityCheck(this); // Setup a pointer to the codeblock in the CallFrameHeader. emitPutImmediateToCallFrameHeader(m_codeBlock, RegisterFile::CodeBlock); // Plant a check that sufficient space is available in the RegisterFile. // FIXME: https://bugs.webkit.org/show_bug.cgi?id=56291 addPtr(Imm32(m_codeBlock->m_numCalleeRegisters * sizeof(Register)), callFrameRegister, regT1); Jump registerFileCheck = branchPtr(Below, AbsoluteAddress(m_globalData->interpreter->registerFile().addressOfEnd()), regT1); // Return here after register file check. Label fromRegisterFileCheck = label(); // === Stage 2 - Function body code generation === // // We generate the speculative code path, followed by the non-speculative // code for the function. Next we need to link the two together, making // bail-outs from the speculative path jump to the corresponding point on // the non-speculative one (and generating any code necessary to juggle // register values around, rebox values, and ensure spilled, to match the // non-speculative path's requirements). #if DFG_JIT_BREAK_ON_EVERY_FUNCTION // Handy debug tool! breakpoint(); #endif // First generate the speculative path. Label speculativePathBegin = label(); SpeculativeJIT speculative(*this); bool compiledSpeculative = speculative.compile(); // Next, generate the non-speculative path. We pass this a SpeculationCheckIndexIterator // to allow it to check which nodes in the graph may bail out, and may need to reenter the // non-speculative path. if (compiledSpeculative) { SpeculationCheckIndexIterator checkIterator(speculative.speculationChecks()); NonSpeculativeJIT nonSpeculative(*this); nonSpeculative.compile(checkIterator); // Link the bail-outs from the speculative path to the corresponding entry points into the non-speculative one. linkSpeculationChecks(speculative, nonSpeculative); } else { // If compilation through the SpeculativeJIT failed, throw away the code we generated. m_calls.clear(); rewindToLabel(speculativePathBegin); SpeculationCheckVector noChecks; SpeculationCheckIndexIterator checkIterator(noChecks); NonSpeculativeJIT nonSpeculative(*this); nonSpeculative.compile(checkIterator); } // === Stage 3 - Function footer code generation === // // Generate code to lookup and jump to exception handlers, to perform the slow // register file check (if the fast one in the function header fails), and // generate the entry point with arity check. // Iterate over the m_calls vector, checking for exception checks, // and linking them to here. unsigned exceptionCheckCount = 0; for (unsigned i = 0; i < m_calls.size(); ++i) { Jump& exceptionCheck = m_calls[i].m_exceptionCheck; if (exceptionCheck.isSet()) { exceptionCheck.link(this); ++exceptionCheckCount; } } // If any exception checks were linked, generate code to lookup a handler. if (exceptionCheckCount) { // lookupExceptionHandler is passed two arguments, exec (the CallFrame*), and // an identifier for the operation that threw the exception, which we can use // to look up handler information. The identifier we use is the return address // of the call out from JIT code that threw the exception; this is still // available on the stack, just below the stack pointer! move(callFrameRegister, argumentRegister0); peek(argumentRegister1, -1); m_calls.append(CallRecord(call(), lookupExceptionHandler)); // lookupExceptionHandler leaves the handler CallFrame* in the returnValueRegister, // and the address of the handler in returnValueRegister2. jump(returnValueRegister2); } // Generate the register file check; if the fast check in the function head fails, // we need to call out to a helper function to check whether more space is available. // FIXME: change this from a cti call to a DFG style operation (normal C calling conventions). registerFileCheck.link(this); move(stackPointerRegister, argumentRegister0); poke(callFrameRegister, OBJECT_OFFSETOF(struct JITStackFrame, callFrame) / sizeof(void*)); Call callRegisterFileCheck = call(); jump(fromRegisterFileCheck); // The fast entry point into a function does not check the correct number of arguments // have been passed to the call (we only use the fast entry point where we can statically // determine the correct number of arguments have been passed, or have already checked). // In cases where an arity check is necessary, we enter here. // FIXME: change this from a cti call to a DFG style operation (normal C calling conventions). Label arityCheck = label(); preserveReturnAddressAfterCall(regT2); emitPutToCallFrameHeader(regT2, RegisterFile::ReturnPC); branch32(Equal, regT1, Imm32(m_codeBlock->m_numParameters)).linkTo(fromArityCheck, this); move(stackPointerRegister, argumentRegister0); poke(callFrameRegister, OBJECT_OFFSETOF(struct JITStackFrame, callFrame) / sizeof(void*)); Call callArityCheck = call(); move(regT0, callFrameRegister); jump(fromArityCheck); // === Stage 4 - Link === // // Link the code, populate data in CodeBlock data structures. LinkBuffer linkBuffer(this, m_globalData->executableAllocator.poolForSize(m_assembler.size()), 0); #if DFG_DEBUG_VERBOSE fprintf(stderr, "JIT code start at %p\n", linkBuffer.debugAddress()); #endif // Link all calls out from the JIT code to their respective functions. for (unsigned i = 0; i < m_calls.size(); ++i) linkBuffer.link(m_calls[i].m_call, m_calls[i].m_function); if (m_codeBlock->needsCallReturnIndices()) { m_codeBlock->callReturnIndexVector().reserveCapacity(exceptionCheckCount); for (unsigned i = 0; i < m_calls.size(); ++i) { if (m_calls[i].m_exceptionCheck.isSet()) { unsigned returnAddressOffset = linkBuffer.returnAddressOffset(m_calls[i].m_call); unsigned exceptionInfo = m_calls[i].m_exceptionInfo; m_codeBlock->callReturnIndexVector().append(CallReturnOffsetToBytecodeOffset(returnAddressOffset, exceptionInfo)); } } } // FIXME: switch the register file check & arity check over to DFGOpertaion style calls, not JIT stubs. linkBuffer.link(callRegisterFileCheck, cti_register_file_check); linkBuffer.link(callArityCheck, m_codeBlock->m_isConstructor ? cti_op_construct_arityCheck : cti_op_call_arityCheck); entryWithArityCheck = linkBuffer.locationOf(arityCheck); entry = linkBuffer.finalizeCode(); } #if DFG_JIT_ASSERT void JITCompiler::jitAssertIsInt32(GPRReg gpr) { #if CPU(X86_64) Jump checkInt32 = branchPtr(BelowOrEqual, gprToRegisterID(gpr), TrustedImmPtr(reinterpret_cast(static_cast(0xFFFFFFFFu)))); breakpoint(); checkInt32.link(this); #else UNUSED_PARAM(gpr); #endif } void JITCompiler::jitAssertIsJSInt32(GPRReg gpr) { Jump checkJSInt32 = branchPtr(AboveOrEqual, gprToRegisterID(gpr), tagTypeNumberRegister); breakpoint(); checkJSInt32.link(this); } void JITCompiler::jitAssertIsJSNumber(GPRReg gpr) { Jump checkJSNumber = branchTestPtr(MacroAssembler::NonZero, gprToRegisterID(gpr), tagTypeNumberRegister); breakpoint(); checkJSNumber.link(this); } void JITCompiler::jitAssertIsJSDouble(GPRReg gpr) { Jump checkJSInt32 = branchPtr(AboveOrEqual, gprToRegisterID(gpr), tagTypeNumberRegister); Jump checkJSNumber = branchTestPtr(MacroAssembler::NonZero, gprToRegisterID(gpr), tagTypeNumberRegister); checkJSInt32.link(this); breakpoint(); checkJSNumber.link(this); } #endif #if ENABLE(SAMPLING_COUNTERS) && CPU(X86_64) // Or any other 64-bit platform! void JITCompiler::emitCount(AbstractSamplingCounter& counter, uint32_t increment) { addPtr(TrustedImm32(increment), AbsoluteAddress(counter.addressOfCounter())); } #endif #if ENABLE(SAMPLING_COUNTERS) && CPU(X86) // Or any other little-endian 32-bit platform! void JITCompiler::emitCount(AbstractSamplingCounter& counter, uint32_t increment) { intptr_t hiWord = reinterpret_cast(counter.addressOfCounter()) + sizeof(int32_t); add32(TrustedImm32(increment), AbsoluteAddress(counter.addressOfCounter())); addWithCarry32(TrustedImm32(0), AbsoluteAddress(reinterpret_cast(hiWord))); } #endif } } // namespace JSC::DFG #endif