mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
381 lines
11 KiB
C++
381 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
// NativeCodeCache
|
|
//
|
|
// Scott M. Silver
|
|
//
|
|
|
|
#include "NativeCodeCache.h"
|
|
#include "StringUtils.h"
|
|
#include "ClassWorld.h"
|
|
#include "FieldOrMethod.h"
|
|
#include "NativeMethodDispatcher.h"
|
|
#include "JavaVM.h"
|
|
|
|
UT_DEFINE_LOG_MODULE(NativeCodeCache);
|
|
|
|
#ifdef _WIN32
|
|
Uint8* CacheEntry::asynchVersion(Pool& pool, Uint8*& ip)
|
|
{
|
|
Uint8* src = start.getFunctionAddress();
|
|
Int32 sz = end-src;
|
|
Uint8* dest = new(pool) Uint8[sz];
|
|
Uint32* asynchPoints = eTable->getAsynchPoints();
|
|
int numberofAPoints = eTable->getNrOfAsynchPoints();
|
|
// loop through code and for each byte corresponding to an asynchronous
|
|
// checkpoint, insert 'int 3' (0xCC).
|
|
memcpy((void*)dest,(const void*)src,sz);
|
|
for (int j=0; j<numberofAPoints; j++)
|
|
dest[asynchPoints[j]] = 0xcc;
|
|
ip = dest + (ip-src);
|
|
return dest;
|
|
}
|
|
#else
|
|
Uint8* CacheEntry::asynchVersion(Pool& /*pool*/, Uint8*& /*ip*/)
|
|
{
|
|
assert(false);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
// there is one of these per VM instantiation
|
|
NativeCodeCache NativeCodeCache::sNativeCodeCache;
|
|
|
|
// Access to the database of classes
|
|
extern ClassWorld world;
|
|
|
|
#ifdef DEBUG_LOG
|
|
static void printFrame(Method *method);
|
|
#endif
|
|
|
|
// acquireMemory
|
|
//
|
|
// Reserve inBytes of data for (potentially) a new method, or data
|
|
Uint8* NativeCodeCache::
|
|
acquireMemory(const size_t inBytes)
|
|
{
|
|
// is Pool new thread safe?
|
|
return ((Uint8*) new(mPool) char[inBytes]);
|
|
}
|
|
|
|
|
|
// mapMemoryToMethod
|
|
//
|
|
// Assign a "name" to a method. When other users ask the cache
|
|
// for a given method, this is the entry point returned. The
|
|
// first call for a specific MethodDescriptor M and function f,
|
|
// will assign f to M.
|
|
//
|
|
// FIX-ME do all HashTable entrypoints need monitors?
|
|
void NativeCodeCache::
|
|
mapMemoryToMethod( const MethodDescriptor& inMethodDescriptor,
|
|
PlatformTVector inWhere,
|
|
Uint8* inEnd,
|
|
ExceptionTable* pExceptionTable,
|
|
StackFrameInfo& inPolicy)
|
|
{
|
|
CacheEntry* existingEntry = NULL;
|
|
TemporaryStringCopy key(inMethodDescriptor.method->toString());
|
|
|
|
mIndexByDescriptor.get(key, &existingEntry);
|
|
|
|
if (!existingEntry)
|
|
{
|
|
CacheEntry& cacheEntry = *new(mPool) CacheEntry(inMethodDescriptor);
|
|
cacheEntry.start = inWhere;
|
|
cacheEntry.end = inEnd;
|
|
cacheEntry.eTable = pExceptionTable;
|
|
cacheEntry.policy = inPolicy;
|
|
|
|
mIndexByDescriptor.add(key, &cacheEntry);
|
|
mIndexByRange.append(&cacheEntry);
|
|
}
|
|
else
|
|
{
|
|
existingEntry->start = inWhere;
|
|
existingEntry->end = inEnd;
|
|
existingEntry->eTable = pExceptionTable;
|
|
existingEntry->policy = inPolicy;
|
|
}
|
|
}
|
|
|
|
|
|
// lookupByRange
|
|
//
|
|
// Purpose: used for stack crawling / debugging purposes
|
|
// In: pointer to memory
|
|
// Out: pointer to cache entry if in code cache (NULL otherwise)
|
|
//
|
|
// Notes: currently we do a linear search of a vector of methods, eventually
|
|
// we'll want a better search strategy
|
|
CacheEntry* NativeCodeCache::
|
|
lookupByRange(Uint8* inAddress)
|
|
{
|
|
for(CacheEntry** ce= mIndexByRange.begin(); ce < mIndexByRange.end(); ce++)
|
|
{
|
|
#ifdef GENERATE_FOR_X86
|
|
if ((*ce)->stub <= inAddress && (*ce)->stub + 10 > inAddress)
|
|
return *ce;
|
|
else
|
|
#endif
|
|
if(((*ce)->start.getFunctionAddress() <= inAddress) && (inAddress < (*ce)->end))
|
|
return *ce;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// lookupByDescriptor
|
|
//
|
|
// Looks up method inMethodDescriptor, if inMethodDescriptor
|
|
// is not in the cache, this generates a new "stub" function
|
|
// (which will compile inMethodDescriptor and backpatch the caller)
|
|
// Since we have two entries in the CacheEntry, one for stub and
|
|
// the other for function, we return the function if it exists
|
|
// otherwise we return the stub.
|
|
// inLookupOnly - do not create a stub if the lookup fails
|
|
addr NativeCodeCache::
|
|
lookupByDescriptor(const MethodDescriptor& inMethodDescriptor, bool /*inhibitBackpatch*/, bool inLookupOnly)
|
|
{
|
|
Method *method = inMethodDescriptor.method;
|
|
TemporaryStringCopy key(method->toString());
|
|
|
|
CacheEntry* cacheEntry = NULL;
|
|
|
|
mIndexByDescriptor.get(key, &cacheEntry);
|
|
|
|
// short circuit if inLookupOnly
|
|
if (inLookupOnly && !cacheEntry)
|
|
return (functionAddress(NULL));
|
|
|
|
if (!cacheEntry)
|
|
{
|
|
CacheEntry& newEntry = *new(mPool) CacheEntry(inMethodDescriptor);
|
|
|
|
newEntry.stub = (unsigned char*) generateCompileStub(*this, newEntry);
|
|
newEntry.start.setTVector(NULL);
|
|
newEntry.end = NULL;
|
|
newEntry.eTable = NULL;
|
|
|
|
// FIX set shouldBackPatch to always false. Note that this
|
|
// means that we compile evry function every time
|
|
newEntry.shouldBackPatch = false;
|
|
|
|
//newEntry.shouldBackPatch = !method->getDynamicallyDispatched();
|
|
|
|
cacheEntry = &newEntry;
|
|
mIndexByDescriptor.add(key, cacheEntry);
|
|
mIndexByRange.append(cacheEntry);
|
|
}
|
|
|
|
bool inhibitBackpatching = false;
|
|
inhibitBackpatching = VM::theVM.getInhibitBackpatching();
|
|
|
|
if (cacheEntry->start.getFunctionAddress() && !inhibitBackpatching)
|
|
return (functionAddress((void (*)())cacheEntry->start.getFunctionAddress()));
|
|
else
|
|
{
|
|
if (!cacheEntry->stub)
|
|
cacheEntry->stub = (unsigned char*) generateCompileStub(*this, *cacheEntry);
|
|
return (functionAddress((void (*)())cacheEntry->stub));
|
|
}
|
|
}
|
|
|
|
|
|
UT_DEFINE_LOG_MODULE(NativeCodeCacheMonitor);
|
|
|
|
// compileOrLoadMethod
|
|
//
|
|
// If the method pointed to by inCacheEntry is a native method, resolve and load
|
|
// the native code library. Otherwise compile the method's bytecode into
|
|
// native code. Either way, return a pointer to the resulting native code.
|
|
// Sets shouldBackPatch to true if it is neccessary to back-patch the method.
|
|
static void*
|
|
compileOrLoadMethod(const CacheEntry* inCacheEntry, bool &shouldBackPatch)
|
|
{
|
|
Method* method;
|
|
|
|
// if this function is already compiled then just return it
|
|
if (inCacheEntry->start.getFunctionAddress() != NULL)
|
|
{
|
|
shouldBackPatch = inCacheEntry->shouldBackPatch;
|
|
goto ExitReturnFunc;
|
|
}
|
|
else
|
|
{
|
|
NativeCodeCache::enter();
|
|
// maybe we slept while someone else was compiling, if so
|
|
// don't do any work.
|
|
if (inCacheEntry->start.getFunctionAddress() != NULL) {
|
|
NativeCodeCache::exit();
|
|
goto ExitReturnFunc;
|
|
}
|
|
|
|
method = inCacheEntry->descriptor.method;
|
|
|
|
// If this is a native method, then resolve it appropriately
|
|
if (method->getModifiers() & CR_METHOD_NATIVE)
|
|
{
|
|
addr a = NativeMethodDispatcher::resolve(*method);
|
|
|
|
if (!a)
|
|
{
|
|
NativeCodeCache::exit();
|
|
UT_LOG(NativeCodeCache, PR_LOG_ERROR, ("\tCould not resolve native method %s\n", method->getName()));
|
|
runtimeError(RuntimeError::linkError);
|
|
}
|
|
else
|
|
UT_LOG(NativeCodeCache, PR_LOG_DEBUG, ("\tResolved native method %s\n", method->getName()));
|
|
|
|
|
|
NativeCodeCache &cache = NativeCodeCache::getCache();
|
|
void* code = generateNativeStub(cache, *inCacheEntry, addressFunction(a));
|
|
|
|
PlatformTVector tVector;
|
|
tVector.setTVector((Uint8*)code);
|
|
int codeLength = 0; // We don't know the length of native methods yet
|
|
|
|
/*ExceptionTable* pExceptionTable = NULL; */ // FIX native methods don't have exception tables yet
|
|
StackFrameInfo policy; // FIX we don't know the stack layout policies yet, so pass in junk
|
|
|
|
cache.mapMemoryToMethod(inCacheEntry->descriptor, tVector, (Uint8*)code + codeLength, NULL, policy);
|
|
}
|
|
else
|
|
method->compile();
|
|
|
|
NativeCodeCache::exit();
|
|
}
|
|
|
|
ExitReturnFunc:
|
|
assert(inCacheEntry->start.getFunctionAddress());
|
|
shouldBackPatch = inCacheEntry->shouldBackPatch;
|
|
EventBroadcaster::broadcastEvent(gCompileOrLoadBroadcaster, kEndCompileOrLoad, inCacheEntry->descriptor.method);
|
|
return (inCacheEntry->start.getFunctionAddress());
|
|
/*
|
|
ExitMonitorThrowCompileException:
|
|
PR_CExitMonitor((void*)inCacheEntry);
|
|
assert(false);
|
|
return (NULL);
|
|
*/
|
|
}
|
|
|
|
|
|
// compileAndBackPatchMethod
|
|
//
|
|
// compile the method pointed to by inCacheEntry and then back patch
|
|
// the caller.
|
|
//
|
|
// inLastPC = address of next native instruction to be executed
|
|
// if the callee executed and returned. (where callee is the method that
|
|
// is to be compiled)
|
|
extern "C" void*
|
|
compileAndBackPatchMethod(const CacheEntry* inCacheEntry, void* inLastPC, void* inUserDefined)
|
|
{
|
|
bool shouldBackPatch;
|
|
|
|
void *nativeCode = compileOrLoadMethod(inCacheEntry, shouldBackPatch);
|
|
|
|
if (VM::theVM.getInhibitBackpatching())
|
|
shouldBackPatch = false;
|
|
|
|
#ifdef DEBUG
|
|
if (VM::theVM.getTraceAllMethods())
|
|
{
|
|
Method *method = inCacheEntry->descriptor.method;
|
|
printFrame(method);
|
|
}
|
|
#endif
|
|
|
|
/* Back-patch method only if method was resolved statically */
|
|
return (shouldBackPatch) ?
|
|
(backPatchMethod(nativeCode, inLastPC, inUserDefined)) : nativeCode;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_LOG
|
|
// C-entry point so we can call this from
|
|
// the MSVC debugger
|
|
void NS_EXTERN
|
|
printMethodTable()
|
|
{
|
|
NativeCodeCache::getCache().printMethodTable(UT_LOG_MODULE(NativeCodeCache));
|
|
}
|
|
|
|
// printMethodTable
|
|
//
|
|
// Print out all the methods currently in the cache, with their
|
|
// ExceptionTables
|
|
void NativeCodeCache::
|
|
printMethodTable(LogModuleObject &f)
|
|
{
|
|
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n__________________Method Table___________________\n"));
|
|
for(CacheEntry** ce= mIndexByRange.begin(); ce < mIndexByRange.end(); ce++)
|
|
{
|
|
Uint8* pStart = (*ce)->start.getFunctionAddress();
|
|
Uint8* pEnd = (*ce)->end;
|
|
const char* pName= (*ce)->descriptor.method->toString();
|
|
ExceptionTable* eTable= (*ce)->eTable;
|
|
|
|
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("[%#010x-%#010x] %s\n", Uint32(pStart), Uint32(pEnd), pName));
|
|
if(eTable)
|
|
eTable->printShort(f);
|
|
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n"));
|
|
}
|
|
UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n\n"));
|
|
}
|
|
|
|
UT_EXTERN_LOG_MODULE(StackWalker);
|
|
|
|
#include "StackWalker.h"
|
|
|
|
// printFrame
|
|
//
|
|
// Print a frame of a given method (which is
|
|
// the caller). This can be called from a debugger
|
|
static void printFrame(Method *method)
|
|
{
|
|
UT_SET_LOG_LEVEL(StackWalker, PR_LOG_DEBUG);
|
|
|
|
Frame frame;
|
|
frame.moveToPrevFrame();
|
|
frame.moveToPrevFrame();
|
|
|
|
// Indent the frame according to stack depth
|
|
int stackDepth = Frame::getStackDepth();
|
|
int i;
|
|
|
|
// Discount the stack frames used for internal EF native code
|
|
stackDepth -= 7;
|
|
if (stackDepth < 0)
|
|
stackDepth = 0;
|
|
|
|
for (i = 0; i < stackDepth; i++)
|
|
UT_LOG(StackWalker, PR_LOG_ALWAYS, (" "));
|
|
|
|
Frame::printWithArgs(UT_LOG_MODULE(StackWalker), (Uint8*)(frame.getBase()), method);
|
|
UT_LOG(StackWalker, PR_LOG_ALWAYS, ("\n"));
|
|
|
|
UT_LOG_MODULE(StackWalker).flushLogFile();
|
|
}
|
|
|
|
#endif // DEBUG_LOG
|