/* -*- 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): */ // // NativeFormatter.cpp // // Scott M. Silver // // Actually output native code for a function to memory (from the NativeCodeCache). // // Summary // // A NativeFormatter takes an InstructionEmitter (a per/method data structure) and a Method // (a runtime representation of a method) and actually outputs the native representation // of the function out to memory. Memory is acquired from the NativeCodeCache. // // Each function has the following structural layout: // // ControlNodes Formatted Correlation // PreMethod // ckBegin Prolog // ckReturn Epilog // ckEnd PostMethod // // Prolog and Epilog are normal entry and exit points from a function. Note that even // if a function does not return a value a ckReturn node must exist. // PreMethod and PostMethod are invented pieces of code/data which may be used // for debuggers or stack inspection (eg. MacsBug symbols, TraceBack tables, etc...) #include "NativeFormatter.h" #include "ControlGraph.h" #include "InstructionEmitter.h" #include "NativeCodeCache.h" #include "ExceptionTable.h" #ifdef DEBUG #include "JavaVM.h" // to insert breakpoints in jitted code #endif UT_DEFINE_LOG_MODULE(ExceptionMatrix); //----------------------------------------------------------------------------------------------------------- // format // // Actually output the native instructions of a method out to memory // acquired from the cache. // // outInfo: Normally temporal information about the formatting the function // mainly used for debugging purposes void* NativeFormatter:: format(Method& inMethod, FormattedCodeInfo* outInfo) { // now setup a FormattedCodeInfo with collected data about the method FormattedCodeInfo fci; fci.method = &inMethod; // Need policy before any formatting can be done mFormatter.initStackFrameInfo(); mFormatter.calculatePrologEpilog(inMethod, fci.prologSize, fci.epilogSize); mFormatter.calculatePrePostMethod(inMethod, fci.preMethodSize, fci.postMethodSize); // potentially shortens or lengthens branches fci.bodySize = resolveBranches(fci.prologSize, fci.epilogSize); // actually grab some memory for this method Uint32 methodSize = fci.preMethodSize + fci.bodySize + fci.postMethodSize; #ifdef DEBUG #if defined(XP_PC) || defined(LINUX) DebugDesc *bps; Uint32 nbps; nbps = VM::theVM.getExecBreakPoints(bps); bool hasInt3 = false; bool foundBreakPoint = false; for (Uint32 index = 0; index < nbps; index++) if (inMethod.isSelf(bps[index].className, bps[index].methodName, bps[index].sig)) { methodSize++; // for int 3 fci.methodStart = NativeCodeCache::getCache().acquireMemory(methodSize); *fci.methodStart++ = 0xcc; hasInt3 = true; foundBreakPoint = true; break; } if (!foundBreakPoint) fci.methodStart = NativeCodeCache::getCache().acquireMemory(methodSize); #else fci.methodStart = NativeCodeCache::getCache().acquireMemory(methodSize); #endif #else fci.methodStart = NativeCodeCache::getCache().acquireMemory(methodSize); #endif // build the exception table // FIX FIX FIX // for now just grab some memory Pool* tempPool = new Pool(); ExceptionTable& eTable = buildExceptionTable(fci.methodStart, tempPool); DEBUG_LOG_ONLY(eTable.print(UT_LOG_MODULE(ExceptionMatrix))); DEBUG_LOG_ONLY(eTable.printFormatted(UT_LOG_MODULE(ExceptionMatrix), nodes, nNodes, false)); // fci is now valid mFormatter.beginFormatting(fci); // dump out the function to memory outputNativeToMemory(fci.methodStart, fci); // make an entry point for this function MethodDescriptor md(inMethod); mFormatter.endFormatting(fci); #if defined(DEBUG) && (defined(XP_PC) || defined(LINUX)) if (hasInt3) fci.methodStart--; #endif PlatformTVector functionDescriptor; functionDescriptor.setTVector(mFormatter.createTransitionVector(fci)); fci.methodEnd = fci.methodStart + methodSize; #ifdef WIN32 // temporarily check this invariant assert((fci.methodStart + methodSize) == (functionDescriptor.getFunctionAddress() + methodSize)); #endif // Eventually we may wish to have choose different GPR/FPR save procedures and local store sizes for // different nodes in the control graph. Correspondingly the restore policies in an exception unwind // can vary by control node -- ie they are not necessarily the same across the whole method. // For now we will give each method the same info, but we reserve the right later to make this optimisation. // FIX for mow, NativeCodeCache::mapMemoryToMethod caches a copy of the policy StackFrameInfo& policy = mFormatter.getStackFrameInfo(); NativeCodeCache::getCache().mapMemoryToMethod(md, functionDescriptor, fci.methodEnd, &eTable, policy); if (outInfo) *outInfo = fci; #ifdef DEBUG_LOG // debugging stuff if(VM::theVM.getEmitHTML()) dumpMethodToHTML(fci, eTable); #endif // return the entry point return (fci.methodStart); } // outputNativeToMemory // actually ouput this method to memory starting at inWhere. // inInfo contains some precalculated information about the method // which is useful in its formatting. void NativeFormatter:: outputNativeToMemory(void* inWhere, const FormattedCodeInfo& inInfo) { Uint32 curOffset; char* nextMemory = (char*) inWhere; assert(nodes[0]->hasControlKind(ckBegin)); assert(nodes[nNodes-1]->hasControlKind(ckEnd)); // first output the PreMethod mFormatter.formatPreMethodToMemory(nextMemory, inInfo); nextMemory += inInfo.preMethodSize; mFormatter.formatPrologToMemory(nextMemory); nextMemory += inInfo.prologSize; curOffset = inInfo.prologSize; for (Uint32 n = 0; n < nNodes; n++) { InstructionList& instructions = nodes[n]->getInstructions(); for(InstructionList::iterator i = instructions.begin(); !instructions.done(i); i = instructions.advance(i)) { Instruction& curInstruction = instructions.get(i); curInstruction.formatToMemory(nextMemory, curOffset, mFormatter); curOffset += curInstruction.getFormattedSize(mFormatter); nextMemory += curInstruction.getFormattedSize(mFormatter); } if (nodes[n]->hasControlKind(ckReturn)) { mFormatter.formatEpilogToMemory(nextMemory); curOffset += inInfo.epilogSize; nextMemory += inInfo.epilogSize; } } // Now end with the PostMethod mFormatter.formatPostMethodToMemory(nextMemory, inInfo); //nextMemory += inInfo.preMethodSize; } // accumulateSize // // Accumulate the size of the the formatted (ie in-memory) representation // of each Instruction in this ControlNode. ckBegin and ckReturn nodes // include the prolog and epilog size, respectively. Uint32 NativeFormatter:: accumulateSize(ControlNode& inNode, Uint32 inPrologSize, Uint32 inEpilogSize) { Uint32 accum; InstructionList& instructions = inNode.getInstructions(); accum = 0; for(InstructionList::iterator i = instructions.begin(); !instructions.done(i); i = instructions.advance(i)) accum += instructions.get(i).getFormattedSize(mFormatter); if (inNode.hasControlKind(ckReturn)) accum += inEpilogSize; else if (inNode.hasControlKind(ckBegin)) accum += inPrologSize; return (accum); } // resolveBranches // // for each node in the scheduled ControlNodes // assign a native offset to each ControlNode. // decisions to make short/long branches would normally be made // here. returns the size of the formatted method. including // epilog and prolog. The PreMethod and PostMethod are not // considered here. Uint32 NativeFormatter:: resolveBranches(Uint32 inPrologSize, Uint32 inEpilogSize) { Uint32 accumSize; accumSize = 0; for (Uint32 i = 0; i < nNodes; i++) { ControlNode& node = *nodes[i]; Uint32 formattedSize; node.setNativeOffset(accumSize); // fix-me this should be a function of the list, ie the list // should maintain the size of the instructions in it formattedSize = accumulateSize(node, inPrologSize, inEpilogSize); accumSize += formattedSize; } // make long/short branch decisions here return (accumSize); } // buildExceptionTable // In: start of method // pool to create table in // Out: the Exception Table // #define DEBUG_GENERATE_EXCEPTION_MATRIX // Debugging code to print out a matrix of exception handlers // Ugly -- farm out to a class, time permitting #ifdef DEBUG_GENERATE_EXCEPTION_MATRIX #define DEBUG_SET_UP_EXCEPTION_MATRIX \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("\n\nBuilding Exception Table\n")); \ assert(nNodes < 100); /* dumb check */ \ int _exceptionMatrix[100][100]; \ Uint32 _rows, _cols; \ for(_rows = 0; _rows < nNodes; _rows++) \ for(_cols = 0; _cols < nNodes; _cols++) \ _exceptionMatrix[_rows][_cols] = 0; \ Uint32 _addorder = 1; #define DEBUG_ADD_ENTRY_TO_EXCEPTION_MATRIX \ _exceptionMatrix[node->dfsNum][e->getTarget().dfsNum] = _addorder++; #define DEBUG_PRINT_EXCEPTION_MATRIX \ /* print header */ \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("\n\nException Matrix\n ")); \ for(_cols = 0; _cols < nNodes; _cols++) \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("N%02d ", nodes[_cols]->dfsNum)); \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("\n")); \ /* print rows */ \ for(_rows = 0; _rows < nNodes; _rows++) \ { \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, (" N%02d: ", nodes[_rows]->dfsNum)); \ for(_cols = 0; _cols < nNodes; _cols++) \ { \ _addorder = _exceptionMatrix[nodes[_rows]->dfsNum][nodes[_cols]->dfsNum]; \ if(_addorder != 0) \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, (" %02d ", _addorder)); \ else \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, (" - ")); \ } \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("\n")); \ } \ UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("\n\n\n")); #else #define DEBUG_SET_UP_EXCEPTION_MATRIX #define DEBUG_ADD_ENTRY_TO_EXCEPTION_MATRIX #define DEBUG_PRINT_EXCEPTION_MATRIX #endif // DEBUG_GENERATE_EXCEPTION_MATRIX ExceptionTable& NativeFormatter:: buildExceptionTable(Uint8* methodStart, Pool* inPool) { Uint32 i; DEBUG_SET_UP_EXCEPTION_MATRIX; ExceptionTableBuilder eBuilder(methodStart); for (i = 0; i < nNodes; i++) // iterate through nodes in scheduled order { ControlNode* node = nodes[i]; ControlKind kind = node->getControlKind(); if(kind == ckExc || kind == ckAExc || kind == ckThrow) { // has exception ControlNode::ExceptionExtra& excExtra = node->getExceptionExtra(); const Class **exceptionClass = excExtra.handlerFilters + excExtra.nHandlers; if (kind == ckAExc) eBuilder.addAEntry(node->getNativeOffset()); // iterate through exception edges Uint32 startIndex = 0; // start extending exceptions from beginning of table ControlEdge *e = node->getSuccessorsEnd(); for (Uint32 exceptNum = 0; exceptNum < excExtra.nHandlers; exceptNum++) { e--; // move to previous handler edge exceptionClass--; // move to previous handler filter assert(&(e->getSource()) == node); if(e->getTarget().getControlKind() != ckEnd) // only interested in exceptions handled locally { // assert(e->getTarget().getControlKind() == ckCatch); // targets of exception edges must be End or Catch nodes ExceptionTableEntry ete; // container for exception info passing ete.pStart = node->getNativeOffset(); ete.pEnd = nodes[i+1]->getNativeOffset(); // points to byte after 'node' ete.pHandler = e->getTarget().getNativeOffset(); ete.pExceptionType = *exceptionClass; UT_LOG(ExceptionMatrix, PR_LOG_ALWAYS, ("N%02d->N%02d ", node->dfsNum, e->getTarget().dfsNum)); eBuilder.addEntry(startIndex, ete); // note that startIndex is passed by reference DEBUG_ADD_ENTRY_TO_EXCEPTION_MATRIX; } } } } DEBUG_PRINT_EXCEPTION_MATRIX; DEBUG_ONLY(eBuilder.checkInOrder()); // copy table into new (efficient) storage and insert into NativeCodeCache ExceptionTable* eTable = new(*inPool) ExceptionTable(eBuilder, *inPool); return (*eTable); }