blob: fd54276875961349fe1ce47ee8d32bd997064623 [file] [log] [blame]
/*
* Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.code;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static jdk.vm.ci.meta.MetaUtil.identityHashCodeString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jdk.internal.vm.compiler.collections.EconomicSet;
import org.graalvm.compiler.core.common.CompilationIdentifier;
import org.graalvm.compiler.graph.NodeSourcePosition;
import jdk.vm.ci.code.DebugInfo;
import jdk.vm.ci.code.StackSlot;
import jdk.vm.ci.code.site.Call;
import jdk.vm.ci.code.site.ConstantReference;
import jdk.vm.ci.code.site.DataPatch;
import jdk.vm.ci.code.site.DataSectionReference;
import jdk.vm.ci.code.site.ExceptionHandler;
import jdk.vm.ci.code.site.Infopoint;
import jdk.vm.ci.code.site.InfopointReason;
import jdk.vm.ci.code.site.Mark;
import jdk.vm.ci.code.site.Reference;
import jdk.vm.ci.code.site.Site;
import jdk.vm.ci.meta.Assumptions.Assumption;
import jdk.vm.ci.meta.InvokeTarget;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
/**
* Represents the output from compiling a method, including the compiled machine code, associated
* data and references, relocation information, deoptimization information, etc.
*/
public class CompilationResult {
/**
* Provides extra information about instructions or data at specific positions in
* {@link CompilationResult#getTargetCode()}. This is optional information that can be used to
* enhance a disassembly of the code.
*/
public abstract static class CodeAnnotation {
public final int position;
public CodeAnnotation(int position) {
this.position = position;
}
@Override
public final int hashCode() {
throw new UnsupportedOperationException("hashCode");
}
@Override
public String toString() {
return identityHashCodeString(this);
}
@Override
public abstract boolean equals(Object obj);
}
/**
* A string comment about one or more instructions at a specific position in the code.
*/
public static final class CodeComment extends CodeAnnotation {
public final String value;
public CodeComment(int position, String comment) {
super(position);
this.value = comment;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof CodeComment) {
CodeComment that = (CodeComment) obj;
if (this.position == that.position && this.value.equals(that.value)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + position + ": " + value;
}
}
/**
* Describes a table of signed offsets embedded in the code. The offsets are relative to the
* starting address of the table. This type of table maybe generated when translating a
* multi-way branch based on a key value from a dense value set (e.g. the {@code tableswitch}
* JVM instruction).
*
* The table is indexed by the contiguous range of integers from {@link #low} to {@link #high}
* inclusive.
*/
public static final class JumpTable extends CodeAnnotation {
/**
* The low value in the key range (inclusive).
*/
public final int low;
/**
* The high value in the key range (inclusive).
*/
public final int high;
/**
* The size (in bytes) of each table entry.
*/
public final int entrySize;
public JumpTable(int position, int low, int high, int entrySize) {
super(position);
this.low = low;
this.high = high;
this.entrySize = entrySize;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof JumpTable) {
JumpTable that = (JumpTable) obj;
if (this.position == that.position && this.entrySize == that.entrySize && this.low == that.low && this.high == that.high) {
return true;
}
}
return false;
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + position + ": [" + low + " .. " + high + "]";
}
}
private boolean closed;
private int entryBCI = -1;
private final DataSection dataSection = new DataSection();
private final List<Infopoint> infopoints = new ArrayList<>();
private final List<SourceMapping> sourceMapping = new ArrayList<>();
private final List<DataPatch> dataPatches = new ArrayList<>();
private final List<ExceptionHandler> exceptionHandlers = new ArrayList<>();
private final List<Mark> marks = new ArrayList<>();
private int totalFrameSize = -1;
private int maxInterpreterFrameSize = -1;
private StackSlot customStackArea = null;
private final String name;
private final CompilationIdentifier compilationId;
/**
* The buffer containing the emitted machine code.
*/
private byte[] targetCode;
/**
* The leading number of bytes in {@link #targetCode} containing the emitted machine code.
*/
private int targetCodeSize;
private ArrayList<CodeAnnotation> annotations;
private Assumption[] assumptions;
/**
* The list of the methods whose bytecodes were used as input to the compilation. If
* {@code null}, then the compilation did not record method dependencies. Otherwise, the first
* element of this array is the root method of the compilation.
*/
private ResolvedJavaMethod[] methods;
/**
* The list of fields that were accessed from the bytecodes.
*/
private ResolvedJavaField[] fields;
private int bytecodeSize;
private boolean hasUnsafeAccess;
private boolean isImmutablePIC;
public CompilationResult(CompilationIdentifier compilationId) {
this(compilationId, compilationId.toString(CompilationIdentifier.Verbosity.NAME), false);
}
public CompilationResult(CompilationIdentifier compilationId, String name) {
this(compilationId, name, false);
}
public CompilationResult(CompilationIdentifier compilationId, boolean isImmutablePIC) {
this(compilationId, null, isImmutablePIC);
}
public CompilationResult(CompilationIdentifier compilationId, String name, boolean isImmutablePIC) {
this.compilationId = compilationId;
this.name = name;
this.isImmutablePIC = isImmutablePIC;
}
public CompilationResult(String name) {
this(null, name);
}
@Override
public int hashCode() {
// CompilationResult instances should not be used as hash map keys
throw new UnsupportedOperationException("hashCode");
}
@Override
public String toString() {
if (methods != null) {
return getClass().getName() + "[" + methods[0].format("%H.%n(%p)%r") + "]";
}
return identityHashCodeString(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj.getClass() == getClass()) {
CompilationResult that = (CompilationResult) obj;
// @formatter:off
if (this.entryBCI == that.entryBCI &&
Objects.equals(this.customStackArea, that.customStackArea) &&
this.totalFrameSize == that.totalFrameSize &&
this.targetCodeSize == that.targetCodeSize &&
Objects.equals(this.name, that.name) &&
Objects.equals(this.compilationId, that.compilationId) &&
Objects.equals(this.annotations, that.annotations) &&
Objects.equals(this.dataSection, that.dataSection) &&
Objects.equals(this.exceptionHandlers, that.exceptionHandlers) &&
Objects.equals(this.dataPatches, that.dataPatches) &&
Objects.equals(this.infopoints, that.infopoints) &&
Objects.equals(this.marks, that.marks) &&
Arrays.equals(this.assumptions, that.assumptions) &&
Arrays.equals(targetCode, that.targetCode)) {
return true;
}
// @formatter:on
}
return false;
}
/**
* @return the entryBCI
*/
public int getEntryBCI() {
return entryBCI;
}
/**
* @param entryBCI the entryBCI to set
*/
public void setEntryBCI(int entryBCI) {
checkOpen();
this.entryBCI = entryBCI;
}
/**
* Sets the assumptions made during compilation.
*/
public void setAssumptions(Assumption[] assumptions) {
this.assumptions = assumptions;
}
/**
* Gets the assumptions made during compilation.
*
* The caller must not modify the contents of the returned array.
*/
public Assumption[] getAssumptions() {
return assumptions;
}
/**
* Sets the methods whose bytecodes were used as input to the compilation.
*
* @param rootMethod the root method of the compilation
* @param inlinedMethods the methods inlined during compilation
*/
public void setMethods(ResolvedJavaMethod rootMethod, Collection<ResolvedJavaMethod> inlinedMethods) {
checkOpen();
assert rootMethod != null;
assert inlinedMethods != null;
if (inlinedMethods.contains(rootMethod)) {
methods = inlinedMethods.toArray(new ResolvedJavaMethod[inlinedMethods.size()]);
for (int i = 0; i < methods.length; i++) {
if (methods[i].equals(rootMethod)) {
if (i != 0) {
ResolvedJavaMethod tmp = methods[0];
methods[0] = methods[i];
methods[i] = tmp;
}
break;
}
}
} else {
methods = new ResolvedJavaMethod[1 + inlinedMethods.size()];
methods[0] = rootMethod;
int i = 1;
for (ResolvedJavaMethod m : inlinedMethods) {
methods[i++] = m;
}
}
}
/**
* Gets the methods whose bytecodes were used as input to the compilation.
*
* The caller must not modify the contents of the returned array.
*
* @return {@code null} if the compilation did not record method dependencies otherwise the
* methods whose bytecodes were used as input to the compilation with the first element
* being the root method of the compilation
*/
public ResolvedJavaMethod[] getMethods() {
return methods;
}
/**
* Sets the fields that were referenced from the bytecodes that were used as input to the
* compilation.
*
* @param accessedFields the collected set of fields accessed during compilation
*/
public void setFields(EconomicSet<ResolvedJavaField> accessedFields) {
if (accessedFields != null) {
fields = accessedFields.toArray(new ResolvedJavaField[accessedFields.size()]);
}
}
/**
* Gets the fields that were referenced from bytecodes that were used as input to the
* compilation.
*
* The caller must not modify the contents of the returned array.
*
* @return {@code null} if the compilation did not record fields dependencies otherwise the
* fields that were accessed from bytecodes were used as input to the compilation.
*/
public ResolvedJavaField[] getFields() {
return fields;
}
public void setBytecodeSize(int bytecodeSize) {
checkOpen();
this.bytecodeSize = bytecodeSize;
}
public int getBytecodeSize() {
return bytecodeSize;
}
public DataSection getDataSection() {
return dataSection;
}
/**
* The total frame size of the method in bytes. This includes the return address pushed onto the
* stack, if any.
*
* @return the frame size
*/
public int getTotalFrameSize() {
assert totalFrameSize != -1 : "frame size not yet initialized!";
return totalFrameSize;
}
/**
* Sets the total frame size in bytes. This includes the return address pushed onto the stack,
* if any.
*
* @param size the size of the frame in bytes
*/
public void setTotalFrameSize(int size) {
checkOpen();
totalFrameSize = size;
}
public int getMaxInterpreterFrameSize() {
return maxInterpreterFrameSize;
}
public void setMaxInterpreterFrameSize(int maxInterpreterFrameSize) {
checkOpen();
this.maxInterpreterFrameSize = maxInterpreterFrameSize;
}
public boolean isImmutablePIC() {
return this.isImmutablePIC;
}
/**
* Sets the machine that has been generated by the compiler.
*
* @param code the machine code generated
* @param size the size of the machine code
*/
public void setTargetCode(byte[] code, int size) {
checkOpen();
targetCode = code;
targetCodeSize = size;
}
/**
* Records a data patch in the code section. The data patch can refer to something in the
* {@link DataSectionReference data section} or directly to an {@link ConstantReference inlined
* constant}.
*
* @param codePos the position in the code that needs to be patched
* @param ref the reference that should be inserted in the code
*/
public void recordDataPatch(int codePos, Reference ref) {
checkOpen();
assert codePos >= 0 && ref != null;
dataPatches.add(new DataPatch(codePos, ref));
}
/**
* Records a data patch in the code section. The data patch can refer to something in the
* {@link DataSectionReference data section} or directly to an {@link ConstantReference inlined
* constant}.
*
* @param codePos the position in the code that needs to be patched
* @param ref the reference that should be inserted in the code
* @param note a note attached to data patch for use by post-processing tools
*/
public void recordDataPatchWithNote(int codePos, Reference ref, Object note) {
assert codePos >= 0 && ref != null;
dataPatches.add(new DataPatch(codePos, ref, note));
}
/**
* Records a call in the code array.
*
* @param codePos the position of the call in the code array
* @param size the size of the call instruction
* @param target the being called
* @param debugInfo the debug info for the call
* @param direct specifies if this is a {@linkplain Call#direct direct} call
*/
public void recordCall(int codePos, int size, InvokeTarget target, DebugInfo debugInfo, boolean direct) {
checkOpen();
final Call call = new Call(target, codePos, size, direct, debugInfo);
addInfopoint(call);
}
/**
* Records an exception handler for this method.
*
* @param codePos the position in the code that is covered by the handler
* @param handlerPos the position of the handler
*/
public void recordExceptionHandler(int codePos, int handlerPos) {
checkOpen();
assert validateExceptionHandlerAdd(codePos, handlerPos) : String.format("Duplicate exception handler for pc 0x%x handlerPos 0x%x", codePos, handlerPos);
exceptionHandlers.add(new ExceptionHandler(codePos, handlerPos));
}
/**
* Validate if the exception handler for codePos already exists and handlerPos is different.
*
* @param codePos
* @param handlerPos
* @return true if the validation is successful
*/
private boolean validateExceptionHandlerAdd(int codePos, int handlerPos) {
ExceptionHandler exHandler = getExceptionHandlerForCodePos(codePos);
return exHandler == null || exHandler.handlerPos == handlerPos;
}
/**
* Returns the first ExceptionHandler which matches codePos.
*
* @param codePos position to search for
* @return first matching ExceptionHandler
*/
private ExceptionHandler getExceptionHandlerForCodePos(int codePos) {
for (ExceptionHandler h : exceptionHandlers) {
if (h.pcOffset == codePos) {
return h;
}
}
return null;
}
/**
* Records an infopoint in the code array.
*
* @param codePos the position of the infopoint in the code array
* @param debugInfo the debug info for the infopoint
*/
public void recordInfopoint(int codePos, DebugInfo debugInfo, InfopointReason reason) {
addInfopoint(new Infopoint(codePos, debugInfo, reason));
}
/**
* Records a custom infopoint in the code section.
*
* Compiler implementations can use this method to record non-standard infopoints, which are not
* handled by dedicated methods like {@link #recordCall}.
*
* @param infopoint the infopoint to record, usually a derived class from {@link Infopoint}
*/
public void addInfopoint(Infopoint infopoint) {
checkOpen();
infopoints.add(infopoint);
}
public void recordSourceMapping(int startOffset, int endOffset, NodeSourcePosition sourcePosition) {
checkOpen();
sourceMapping.add(new SourceMapping(startOffset, endOffset, sourcePosition));
}
/**
* Records an instruction mark within this method.
*
* @param codePos the position in the code that is covered by the handler
* @param markId the identifier for this mark
*/
public Mark recordMark(int codePos, Object markId) {
checkOpen();
Mark mark = new Mark(codePos, markId);
marks.add(mark);
return mark;
}
/**
* Start of the custom stack area.
*
* @return the first stack slot of the custom stack area
*/
public StackSlot getCustomStackArea() {
return customStackArea;
}
/**
* @see #getCustomStackArea()
* @param slot
*/
public void setCustomStackAreaOffset(StackSlot slot) {
checkOpen();
customStackArea = slot;
}
/**
* @return the machine code generated for this method
*/
public byte[] getTargetCode() {
return targetCode;
}
/**
* @return the size of the machine code generated for this method
*/
public int getTargetCodeSize() {
return targetCodeSize;
}
/**
* @return the code annotations or {@code null} if there are none
*/
public List<CodeAnnotation> getAnnotations() {
if (annotations == null) {
return Collections.emptyList();
}
return annotations;
}
public void addAnnotation(CodeAnnotation annotation) {
checkOpen();
assert annotation != null;
if (annotations == null) {
annotations = new ArrayList<>();
}
annotations.add(annotation);
}
/**
* @return the list of infopoints, sorted by {@link Site#pcOffset}
*/
public List<Infopoint> getInfopoints() {
if (infopoints.isEmpty()) {
return emptyList();
}
return unmodifiableList(infopoints);
}
/**
* @return the list of data references
*/
public List<DataPatch> getDataPatches() {
if (dataPatches.isEmpty()) {
return emptyList();
}
return unmodifiableList(dataPatches);
}
/**
* @return the list of exception handlers
*/
public List<ExceptionHandler> getExceptionHandlers() {
if (exceptionHandlers.isEmpty()) {
return emptyList();
}
return unmodifiableList(exceptionHandlers);
}
/**
* @return the list of marks
*/
public List<Mark> getMarks() {
if (marks.isEmpty()) {
return emptyList();
}
return unmodifiableList(marks);
}
/**
* @return the list of {@link SourceMapping}s
*/
public List<SourceMapping> getSourceMappings() {
if (sourceMapping.isEmpty()) {
return emptyList();
}
return unmodifiableList(sourceMapping);
}
public String getName() {
return name;
}
public CompilationIdentifier getCompilationId() {
return compilationId;
}
public void setHasUnsafeAccess(boolean hasUnsafeAccess) {
checkOpen();
this.hasUnsafeAccess = hasUnsafeAccess;
}
public boolean hasUnsafeAccess() {
return hasUnsafeAccess;
}
/**
* Clears the information in this object pertaining to generating code. That is, the
* {@linkplain #getMarks() marks}, {@linkplain #getInfopoints() infopoints},
* {@linkplain #getExceptionHandlers() exception handlers}, {@linkplain #getDataPatches() data
* patches} and {@linkplain #getAnnotations() annotations} recorded in this object are cleared.
*/
public void resetForEmittingCode() {
checkOpen();
infopoints.clear();
sourceMapping.clear();
dataPatches.clear();
exceptionHandlers.clear();
marks.clear();
dataSection.clear();
if (annotations != null) {
annotations.clear();
}
}
private void checkOpen() {
if (closed) {
throw new IllegalStateException();
}
}
/**
* Closes this compilation result to future updates.
*/
public void close() {
if (closed) {
throw new IllegalStateException("Cannot re-close compilation result " + this);
}
dataSection.close();
closed = true;
}
}