Mercurial > hg > truffle
diff graal/com.oracle.max.asmdis/src/com/sun/max/asm/Assembler.java @ 3733:e233f5660da4
Added Java files from Maxine project.
author | Thomas Wuerthinger <thomas.wuerthinger@oracle.com> |
---|---|
date | Sat, 17 Dec 2011 19:59:18 +0100 |
parents | |
children | bc8527f3071c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.max.asmdis/src/com/sun/max/asm/Assembler.java Sat Dec 17 19:59:18 2011 +0100 @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2007, 2011, 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 com.sun.max.asm; + +import java.io.*; +import java.util.*; + +import com.sun.max.lang.*; +import com.sun.max.program.*; + +/** + * Assembler base class. + */ +public abstract class Assembler { + + private final Directives directives; + + protected Assembler(byte byteData, boolean isValidCode) { + this.directives = new Directives(byteData, isValidCode); + } + + public abstract ISA isa(); + + public final Directives directives() { + return directives; + } + + /** + * Gets the width of a word on machine for which the code is being assembled. + */ + public abstract WordWidth wordWidth(); + + /** + * Resets any internal state in this assembler to the equivalent state it had when first constructed. + * <p> + * This method is overridden as needed to ensure the state in subclasses is reset as well. Any + * overriding implementation must call this method in its superclass. + */ + public Assembler reset() { + boundLabels.clear(); + assembledObjects.clear(); + mutableAssembledObjects.clear(); + padOutput = false; + potentialExpansionSize = 0; + selectingLabelInstructions = true; + stream.reset(); + if (this instanceof Assembler32) { + final Assembler32 assembler32 = (Assembler32) this; + assembler32.setStartAddress(0); + } else if (this instanceof Assembler64) { + final Assembler64 assembler64 = (Assembler64) this; + assembler64.setStartAddress(0); + } + return this; + } + + /** + * A facility for including output during assembly that may not necessarily be decoded interpreted as code. + * + */ + public final class Directives { + + private final byte padByte; + private final boolean isValidCode; + + public Directives(byte padByte, boolean isValidCode) { + this.padByte = padByte; + this.isValidCode = isValidCode; + } + /** + * Inserts as many {@linkplain #padByte pad bytes} as necessary to ensure that the next assembled object starts + * at an address aligned by a given number. + * + * @param alignment + * the next assembled object is guaranteed to start at the next highest address starting at the + * current address that is divisible by this value. Note that this computed address will be the + * current address if the current address is already aligned by {@code alignment} + */ + private void alignIfSpaceLeftSmallerThan(int alignment, int requiredSpace, int alignmentStart) { + final int startPosition = currentPosition(); + + // We avoid sign problems with '%' below by masking off the sign bit: + final long unsignedAddend = (baseAddress() + startPosition + alignmentStart) & Long.MAX_VALUE; + + final int misalignmentSize = (int) (unsignedAddend % alignment); + final int padSize = misalignmentSize > 0 ? (alignment - misalignmentSize) : 0; + for (int i = 0; i < padSize; i++) { + emitByte(padByte); + } + new AlignmentPadding(Assembler.this, startPosition, currentPosition(), alignment, alignmentStart, requiredSpace, padByte) { + public boolean isCode() { + return isValidCode; + } + }; + } + + public void align(int alignment) { + alignIfSpaceLeftSmallerThan(alignment, alignment, 0); + } + + /** + * Enforce that the next assembled object fits within an aligned chunk. + * The specified space required by the next assembled object must be smaller or equals to the specified alignment. If the object cannot fit in the current chunk, + * Inserts as many {@linkplain #padByte pad bytes} as necessary to ensure that the next assembled object starts + * at the next alignment. + * + * @param alignment + * the next assembled object is guaranteed to fit in within a block of memory whose starting address is the next + * address starting at the + * current address that is divisible by this value. + * @param requiredSpace size of the next assembled object; it must be smaller or equals to alignment + */ + public boolean align(int alignment, int requiredSpace) { + if (alignment < requiredSpace) { + return false; + } + alignIfSpaceLeftSmallerThan(alignment, requiredSpace, 0); + return true; + } + + /** + * Inserts as many {@linkplain #padByte pad bytes} as necessary to ensure that the nth byte within the next assembled object starts + * at an address aligned by a given number. + * @param alignment + * @param alignmentStart where the alignment should start within the next assembled object. + */ + public void alignAfter(int alignment, int alignmentStart) { + alignIfSpaceLeftSmallerThan(alignment, alignment, alignmentStart); + } + + public void inlineByte(byte byteValue) { + addInlineData(currentPosition(), Bytes.SIZE); + emitByte(byteValue); + } + + public void inlineByteArray(byte[] byteArrayValue) { + addInlineData(currentPosition(), byteArrayValue.length); + emitByteArray(byteArrayValue, 0, byteArrayValue.length); + } + + public void inlineByteArray(byte[] byteArrayValue, int offset, int length) { + addInlineData(currentPosition(), length); + emitByteArray(byteArrayValue, offset, length); + } + + public void inlineShort(short shortValue) { + addInlineData(currentPosition(), Shorts.SIZE); + emitShort(shortValue); + } + + public void inlineInt(int intValue) { + addInlineData(currentPosition(), Ints.SIZE); + emitInt(intValue); + } + + public void inlineLong(long longValue) { + addInlineData(currentPosition(), Longs.SIZE); + emitLong(longValue); + } + + /** + * Inlines the absolute address of a position (represented by a given label) in the assembled code. + * The absolute address is calculated as {@code baseAddress() + label.position()}. The size + * of the inlined address is determined by {@link Assembler#wordWidth()}. + * + * @param label the label whose absolute address is to be inlined + */ + public AddressLiteral inlineAddress(Label label) { + final int startPosition = currentPosition(); + // Emit placeholder bytes + final WordWidth width = wordWidth(); + for (int i = 0; i < width.numberOfBytes; i++) { + emitByte((byte) 0); + } + final AddressLiteral addressLiteral = new AddressLiteral(Assembler.this, startPosition, currentPosition(), label); + assert addressLiteral.size() == width.numberOfBytes; + return addressLiteral; + } + + /** + * Inlines the offset between two positions (represented by given labels) in the assembled code. + * + * @param base the label whose position marks the base of the offset + * @param target the label whose position marks the target of the offset + * @param width the fixed size to be used for the offset + */ + public OffsetLiteral inlineOffset(Label target, Label base, WordWidth width) { + final int startPosition = currentPosition(); + for (int i = 0; i < width.numberOfBytes; i++) { + emitByte((byte) 0); + } + final OffsetLiteral offsetLiteral = new OffsetLiteral(Assembler.this, startPosition, currentPosition(), target, base); + assert offsetLiteral.size() == width.numberOfBytes; + return offsetLiteral; + } + } + + /** + * Gets the number of bytes that have been written to the underlying output stream. + */ + public int currentPosition() { + return stream.size(); + } + + /** + * Gets the start address of the code assembled by this assembler. + */ + public abstract long baseAddress(); + + private ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + protected void emitByte(int byteValue) { + stream.write(byteValue); + } + + protected void emitZeroes(int count) { + for (int i = 0; i < count; ++i) { + stream.write(0); + } + } + + protected abstract void emitShort(short shortValue); + + protected abstract void emitInt(int intValue); + + protected abstract void emitLong(long longValue); + + public void emitByteArray(byte[] byteArrayValue, int off, int len) { + stream.write(byteArrayValue, off, len); + } + + private boolean selectingLabelInstructions = true; + + boolean selectingLabelInstructions() { + return selectingLabelInstructions; + } + + private final Set<Label> boundLabels = Collections.newSetFromMap(new IdentityHashMap<Label, Boolean>()); + + public Set<Label> boundLabels() { + return boundLabels; + } + + /** + * Binds a given label to the current position in the assembler's instruction stream. The assembler may update the + * label's position if any emitted instructions change lengths, so that this label keeps addressing the same logical + * position. + * + * @param label + * the label that is to be bound to the current position + * + * @see Label#fix32 + */ + public final void bindLabel(Label label) { + label.bind(currentPosition()); + boundLabels.add(label); + } + + private final List<AssembledObject> assembledObjects = new LinkedList<AssembledObject>(); + private final List<MutableAssembledObject> mutableAssembledObjects = new LinkedList<MutableAssembledObject>(); + + private int potentialExpansionSize; + + /** + * Adds the description of an instruction that is fixed in size. + * + * @param fixedSizeAssembledObject + */ + void addFixedSizeAssembledObject(AssembledObject fixedSizeAssembledObject) { + assembledObjects.add(fixedSizeAssembledObject); + if (fixedSizeAssembledObject instanceof MutableAssembledObject) { + mutableAssembledObjects.add((MutableAssembledObject) fixedSizeAssembledObject); + } + } + + /** + * Adds the description of an instruction that can change in size, depending on where it is located in the + * instruction and/or where another object it addresses is located in the instruction stream. + * + * @param spanDependentInstruction + */ + void addSpanDependentInstruction(InstructionWithOffset spanDependentInstruction) { + assembledObjects.add(spanDependentInstruction); + mutableAssembledObjects.add(spanDependentInstruction); + // A span-dependent instruction's offset operand can potentially grow from 8 bits to 32 bits. + // Also, some instructions need an extra byte for encoding when not using an 8-bit operand. + // Together, this might enlarge every span-dependent label instruction by maximally 4 bytes. + potentialExpansionSize += 4; + } + + void addAlignmentPadding(AlignmentPadding alignmentPadding) { + assembledObjects.add(alignmentPadding); + mutableAssembledObjects.add(alignmentPadding); + potentialExpansionSize += alignmentPadding.alignment() - alignmentPadding.size(); + } + + void addInlineData(int startPosition, int size) { + assembledObjects.add(new AssembledObject(startPosition, startPosition + size) { + public boolean isCode() { + return false; + } + }); + } + + private void gatherLabels() throws AssemblyException { + for (AssembledObject assembledObject : assembledObjects) { + if (assembledObject instanceof InstructionWithLabel) { + final InstructionWithLabel labelInstruction = (InstructionWithLabel) assembledObject; + switch (labelInstruction.label().state()) { + case UNASSIGNED: + throw new AssemblyException("unassigned label"); + case BOUND: + boundLabels.add(labelInstruction.label()); + break; + default: + break; + } + } + } + } + + private boolean updateSpanDependentInstruction(InstructionWithOffset instruction) throws AssemblyException { + if (!instruction.updateLabelSize()) { + return false; + } + final int oldSize = instruction.size(); + final int oldEndPosition = instruction.endPosition(); + stream.reset(); + instruction.assemble(); + final int newSize = stream.toByteArray().length; + instruction.setSize(newSize); + final int delta = newSize - oldSize; + adjustMutableAssembledObjects(delta, oldEndPosition, null); + return true; + } + + private boolean updateAlignmentPadding(AlignmentPadding alignmentPadding) throws AssemblyException { + final int oldSize = alignmentPadding.size(); + final int oldEndPosition = alignmentPadding.endPosition(); + alignmentPadding.updatePadding(); + final int newSize = alignmentPadding.size(); + if (oldSize != newSize) { + // Only if the padding expanded will subsequent objects in the stream need to be adjusted + final int delta = newSize - oldSize; + adjustMutableAssembledObjects(delta, oldEndPosition, alignmentPadding); + return true; + } + return false; + } + + /** + * Adjusts the position of all the mutable objects that are currently at or after a given position. + * + * @param delta the amount by which the position of each mutable object is to be adjusted + * @param startPosition only mutable objects whose current {@linkplain AssembledObject#startPosition() start + * position} is equal to or greater than this value are adjusted + * @param adjustedPadding the padding object whose adjustment caused the need for this call. If this call was made + * for some other reason than having adjusted a padding object's size, then this value will be null. In this + * value is not null, then its position will not be adjusted by this call. + */ + private void adjustMutableAssembledObjects(int delta, int startPosition, AlignmentPadding adjustedPadding) throws AssemblyException { + for (Label label : boundLabels) { + if (label.position() >= startPosition) { + label.adjust(delta); + } + } + + for (MutableAssembledObject mutableAssembledObject : mutableAssembledObjects) { + if (mutableAssembledObject != adjustedPadding && mutableAssembledObject.startPosition() >= startPosition) { + mutableAssembledObject.adjust(delta); + } + } + } + + private void updateSpanDependentVariableInstructions() throws AssemblyException { + boolean changed; + do { + changed = false; + for (MutableAssembledObject mutableAssembledObject : mutableAssembledObjects) { + if (mutableAssembledObject instanceof InstructionWithOffset) { + changed |= updateSpanDependentInstruction((InstructionWithOffset) mutableAssembledObject); + } else if (mutableAssembledObject instanceof AlignmentPadding) { + changed |= updateAlignmentPadding((AlignmentPadding) mutableAssembledObject); + } + } + } while (changed); + } + + private int writeOutput(OutputStream outputStream, byte[] initialBytes, InlineDataRecorder inlineDataRecorder) throws IOException, AssemblyException { + selectingLabelInstructions = false; + int bytesWritten = 0; + try { + int initialOffset = 0; + for (AssembledObject assembledObject : assembledObjects) { + if (inlineDataRecorder != null && !assembledObject.isCode()) { + inlineDataRecorder.add(new InlineDataDescriptor.ByteData(assembledObject.startPosition(), assembledObject.size())); + } + + if (assembledObject instanceof MutableAssembledObject) { + final MutableAssembledObject mutableAssembledObject = (MutableAssembledObject) assembledObject; + + // Copy the original assembler output between the end of the last mutable assembled object and the start of the current one + final int length = mutableAssembledObject.initialStartPosition() - initialOffset; + outputStream.write(initialBytes, initialOffset, length); + bytesWritten += length; + + // Now (re)assemble the mutable assembled object + stream.reset(); + mutableAssembledObject.assemble(); + stream.writeTo(outputStream); + bytesWritten += stream.size(); + initialOffset = mutableAssembledObject.initialEndPosition(); + } else { + // Copy the original assembler output between the end of the last assembled object and the end of current one + final int length = assembledObject.endPosition() - initialOffset; + outputStream.write(initialBytes, initialOffset, length); + bytesWritten += length; + initialOffset = assembledObject.endPosition(); + } + } + + // Copy the original assembler output (if any) after the last mutable assembled object + outputStream.write(initialBytes, initialOffset, initialBytes.length - initialOffset); + bytesWritten += initialBytes.length - initialOffset; + + if (padOutput) { + final int padding = (initialBytes.length + potentialExpansionSize) - bytesWritten; + assert padding >= 0; + if (padding > 0) { + stream.reset(); + emitPadding(padding); + stream.writeTo(outputStream); + bytesWritten += padding; + } + } + return bytesWritten; + } finally { + selectingLabelInstructions = true; + } + } + + /** + * Emits padding to the instruction stream in the form of NOP instructions. + * + * @param numberOfBytes + * @throws AssemblyException if exactly {@code numberOfBytes} cannot be emitted as a sequence of one or more valid NOP instructions + */ + protected abstract void emitPadding(int numberOfBytes) throws AssemblyException; + + /** + * Writes the object code assembled so far to a given output stream. + * + * @return the number of bytes written {@code outputStream} + * @throws AssemblyException + * if there any problem with binding labels to addresses + */ + public int output(OutputStream outputStream, InlineDataRecorder inlineDataRecorder) throws IOException, AssemblyException { + final int upperLimitForCurrentOutputSize = upperLimitForCurrentOutputSize(); + final byte[] initialBytes = stream.toByteArray(); + gatherLabels(); + updateSpanDependentVariableInstructions(); + final int bytesWritten = writeOutput(outputStream, initialBytes, inlineDataRecorder); + assert !padOutput || upperLimitForCurrentOutputSize == bytesWritten; + return bytesWritten; + } + + /** + * Gets the maximum size of the code array that would be assembled by a call to {@link #output(OutputStream)} or + * {@link #toByteArray()}. For a variable sized instruction set (e.g. x86), the exact size may be known until the + * code is assembled as the size of certain instructions depends on their position in the instruction and/or the + * {@linkplain #baseAddress() base address} at which the code is being assembled. + * <p> + * <b>Note that any subsequent call that adds a new instruction to the instruction stream invalidates the value + * returned by this method.</b> + */ + public int upperLimitForCurrentOutputSize() { + return currentPosition() + potentialExpansionSize; + } + + private boolean padOutput; + + /** + * Sets or unsets the flag determining if the code assembled by a call to {@link #output(OutputStream)} or + * {@link #toByteArray()} should be padded with NOPs at the end to ensure that the code size equals the value + * returned by {@link #upperLimitForCurrentOutputSize()}. This default value of the flag is {@code false}. + */ + public void setPadOutput(boolean flag) { + padOutput = flag; + } + + /** + * Returns the object code assembled so far in a byte array. + * + * @throws AssemblyException + * if there any problem with binding labels to addresses + */ + public byte[] toByteArray(InlineDataRecorder inlineDataRecorder) throws AssemblyException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(upperLimitForCurrentOutputSize()); + try { + output(baos, inlineDataRecorder); + baos.close(); + final byte[] result = baos.toByteArray(); + return result; + } catch (IOException ioException) { + throw ProgramError.unexpected("IOException during output to byte array", ioException); + } + } + + public byte[] toByteArray() throws AssemblyException { + return toByteArray(null); + } + + /** + * @see Label#fix32(int) + */ + protected void fixLabel32(Label label, int address32) { + label.fix32(address32); + } + + /** + * @see Label#fix64(long) + */ + protected void fixLabel64(Label label, long address64) { + label.fix64(address64); + } + + protected int address32(Label label) throws AssemblyException { + return label.address32(); + } + + protected long address64(Label label) throws AssemblyException { + return label.address64(); + } + + protected void checkConstraint(boolean passed, String expression) { + if (!passed) { + throw new IllegalArgumentException(expression); + } + } + + /** + * Calculate the difference between two Labels. This works whether the labels + * are fixed or bound. + * @throws AssemblyException + */ + public int labelOffsetRelative(Label label, Label relativeTo) throws AssemblyException { + return labelOffsetRelative(label, 0) - labelOffsetRelative(relativeTo, 0); + } + + /** + * Calculate the difference between a Label and a position within the assembled code. + * @throws AssemblyException + */ + public int labelOffsetRelative(Label label, int position) throws AssemblyException { + switch (label.state()) { + case BOUND: { + return label.position() - position; + } + case FIXED_32: { + final Assembler32 assembler32 = (Assembler32) this; + return assembler32.address(label) - (assembler32.startAddress() + position); + } + case FIXED_64: { + final Assembler64 assembler64 = (Assembler64) this; + final long offset64 = assembler64.address(label) - (assembler64.startAddress() + position); + if (Longs.numberOfEffectiveSignedBits(offset64) > 32) { + throw new AssemblyException("fixed 64-bit label out of 32-bit range"); + } + return (int) offset64; + } + default: { + throw new AssemblyException("unassigned label"); + } + } + } + + /** + * Calculate the difference between a Label and an assembled object. + * Different CPUs have different conventions for which end of an + * instruction to measure from. + * @throws AssemblyException + */ + public final int offsetInstructionRelative(Label label, AssemblyObject assembledObject) throws AssemblyException { + final int position = (isa().relativeBranchFromStart) ? assembledObject.startPosition() : assembledObject.endPosition(); + return labelOffsetRelative(label, position); + } +}