blob: 7c0d184e02c87de42a4aba40a3265f7df0869767 [file] [log] [blame]
/*
* Copyright (c) 2013, 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.asm.aarch64;
import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.BASE_REGISTER_ONLY;
import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.EXTENDED_REGISTER_OFFSET;
import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_SCALED;
import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_UNSCALED;
import static org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode.REGISTER_OFFSET;
import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.ADD_TO_BASE;
import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.ADD_TO_INDEX;
import static org.graalvm.compiler.asm.aarch64.AArch64MacroAssembler.AddressGenerationPlan.WorkPlan.NO_WORK;
import static jdk.vm.ci.aarch64.AArch64.CPU;
import static jdk.vm.ci.aarch64.AArch64.r8;
import static jdk.vm.ci.aarch64.AArch64.r9;
import static jdk.vm.ci.aarch64.AArch64.sp;
import static jdk.vm.ci.aarch64.AArch64.zr;
import org.graalvm.compiler.asm.AbstractAddress;
import org.graalvm.compiler.asm.Label;
import org.graalvm.compiler.asm.NumUtil;
import org.graalvm.compiler.debug.GraalError;
import jdk.vm.ci.aarch64.AArch64;
import jdk.vm.ci.code.Register;
import jdk.vm.ci.code.TargetDescription;
public class AArch64MacroAssembler extends AArch64Assembler {
private final ScratchRegister[] scratchRegister = new ScratchRegister[]{new ScratchRegister(r8), new ScratchRegister(r9)};
// Points to the next free scratch register
private int nextFreeScratchRegister = 0;
public AArch64MacroAssembler(TargetDescription target) {
super(target);
}
public class ScratchRegister implements AutoCloseable {
private final Register register;
public ScratchRegister(Register register) {
this.register = register;
}
public Register getRegister() {
return register;
}
@Override
public void close() {
assert nextFreeScratchRegister > 0 : "Close called too often";
nextFreeScratchRegister--;
}
}
public ScratchRegister getScratchRegister() {
return scratchRegister[nextFreeScratchRegister++];
}
/**
* Specifies what actions have to be taken to turn an arbitrary address of the form
* {@code base + displacement [+ index [<< scale]]} into a valid AArch64Address.
*/
public static class AddressGenerationPlan {
public final WorkPlan workPlan;
public final AArch64Address.AddressingMode addressingMode;
public final boolean needsScratch;
public enum WorkPlan {
/**
* Can be used as-is without extra work.
*/
NO_WORK,
/**
* Add scaled displacement to index register.
*/
ADD_TO_INDEX,
/**
* Add unscaled displacement to base register.
*/
ADD_TO_BASE,
}
/**
* @param workPlan Work necessary to generate a valid address.
* @param addressingMode Addressing mode of generated address.
* @param needsScratch True if generating address needs a scatch register, false otherwise.
*/
public AddressGenerationPlan(WorkPlan workPlan, AArch64Address.AddressingMode addressingMode, boolean needsScratch) {
this.workPlan = workPlan;
this.addressingMode = addressingMode;
this.needsScratch = needsScratch;
}
}
/**
* Generates an addressplan for an address of the form
* {@code base + displacement [+ index [<< log2(transferSize)]]} with the index register and
* scaling being optional.
*
* @param displacement an arbitrary displacement.
* @param hasIndexRegister true if the address uses an index register, false otherwise. non null
* @param transferSize the memory transfer size in bytes. The log2 of this specifies how much
* the index register is scaled. If 0 no scaling is assumed. Can be 0, 1, 2, 4 or 8.
* @return AddressGenerationPlan that specifies the actions necessary to generate a valid
* AArch64Address for the given parameters.
*/
public static AddressGenerationPlan generateAddressPlan(long displacement, boolean hasIndexRegister, int transferSize) {
assert transferSize == 0 || transferSize == 1 || transferSize == 2 || transferSize == 4 || transferSize == 8;
boolean indexScaled = transferSize != 0;
int log2Scale = NumUtil.log2Ceil(transferSize);
long scaledDisplacement = displacement >> log2Scale;
boolean displacementScalable = indexScaled && (displacement & (transferSize - 1)) == 0;
if (displacement == 0) {
// register offset without any work beforehand.
return new AddressGenerationPlan(NO_WORK, REGISTER_OFFSET, false);
} else {
if (hasIndexRegister) {
if (displacementScalable) {
boolean needsScratch = !isArithmeticImmediate(scaledDisplacement);
return new AddressGenerationPlan(ADD_TO_INDEX, REGISTER_OFFSET, needsScratch);
} else {
boolean needsScratch = !isArithmeticImmediate(displacement);
return new AddressGenerationPlan(ADD_TO_BASE, REGISTER_OFFSET, needsScratch);
}
} else {
if (NumUtil.isSignedNbit(9, displacement)) {
return new AddressGenerationPlan(NO_WORK, IMMEDIATE_UNSCALED, false);
} else if (displacementScalable && NumUtil.isUnsignedNbit(12, scaledDisplacement)) {
return new AddressGenerationPlan(NO_WORK, IMMEDIATE_SCALED, false);
} else {
boolean needsScratch = !isArithmeticImmediate(displacement);
return new AddressGenerationPlan(ADD_TO_BASE, REGISTER_OFFSET, needsScratch);
}
}
}
}
/**
* Returns an AArch64Address pointing to
* {@code base + displacement + index << log2(transferSize)}.
*
* @param base general purpose register. May not be null or the zero register.
* @param displacement arbitrary displacement added to base.
* @param index general purpose register. May not be null or the stack pointer.
* @param signExtendIndex if true consider index register a word register that should be
* sign-extended before being added.
* @param transferSize the memory transfer size in bytes. The log2 of this specifies how much
* the index register is scaled. If 0 no scaling is assumed. Can be 0, 1, 2, 4 or 8.
* @param additionalReg additional register used either as a scratch register or as part of the
* final address, depending on whether allowOverwrite is true or not. May not be null
* or stackpointer.
* @param allowOverwrite if true allows to change value of base or index register to generate
* address.
* @return AArch64Address pointing to memory at
* {@code base + displacement + index << log2(transferSize)}.
*/
public AArch64Address makeAddress(Register base, long displacement, Register index, boolean signExtendIndex, int transferSize, Register additionalReg, boolean allowOverwrite) {
AddressGenerationPlan plan = generateAddressPlan(displacement, !index.equals(zr), transferSize);
assert allowOverwrite || !zr.equals(additionalReg) || plan.workPlan == NO_WORK;
assert !plan.needsScratch || !zr.equals(additionalReg);
int log2Scale = NumUtil.log2Ceil(transferSize);
long scaledDisplacement = displacement >> log2Scale;
Register newIndex = index;
Register newBase = base;
int immediate;
switch (plan.workPlan) {
case NO_WORK:
if (plan.addressingMode == IMMEDIATE_SCALED) {
immediate = (int) scaledDisplacement;
} else {
immediate = (int) displacement;
}
break;
case ADD_TO_INDEX:
newIndex = allowOverwrite ? index : additionalReg;
if (plan.needsScratch) {
mov(additionalReg, scaledDisplacement);
add(signExtendIndex ? 32 : 64, newIndex, index, additionalReg);
} else {
add(signExtendIndex ? 32 : 64, newIndex, index, (int) scaledDisplacement);
}
immediate = 0;
break;
case ADD_TO_BASE:
newBase = allowOverwrite ? base : additionalReg;
if (plan.needsScratch) {
mov(additionalReg, displacement);
add(64, newBase, base, additionalReg);
} else {
add(64, newBase, base, (int) displacement);
}
immediate = 0;
break;
default:
throw GraalError.shouldNotReachHere();
}
AArch64Address.AddressingMode addressingMode = plan.addressingMode;
ExtendType extendType = null;
if (addressingMode == REGISTER_OFFSET) {
if (newIndex.equals(zr)) {
addressingMode = BASE_REGISTER_ONLY;
} else if (signExtendIndex) {
addressingMode = EXTENDED_REGISTER_OFFSET;
extendType = ExtendType.SXTW;
}
}
return AArch64Address.createAddress(addressingMode, newBase, newIndex, immediate, transferSize != 0, extendType);
}
/**
* Returns an AArch64Address pointing to {@code base + displacement}. Specifies the memory
* transfer size to allow some optimizations when building the address.
*
* @param base general purpose register. May not be null or the zero register.
* @param displacement arbitrary displacement added to base.
* @param transferSize the memory transfer size in bytes.
* @param additionalReg additional register used either as a scratch register or as part of the
* final address, depending on whether allowOverwrite is true or not. May not be
* null, zero register or stackpointer.
* @param allowOverwrite if true allows to change value of base or index register to generate
* address.
* @return AArch64Address pointing to memory at {@code base + displacement}.
*/
public AArch64Address makeAddress(Register base, long displacement, Register additionalReg, int transferSize, boolean allowOverwrite) {
assert additionalReg.getRegisterCategory().equals(CPU);
return makeAddress(base, displacement, zr, /* sign-extend */false, transferSize, additionalReg, allowOverwrite);
}
/**
* Returns an AArch64Address pointing to {@code base + displacement}. Fails if address cannot be
* represented without overwriting base register or using a scratch register.
*
* @param base general purpose register. May not be null or the zero register.
* @param displacement arbitrary displacement added to base.
* @param transferSize the memory transfer size in bytes. The log2 of this specifies how much
* the index register is scaled. If 0 no scaling is assumed. Can be 0, 1, 2, 4 or 8.
* @return AArch64Address pointing to memory at {@code base + displacement}.
*/
public AArch64Address makeAddress(Register base, long displacement, int transferSize) {
return makeAddress(base, displacement, zr, /* signExtend */false, transferSize, zr, /* allowOverwrite */false);
}
/**
* Loads memory address into register.
*
* @param dst general purpose register. May not be null, zero-register or stackpointer.
* @param address address whose value is loaded into dst. May not be null,
* {@link org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode#IMMEDIATE_POST_INDEXED
* POST_INDEXED} or
* {@link org.graalvm.compiler.asm.aarch64.AArch64Address.AddressingMode#IMMEDIATE_PRE_INDEXED
* IMMEDIATE_PRE_INDEXED}
* @param transferSize the memory transfer size in bytes. The log2 of this specifies how much
* the index register is scaled. Can be 1, 2, 4 or 8.
*/
public void loadAddress(Register dst, AArch64Address address, int transferSize) {
assert transferSize == 1 || transferSize == 2 || transferSize == 4 || transferSize == 8;
assert dst.getRegisterCategory().equals(CPU);
int shiftAmt = NumUtil.log2Ceil(transferSize);
switch (address.getAddressingMode()) {
case IMMEDIATE_SCALED:
int scaledImmediate = address.getImmediateRaw() << shiftAmt;
int lowerBits = scaledImmediate & NumUtil.getNbitNumberInt(12);
int higherBits = scaledImmediate & ~NumUtil.getNbitNumberInt(12);
boolean firstAdd = true;
if (lowerBits != 0) {
add(64, dst, address.getBase(), lowerBits);
firstAdd = false;
}
if (higherBits != 0) {
Register src = firstAdd ? address.getBase() : dst;
add(64, dst, src, higherBits);
}
break;
case IMMEDIATE_UNSCALED:
int immediate = address.getImmediateRaw();
add(64, dst, address.getBase(), immediate);
break;
case REGISTER_OFFSET:
add(64, dst, address.getBase(), address.getOffset(), ShiftType.LSL, address.isScaled() ? shiftAmt : 0);
break;
case EXTENDED_REGISTER_OFFSET:
add(64, dst, address.getBase(), address.getOffset(), address.getExtendType(), address.isScaled() ? shiftAmt : 0);
break;
case PC_LITERAL:
super.adr(dst, address.getImmediateRaw());
break;
case BASE_REGISTER_ONLY:
movx(dst, address.getBase());
break;
default:
throw GraalError.shouldNotReachHere();
}
}
public void movx(Register dst, Register src) {
mov(64, dst, src);
}
public void mov(int size, Register dst, Register src) {
if (dst.equals(sp) || src.equals(sp)) {
add(size, dst, src, 0);
} else {
or(size, dst, zr, src);
}
}
/**
* Generates a 64-bit immediate move code sequence.
*
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param imm
*/
private void mov64(Register dst, long imm) {
// We have to move all non zero parts of the immediate in 16-bit chunks
boolean firstMove = true;
for (int offset = 0; offset < 64; offset += 16) {
int chunk = (int) (imm >> offset) & NumUtil.getNbitNumberInt(16);
if (chunk == 0) {
continue;
}
if (firstMove) {
movz(64, dst, chunk, offset);
firstMove = false;
} else {
movk(64, dst, chunk, offset);
}
}
assert !firstMove;
}
/**
* Loads immediate into register.
*
* @param dst general purpose register. May not be null, zero-register or stackpointer.
* @param imm immediate loaded into register.
*/
public void mov(Register dst, long imm) {
assert dst.getRegisterCategory().equals(CPU);
if (imm == 0L) {
movx(dst, zr);
} else if (LogicalImmediateTable.isRepresentable(true, imm) != LogicalImmediateTable.Representable.NO) {
or(64, dst, zr, imm);
} else if (imm >> 32 == -1L && (int) imm < 0 && LogicalImmediateTable.isRepresentable((int) imm) != LogicalImmediateTable.Representable.NO) {
// If the higher 32-bit are 1s and the sign bit of the lower 32-bits is set *and* we can
// represent the lower 32 bits as a logical immediate we can create the lower 32-bit and
// then sign extend
// them. This allows us to cover immediates like ~1L with 2 instructions.
mov(dst, (int) imm);
sxt(64, 32, dst, dst);
} else {
mov64(dst, imm);
}
}
/**
* Loads immediate into register.
*
* @param dst general purpose register. May not be null, zero-register or stackpointer.
* @param imm immediate loaded into register.
*/
public void mov(Register dst, int imm) {
mov(dst, imm & 0xFFFF_FFFFL);
}
/**
* Generates a 48-bit immediate move code sequence. The immediate may later be updated by
* HotSpot.
*
* In AArch64 mode the virtual address space is 48-bits in size, so we only need three
* instructions to create a patchable instruction sequence that can reach anywhere.
*
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param imm
*/
public void movNativeAddress(Register dst, long imm) {
assert (imm & 0xFFFF_0000_0000_0000L) == 0;
// We have to move all non zero parts of the immediate in 16-bit chunks
boolean firstMove = true;
for (int offset = 0; offset < 48; offset += 16) {
int chunk = (int) (imm >> offset) & NumUtil.getNbitNumberInt(16);
if (firstMove) {
movz(64, dst, chunk, offset);
firstMove = false;
} else {
movk(64, dst, chunk, offset);
}
}
assert !firstMove;
}
/**
* @return Number of instructions necessary to load immediate into register.
*/
public static int nrInstructionsToMoveImmediate(long imm) {
if (imm == 0L || LogicalImmediateTable.isRepresentable(true, imm) != LogicalImmediateTable.Representable.NO) {
return 1;
}
if (imm >> 32 == -1L && (int) imm < 0 && LogicalImmediateTable.isRepresentable((int) imm) != LogicalImmediateTable.Representable.NO) {
// If the higher 32-bit are 1s and the sign bit of the lower 32-bits is set *and* we can
// represent the lower 32 bits as a logical immediate we can create the lower 32-bit and
// then sign extend
// them. This allows us to cover immediates like ~1L with 2 instructions.
return 2;
}
int nrInstructions = 0;
for (int offset = 0; offset < 64; offset += 16) {
int part = (int) (imm >> offset) & NumUtil.getNbitNumberInt(16);
if (part != 0) {
nrInstructions++;
}
}
return nrInstructions;
}
/**
* Loads a srcSize value from address into rt sign-extending it if necessary.
*
* @param targetSize size of target register in bits. Must be 32 or 64.
* @param srcSize size of memory read in bits. Must be 8, 16 or 32 and smaller or equal to
* targetSize.
* @param rt general purpose register. May not be null or stackpointer.
* @param address all addressing modes allowed. May not be null.
*/
@Override
public void ldrs(int targetSize, int srcSize, Register rt, AArch64Address address) {
assert targetSize == 32 || targetSize == 64;
assert srcSize <= targetSize;
if (targetSize == srcSize) {
super.ldr(srcSize, rt, address);
} else {
super.ldrs(targetSize, srcSize, rt, address);
}
}
/**
* Conditional move. dst = src1 if condition else src2.
*
* @param size register size. Has to be 32 or 64.
* @param result general purpose register. May not be null or the stackpointer.
* @param trueValue general purpose register. May not be null or the stackpointer.
* @param falseValue general purpose register. May not be null or the stackpointer.
* @param cond any condition flag. May not be null.
*/
public void cmov(int size, Register result, Register trueValue, Register falseValue, ConditionFlag cond) {
super.csel(size, result, trueValue, falseValue, cond);
}
/**
* Conditional set. dst = 1 if condition else 0.
*
* @param dst general purpose register. May not be null or stackpointer.
* @param condition any condition. May not be null.
*/
public void cset(Register dst, ConditionFlag condition) {
super.csinc(32, dst, zr, zr, condition.negate());
}
/**
* dst = src1 + src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void add(int size, Register dst, Register src1, Register src2) {
super.add(size, dst, src1, src2, ShiftType.LSL, 0);
}
/**
* dst = src1 + src2 and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void adds(int size, Register dst, Register src1, Register src2) {
super.adds(size, dst, src1, src2, getNopExtendType(size), 0);
}
/**
* dst = src1 - src2 and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void subs(int size, Register dst, Register src1, Register src2) {
super.subs(size, dst, src1, src2, getNopExtendType(size), 0);
}
/**
* Returns the ExtendType for the given size that corresponds to a no-op.
*
* I.e. when doing add X0, X1, X2, the actual instruction has the form add X0, X1, X2 UXTX.
*
* @param size
*/
private static ExtendType getNopExtendType(int size) {
if (size == 64) {
return ExtendType.UXTX;
} else if (size == 32) {
return ExtendType.UXTW;
} else {
throw GraalError.shouldNotReachHere("No-op ");
}
}
/**
* dst = src1 - src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void sub(int size, Register dst, Register src1, Register src2) {
super.sub(size, dst, src1, src2, ShiftType.LSL, 0);
}
/**
* dst = src1 + shiftType(src2, shiftAmt & (size - 1)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
* @param shiftType any type but ROR.
* @param shiftAmt arbitrary shift amount.
*/
@Override
public void add(int size, Register dst, Register src1, Register src2, ShiftType shiftType, int shiftAmt) {
int shift = clampShiftAmt(size, shiftAmt);
super.add(size, dst, src1, src2, shiftType, shift);
}
/**
* dst = src1 + shiftType(src2, shiftAmt & (size-1)) and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
* @param shiftType any type but ROR.
* @param shiftAmt arbitrary shift amount.
*/
@Override
public void sub(int size, Register dst, Register src1, Register src2, ShiftType shiftType, int shiftAmt) {
int shift = clampShiftAmt(size, shiftAmt);
super.sub(size, dst, src1, src2, shiftType, shift);
}
/**
* dst = -src1.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
*/
public void neg(int size, Register dst, Register src) {
sub(size, dst, zr, src);
}
/**
* dst = src + immediate.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
* @param immediate arithmetic immediate
*/
@Override
public void add(int size, Register dst, Register src, int immediate) {
if (immediate < 0) {
sub(size, dst, src, -immediate);
} else if (!(dst.equals(src) && immediate == 0)) {
super.add(size, dst, src, immediate);
}
}
/**
* dst = src + aimm and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or zero-register.
* @param immediate arithmetic immediate.
*/
@Override
public void adds(int size, Register dst, Register src, int immediate) {
if (immediate < 0) {
subs(size, dst, src, -immediate);
} else if (!(dst.equals(src) && immediate == 0)) {
super.adds(size, dst, src, immediate);
}
}
/**
* dst = src - immediate.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
* @param immediate arithmetic immediate
*/
@Override
public void sub(int size, Register dst, Register src, int immediate) {
if (immediate < 0) {
add(size, dst, src, -immediate);
} else if (!dst.equals(src) || immediate != 0) {
super.sub(size, dst, src, immediate);
}
}
/**
* dst = src - aimm and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or zero-register.
* @param immediate arithmetic immediate.
*/
@Override
public void subs(int size, Register dst, Register src, int immediate) {
if (immediate < 0) {
adds(size, dst, src, -immediate);
} else if (!dst.equals(src) || immediate != 0) {
super.sub(size, dst, src, immediate);
}
}
/**
* dst = src1 * src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param src1 general purpose register. May not be null or the stackpointer.
* @param src2 general purpose register. May not be null or the stackpointer.
*/
public void mul(int size, Register dst, Register src1, Register src2) {
super.madd(size, dst, src1, src2, zr);
}
/**
* unsigned multiply high. dst = (src1 * src2) >> size
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param src1 general purpose register. May not be null or the stackpointer.
* @param src2 general purpose register. May not be null or the stackpointer.
*/
public void umulh(int size, Register dst, Register src1, Register src2) {
assert size == 32 || size == 64;
if (size == 64) {
super.umulh(dst, src1, src2);
} else {
// xDst = wSrc1 * wSrc2
super.umaddl(dst, src1, src2, zr);
// xDst = xDst >> 32
lshr(64, dst, dst, 32);
}
}
/**
* signed multiply high. dst = (src1 * src2) >> size
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param src1 general purpose register. May not be null or the stackpointer.
* @param src2 general purpose register. May not be null or the stackpointer.
*/
public void smulh(int size, Register dst, Register src1, Register src2) {
assert size == 32 || size == 64;
if (size == 64) {
super.smulh(dst, src1, src2);
} else {
// xDst = wSrc1 * wSrc2
super.smaddl(dst, src1, src2, zr);
// xDst = xDst >> 32
lshr(64, dst, dst, 32);
}
}
/**
* dst = src1 % src2. Signed.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param n numerator. General purpose register. May not be null or the stackpointer.
* @param d denominator. General purpose register. Divisor May not be null or the stackpointer.
*/
public void rem(int size, Register dst, Register n, Register d) {
// There is no irem or similar instruction. Instead we use the relation:
// n % d = n - Floor(n / d) * d if nd >= 0
// n % d = n - Ceil(n / d) * d else
// Which is equivalent to n - TruncatingDivision(n, d) * d
super.sdiv(size, dst, n, d);
super.msub(size, dst, dst, d, n);
}
/**
* dst = src1 % src2. Unsigned.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param n numerator. General purpose register. May not be null or the stackpointer.
* @param d denominator. General purpose register. Divisor May not be null or the stackpointer.
*/
public void urem(int size, Register dst, Register n, Register d) {
// There is no irem or similar instruction. Instead we use the relation:
// n % d = n - Floor(n / d) * d
// Which is equivalent to n - TruncatingDivision(n, d) * d
super.udiv(size, dst, n, d);
super.msub(size, dst, dst, d, n);
}
/**
* Add/subtract instruction encoding supports 12-bit immediate values.
*
* @param imm immediate value to be tested.
* @return true if immediate can be used directly for arithmetic instructions (add/sub), false
* otherwise.
*/
public static boolean isArithmeticImmediate(long imm) {
// If we have a negative immediate we just use the opposite operator. I.e.: x - (-5) == x +
// 5.
return NumUtil.isInt(Math.abs(imm)) && isAimm((int) Math.abs(imm));
}
/**
* Compare instructions are add/subtract instructions and so support 12-bit immediate values.
*
* @param imm immediate value to be tested.
* @return true if immediate can be used directly with comparison instructions, false otherwise.
*/
public static boolean isComparisonImmediate(long imm) {
return isArithmeticImmediate(imm);
}
/**
* Move wide immediate instruction encoding supports 16-bit immediate values which can be
* optionally-shifted by multiples of 16 (i.e. 0, 16, 32, 48).
*
* @return true if immediate can be moved directly into a register, false otherwise.
*/
public static boolean isMovableImmediate(long imm) {
// // Positions of first, respectively last set bit.
// int start = Long.numberOfTrailingZeros(imm);
// int end = 64 - Long.numberOfLeadingZeros(imm);
// int length = end - start;
// if (length > 16) {
// return false;
// }
// // We can shift the necessary part of the immediate (i.e. everything between the first
// and
// // last set bit) by as much as 16 - length around to arrive at a valid shift amount
// int tolerance = 16 - length;
// int prevMultiple = NumUtil.roundDown(start, 16);
// int nextMultiple = NumUtil.roundUp(start, 16);
// return start - prevMultiple <= tolerance || nextMultiple - start <= tolerance;
/*
* This is a bit optimistic because the constant could also be for an arithmetic instruction
* which only supports 12-bits. That case needs to be handled in the backend.
*/
return NumUtil.isInt(Math.abs(imm)) && NumUtil.isUnsignedNbit(16, (int) Math.abs(imm));
}
/**
* dst = src << (shiftAmt & (size - 1)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param src general purpose register. May not be null, stackpointer or zero-register.
* @param shiftAmt amount by which src is shifted.
*/
public void shl(int size, Register dst, Register src, long shiftAmt) {
int shift = clampShiftAmt(size, shiftAmt);
super.ubfm(size, dst, src, (size - shift) & (size - 1), size - 1 - shift);
}
/**
* dst = src1 << (src2 & (size - 1)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
* @param shift general purpose register. May not be null or stackpointer.
*/
public void shl(int size, Register dst, Register src, Register shift) {
super.lsl(size, dst, src, shift);
}
/**
* dst = src >>> (shiftAmt & (size - 1)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param src general purpose register. May not be null, stackpointer or zero-register.
* @param shiftAmt amount by which src is shifted.
*/
public void lshr(int size, Register dst, Register src, long shiftAmt) {
int shift = clampShiftAmt(size, shiftAmt);
super.ubfm(size, dst, src, shift, size - 1);
}
/**
* dst = src1 >>> (src2 & (size - 1)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
* @param shift general purpose register. May not be null or stackpointer.
*/
public void lshr(int size, Register dst, Register src, Register shift) {
super.lsr(size, dst, src, shift);
}
/**
* dst = src >> (shiftAmt & log2(size)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param src general purpose register. May not be null, stackpointer or zero-register.
* @param shiftAmt amount by which src is shifted.
*/
public void ashr(int size, Register dst, Register src, long shiftAmt) {
int shift = clampShiftAmt(size, shiftAmt);
super.sbfm(size, dst, src, shift, size - 1);
}
/**
* dst = src1 >> (src2 & log2(size)).
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
* @param shift general purpose register. May not be null or stackpointer.
*/
public void ashr(int size, Register dst, Register src, Register shift) {
super.asr(size, dst, src, shift);
}
/**
* Clamps shiftAmt into range 0 <= shiftamt < size according to JLS.
*
* @param size size of operation.
* @param shiftAmt arbitrary shift amount.
* @return value between 0 and size - 1 inclusive that is equivalent to shiftAmt according to
* JLS.
*/
private static int clampShiftAmt(int size, long shiftAmt) {
return (int) (shiftAmt & (size - 1));
}
/**
* dst = src1 & src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void and(int size, Register dst, Register src1, Register src2) {
super.and(size, dst, src1, src2, ShiftType.LSL, 0);
}
/**
* dst = src1 ^ src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void eor(int size, Register dst, Register src1, Register src2) {
super.eor(size, dst, src1, src2, ShiftType.LSL, 0);
}
/**
* dst = src1 | src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src1 general purpose register. May not be null or stackpointer.
* @param src2 general purpose register. May not be null or stackpointer.
*/
public void or(int size, Register dst, Register src1, Register src2) {
super.orr(size, dst, src1, src2, ShiftType.LSL, 0);
}
/**
* dst = src | bimm.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or zero-register.
* @param src general purpose register. May not be null or stack-pointer.
* @param bimm logical immediate. See {@link AArch64Assembler.LogicalImmediateTable} for exact
* definition.
*/
public void or(int size, Register dst, Register src, long bimm) {
super.orr(size, dst, src, bimm);
}
/**
* dst = ~src.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stackpointer.
* @param src general purpose register. May not be null or stackpointer.
*/
public void not(int size, Register dst, Register src) {
super.orn(size, dst, zr, src, ShiftType.LSL, 0);
}
/**
* Sign-extend value from src into dst.
*
* @param destSize destination register size. Has to be 32 or 64.
* @param srcSize source register size. May be 8, 16 or 32 and smaller than destSize.
* @param dst general purpose register. May not be null, stackpointer or zero-register.
* @param src general purpose register. May not be null, stackpointer or zero-register.
*/
public void sxt(int destSize, int srcSize, Register dst, Register src) {
assert (destSize == 32 || destSize == 64) && srcSize < destSize;
assert srcSize == 8 || srcSize == 16 || srcSize == 32;
int[] srcSizeValues = {7, 15, 31};
super.sbfm(destSize, dst, src, 0, srcSizeValues[NumUtil.log2Ceil(srcSize / 8)]);
}
/**
* dst = src if condition else -src.
*
* @param size register size. Must be 32 or 64.
* @param dst general purpose register. May not be null or the stackpointer.
* @param src general purpose register. May not be null or the stackpointer.
* @param condition any condition except AV or NV. May not be null.
*/
public void csneg(int size, Register dst, Register src, ConditionFlag condition) {
super.csneg(size, dst, src, src, condition.negate());
}
/**
* @return True if the immediate can be used directly for logical 64-bit instructions.
*/
public static boolean isLogicalImmediate(long imm) {
return LogicalImmediateTable.isRepresentable(true, imm) != LogicalImmediateTable.Representable.NO;
}
/**
* @return True if the immediate can be used directly for logical 32-bit instructions.
*/
public static boolean isLogicalImmediate(int imm) {
return LogicalImmediateTable.isRepresentable(imm) == LogicalImmediateTable.Representable.YES;
}
/* Float instructions */
/**
* Moves integer to float, float to integer, or float to float. Does not support integer to
* integer moves.
*
* @param size register size. Has to be 32 or 64.
* @param dst Either floating-point or general-purpose register. If general-purpose register may
* not be stackpointer or zero register. Cannot be null in any case.
* @param src Either floating-point or general-purpose register. If general-purpose register may
* not be stackpointer. Cannot be null in any case.
*/
@Override
public void fmov(int size, Register dst, Register src) {
assert !(dst.getRegisterCategory().equals(CPU) && src.getRegisterCategory().equals(CPU)) : "src and dst cannot both be integer registers.";
if (dst.getRegisterCategory().equals(CPU)) {
super.fmovFpu2Cpu(size, dst, src);
} else if (src.getRegisterCategory().equals(CPU)) {
super.fmovCpu2Fpu(size, dst, src);
} else {
super.fmov(size, dst, src);
}
}
/**
*
* @param size register size. Has to be 32 or 64.
* @param dst floating point register. May not be null.
* @param imm immediate that is loaded into dst. If size is 32 only float immediates can be
* loaded, i.e. (float) imm == imm must be true. In all cases
* {@code isFloatImmediate}, respectively {@code #isDoubleImmediate} must be true
* depending on size.
*/
@Override
public void fmov(int size, Register dst, double imm) {
if (imm == 0.0) {
assert Double.doubleToRawLongBits(imm) == 0L : "-0.0 is no valid immediate.";
super.fmovCpu2Fpu(size, dst, zr);
} else {
super.fmov(size, dst, imm);
}
}
/**
*
* @return true if immediate can be loaded directly into floating-point register, false
* otherwise.
*/
public static boolean isDoubleImmediate(double imm) {
return Double.doubleToRawLongBits(imm) == 0L || AArch64Assembler.isDoubleImmediate(imm);
}
/**
*
* @return true if immediate can be loaded directly into floating-point register, false
* otherwise.
*/
public static boolean isFloatImmediate(float imm) {
return Float.floatToRawIntBits(imm) == 0 || AArch64Assembler.isFloatImmediate(imm);
}
/**
* Conditional move. dst = src1 if condition else src2.
*
* @param size register size.
* @param result floating point register. May not be null.
* @param trueValue floating point register. May not be null.
* @param falseValue floating point register. May not be null.
* @param condition every condition allowed. May not be null.
*/
public void fcmov(int size, Register result, Register trueValue, Register falseValue, ConditionFlag condition) {
super.fcsel(size, result, trueValue, falseValue, condition);
}
/**
* dst = src1 % src2.
*
* @param size register size. Has to be 32 or 64.
* @param dst floating-point register. May not be null.
* @param n numerator. Floating-point register. May not be null.
* @param d denominator. Floating-point register. May not be null.
*/
public void frem(int size, Register dst, Register n, Register d) {
// There is no frem instruction, instead we compute the remainder using the relation:
// rem = n - Truncating(n / d) * d
super.fdiv(size, dst, n, d);
super.frintz(size, dst, dst);
super.fmsub(size, dst, dst, d, n);
}
/* Branches */
/**
* Compares x and y and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param x general purpose register. May not be null or stackpointer.
* @param y general purpose register. May not be null or stackpointer.
*/
public void cmp(int size, Register x, Register y) {
super.subs(size, zr, x, y, ShiftType.LSL, 0);
}
/**
* Compares x to y and sets condition flags.
*
* @param size register size. Has to be 32 or 64.
* @param x general purpose register. May not be null or stackpointer.
* @param y comparison immediate, {@link #isComparisonImmediate(long)} has to be true for it.
*/
public void cmp(int size, Register x, int y) {
if (y < 0) {
super.adds(size, zr, x, -y);
} else {
super.subs(size, zr, x, y);
}
}
/**
* Sets condition flags according to result of x & y.
*
* @param size register size. Has to be 32 or 64.
* @param dst general purpose register. May not be null or stack-pointer.
* @param x general purpose register. May not be null or stackpointer.
* @param y general purpose register. May not be null or stackpointer.
*/
public void ands(int size, Register dst, Register x, Register y) {
super.ands(size, dst, x, y, ShiftType.LSL, 0);
}
/**
* When patching up Labels we have to know what kind of code to generate.
*/
public enum PatchLabelKind {
BRANCH_CONDITIONALLY(0x0),
BRANCH_UNCONDITIONALLY(0x1),
BRANCH_NONZERO(0x2),
BRANCH_ZERO(0x3),
JUMP_ADDRESS(0x4),
ADR(0x5);
/**
* Offset by which additional information for branch conditionally, branch zero and branch
* non zero has to be shifted.
*/
public static final int INFORMATION_OFFSET = 5;
public final int encoding;
PatchLabelKind(int encoding) {
this.encoding = encoding;
}
/**
* @return PatchLabelKind with given encoding.
*/
private static PatchLabelKind fromEncoding(int encoding) {
return values()[encoding & NumUtil.getNbitNumberInt(INFORMATION_OFFSET)];
}
}
public void adr(Register dst, Label label) {
// TODO Handle case where offset is too large for a single jump instruction
if (label.isBound()) {
int offset = label.position() - position();
super.adr(dst, offset);
} else {
label.addPatchAt(position());
// Encode condition flag so that we know how to patch the instruction later
emitInt(PatchLabelKind.ADR.encoding | dst.encoding << PatchLabelKind.INFORMATION_OFFSET);
}
}
/**
* Compare register and branch if non-zero.
*
* @param size Instruction size in bits. Should be either 32 or 64.
* @param cmp general purpose register. May not be null, zero-register or stackpointer.
* @param label Can only handle 21-bit word-aligned offsets for now. May be unbound. Non null.
*/
public void cbnz(int size, Register cmp, Label label) {
// TODO Handle case where offset is too large for a single jump instruction
if (label.isBound()) {
int offset = label.position() - position();
super.cbnz(size, cmp, offset);
} else {
label.addPatchAt(position());
int regEncoding = cmp.encoding << (PatchLabelKind.INFORMATION_OFFSET + 1);
int sizeEncoding = (size == 64 ? 1 : 0) << PatchLabelKind.INFORMATION_OFFSET;
// Encode condition flag so that we know how to patch the instruction later
emitInt(PatchLabelKind.BRANCH_NONZERO.encoding | regEncoding | sizeEncoding);
}
}
/**
* Compare register and branch if zero.
*
* @param size Instruction size in bits. Should be either 32 or 64.
* @param cmp general purpose register. May not be null, zero-register or stackpointer.
* @param label Can only handle 21-bit word-aligned offsets for now. May be unbound. Non null.
*/
public void cbz(int size, Register cmp, Label label) {
// TODO Handle case where offset is too large for a single jump instruction
if (label.isBound()) {
int offset = label.position() - position();
super.cbz(size, cmp, offset);
} else {
label.addPatchAt(position());
int regEncoding = cmp.encoding << (PatchLabelKind.INFORMATION_OFFSET + 1);
int sizeEncoding = (size == 64 ? 1 : 0) << PatchLabelKind.INFORMATION_OFFSET;
// Encode condition flag so that we know how to patch the instruction later
emitInt(PatchLabelKind.BRANCH_ZERO.encoding | regEncoding | sizeEncoding);
}
}
/**
* Branches to label if condition is true.
*
* @param condition any condition value allowed. Non null.
* @param label Can only handle 21-bit word-aligned offsets for now. May be unbound. Non null.
*/
public void branchConditionally(ConditionFlag condition, Label label) {
// TODO Handle case where offset is too large for a single jump instruction
if (label.isBound()) {
int offset = label.position() - position();
super.b(condition, offset);
} else {
label.addPatchAt(position());
// Encode condition flag so that we know how to patch the instruction later
emitInt(PatchLabelKind.BRANCH_CONDITIONALLY.encoding | condition.encoding << PatchLabelKind.INFORMATION_OFFSET);
}
}
/**
* Branches if condition is true. Address of jump is patched up by HotSpot c++ code.
*
* @param condition any condition value allowed. Non null.
*/
public void branchConditionally(ConditionFlag condition) {
// Correct offset is fixed up by HotSpot later.
super.b(condition, 0);
}
/**
* Jumps to label.
*
* param label Can only handle signed 28-bit offsets. May be unbound. Non null.
*/
@Override
public void jmp(Label label) {
// TODO Handle case where offset is too large for a single jump instruction
if (label.isBound()) {
int offset = label.position() - position();
super.b(offset);
} else {
label.addPatchAt(position());
emitInt(PatchLabelKind.BRANCH_UNCONDITIONALLY.encoding);
}
}
/**
* Jump to address in dest.
*
* @param dest General purpose register. May not be null, zero-register or stackpointer.
*/
public void jmp(Register dest) {
super.br(dest);
}
/**
* Immediate jump instruction fixed up by HotSpot c++ code.
*/
public void jmp() {
// Offset has to be fixed up by c++ code.
super.b(0);
}
/**
*
* @return true if immediate offset can be used in a single branch instruction.
*/
public static boolean isBranchImmediateOffset(long imm) {
return NumUtil.isSignedNbit(28, imm);
}
/* system instructions */
/**
* Exception codes used when calling hlt instruction.
*/
public enum AArch64ExceptionCode {
NO_SWITCH_TARGET(0x0),
BREAKPOINT(0x1);
public final int encoding;
AArch64ExceptionCode(int encoding) {
this.encoding = encoding;
}
}
/**
* Halting mode software breakpoint: Enters halting mode debug state if enabled, else treated as
* UNALLOCATED instruction.
*
* @param exceptionCode exception code specifying why halt was called. Non null.
*/
public void hlt(AArch64ExceptionCode exceptionCode) {
super.hlt(exceptionCode.encoding);
}
/**
* Monitor mode software breakpoint: exception routed to a debug monitor executing in a higher
* exception level.
*
* @param exceptionCode exception code specifying why break was called. Non null.
*/
public void brk(AArch64ExceptionCode exceptionCode) {
super.brk(exceptionCode.encoding);
}
public void pause() {
throw GraalError.unimplemented();
}
/**
* Executes no-op instruction. No registers or flags are updated, except for PC.
*/
public void nop() {
super.hint(SystemHint.NOP);
}
/**
* Same as {@link #nop()}.
*/
@Override
public void ensureUniquePC() {
nop();
}
/**
* Aligns PC.
*
* @param modulus Has to be positive multiple of 4.
*/
@Override
public void align(int modulus) {
assert modulus > 0 && (modulus & 0x3) == 0 : "Modulus has to be a positive multiple of 4.";
if (position() % modulus == 0) {
return;
}
int offset = modulus - position() % modulus;
for (int i = 0; i < offset; i += 4) {
nop();
}
}
/**
* Patches jump targets when label gets bound.
*/
@Override
protected void patchJumpTarget(int branch, int jumpTarget) {
int instruction = getInt(branch);
int branchOffset = jumpTarget - branch;
PatchLabelKind type = PatchLabelKind.fromEncoding(instruction);
switch (type) {
case BRANCH_CONDITIONALLY:
ConditionFlag cf = ConditionFlag.fromEncoding(instruction >>> PatchLabelKind.INFORMATION_OFFSET);
super.b(cf, branchOffset, branch);
break;
case BRANCH_UNCONDITIONALLY:
super.b(branchOffset, branch);
break;
case JUMP_ADDRESS:
emitInt(jumpTarget, branch);
break;
case BRANCH_NONZERO:
case BRANCH_ZERO: {
int information = instruction >>> PatchLabelKind.INFORMATION_OFFSET;
int sizeEncoding = information & 1;
int regEncoding = information >>> 1;
Register reg = AArch64.cpuRegisters.get(regEncoding);
// 1 => 64; 0 => 32
int size = sizeEncoding * 32 + 32;
switch (type) {
case BRANCH_NONZERO:
super.cbnz(size, reg, branchOffset, branch);
break;
case BRANCH_ZERO:
super.cbz(size, reg, branchOffset, branch);
break;
}
break;
}
case ADR: {
int information = instruction >>> PatchLabelKind.INFORMATION_OFFSET;
int regEncoding = information;
Register reg = AArch64.cpuRegisters.get(regEncoding);
super.adr(reg, branchOffset, branch);
break;
}
default:
throw GraalError.shouldNotReachHere();
}
}
/**
* Generates an address of the form {@code base + displacement}.
*
* Does not change base register to fulfill this requirement. Will fail if displacement cannot
* be represented directly as address.
*
* @param base general purpose register. May not be null or the zero register.
* @param displacement arbitrary displacement added to base.
* @return AArch64Address referencing memory at {@code base + displacement}.
*/
@Override
public AArch64Address makeAddress(Register base, int displacement) {
return makeAddress(base, displacement, zr, /* signExtend */false, /* transferSize */0, zr, /* allowOverwrite */false);
}
@Override
public AbstractAddress getPlaceholder(int instructionStartPosition) {
return AArch64Address.PLACEHOLDER;
}
}