///|
pub type GprWidth = @vcode.RegisterWidth
///|
pub type X64IntUnary = @vcode.ScalarIntUnary
///|
#derive(Eq, Debug)
pub(all) enum X64IntBinary {
Add
Sub
Mul
And
Orr
Eor
Lsl
Asr
Lsr
Ror
Sdiv
Udiv
}
///|
pub type X64Condition = @vcode.IntegerCondition
///|
pub type X64FloatUnary = @lowering.FloatUnaryOp
///|
pub type X64FloatBinary = @lowering.FloatBinaryOp
///|
pub type X64FloatTernary = @lowering.FloatTernaryOp
///|
pub type X64VectorIntUnary = @lowering.VectorIntUnaryOp
///|
pub type X64VectorIntBinary = @lowering.VectorIntBinaryOp
///|
pub type X64VectorPredicate = @lowering.VectorPredicateOp
///|
pub type X64VectorConversion = @vcode.VectorConversion
///|
pub type X64FloatCondition = @lowering.FloatComparison
///|
pub type X64FloatTrapCondition = @vcode.FloatTrapCondition
///|
pub type X64Address = @vcode.SymbolicAddress
///|
#derive(Eq, Debug)
pub(all) enum X64Conversion {
WrapI64ToI32
ExtendI32ToI64(@native.Signedness)
SignExtend(@native.IntegerType, @native.AccessWidth)
DemoteF64ToF32
PromoteF32ToF64
Bitcast(@native.ValueType, @native.ValueType)
IntToFloat(@native.IntegerType, @native.FloatType, @native.Signedness)
FloatToInt(@native.FloatType, @native.IntegerType, @native.Signedness)
FloatToIntSaturating(
@native.FloatType,
@native.IntegerType,
@native.Signedness
)
}
///|
pub type X64StackObject = @vcode.SizedStackObject
///|
#derive(Eq, Debug)
pub(all) enum X64Inst {
IncomingReg(@native.ValueType, @vcode.PhysicalReg)
IncomingStack(@native.ValueType, Int32)
IncomingResultArea(@vcode.PhysicalReg)
IncomingCallResult(@native.ValueType, @vcode.PhysicalReg)
IncomingCallAreaResult(@native.ValueType, Int32)
OutgoingReg(@native.ValueType, @vcode.PhysicalReg)
OutgoingAreaResult(@native.ValueType, Int32)
KeepAlive(@native.ValueType)
LoadConstant(GprWidth, UInt64)
LoadVectorConstant(UInt64, UInt64)
LoadNull(@native.ValueType)
LoadAddress(X64Address)
StackAddress(X64StackObject)
LoadFloatConstant(@native.ValueType, UInt64)
Move(@native.ValueType)
CarrierMove(@native.ValueType, @native.ValueType)
Select(@native.ValueType)
SpectreGuard
VectorSelect
VectorSplat(@lowering.VectorLane)
VectorExtractLane(@lowering.VectorLane, Int32, @native.Signedness?)
VectorReplaceLane(@lowering.VectorLane, Int32)
VectorShuffle(FixedArray[Int32])
VectorSwizzle
VectorBitwise(@lowering.VectorBitwiseOp)
VectorIntUnary(@lowering.VectorLane, X64VectorIntUnary)
VectorIntBinary(@lowering.VectorLane, X64VectorIntBinary)
VectorIntShift(@lowering.VectorLane, @lowering.VectorIntShiftOp)
VectorIntCompare(@lowering.VectorLane, @lowering.VectorIntComparison)
VectorConvert(X64VectorConversion)
VectorPredicate(X64VectorPredicate)
VectorFloatUnary(@lowering.VectorLane, @lowering.VectorFloatUnaryOp)
VectorFloatBinary(@lowering.VectorLane, @lowering.VectorFloatBinaryOp)
VectorFloatTernary(@lowering.VectorLane, @lowering.FloatTernaryOp)
VectorFloatCompare(@lowering.VectorLane, @lowering.VectorFloatComparison)
VectorPairwiseAddI16x8
VectorRelaxedDot8To16
IntUnary(GprWidth, X64IntUnary)
IntBinary(GprWidth, X64IntBinary)
PopulationCount(GprWidth)
IntHighMultiply(GprWidth, @native.Signedness)
IntWithOverflow(GprWidth, @lowering.IntOverflowOp)
IntRemainder(GprWidth, @native.Signedness)
TrapIfZero(GprWidth)
TrapIfSignedDivOverflow(GprWidth)
CompareSet(GprWidth, X64Condition)
ReferenceCompareSet(@native.ValueType, X64Condition)
FloatUnary(@native.ValueType, X64FloatUnary)
FloatBinary(@native.ValueType, X64FloatBinary)
FloatTernary(@native.ValueType, X64FloatTernary)
FloatCompareSet(@native.ValueType, X64FloatCondition)
TrapIfFloat(@native.ValueType, X64FloatTrapCondition)
Convert(X64Conversion)
AddAddress
ScalarLoad(
@native.AccessWidth,
@native.LoadExtension,
@native.ValueType,
UInt64
)
ScalarStore(@native.AccessWidth, @native.ValueType, UInt64)
StoreRepeated64(UInt64, UInt32, UInt64)
VectorLoad128(UInt64)
VectorStore128(UInt64)
VectorLoadSplat(@lowering.VectorLane, UInt64)
VectorLoadExtend(@lowering.VectorLane, @native.Signedness, UInt64)
VectorLoadZero(@native.AccessWidth, UInt64)
VectorLoadLane(@lowering.VectorLane, Int32, UInt64)
VectorStoreLane(@lowering.VectorLane, Int32, UInt64)
AtomicLoad(@native.AccessWidth, @native.ValueType)
AtomicStore(@native.AccessWidth, @native.ValueType)
AtomicRmw(@native.AccessWidth, @native.ValueType, @lowering.AtomicRmwOp)
AtomicCompareExchange(@native.AccessWidth, @native.ValueType)
AtomicFence
SafepointMarker
PlatformCall(@native.ExternalSymbol, @native.Signature)
ReturnsTwicePlatformCall(@native.ExternalSymbol, @native.Signature)
InternalCall(@native.CodeSymbol, @native.Signature, InternalCallPlan)
InternalCallIndirect(@native.Signature, InternalCallPlan)
TailCallDirect(@native.CodeSymbol, @native.Signature, InternalCallPlan)
TailCallIndirect(@native.Signature, InternalCallPlan)
Jump
BranchNonZero32
BranchCompare(GprWidth, X64Condition)
Switch(GprWidth, Array[UInt64])
Trap(@native.TrapReason)
Return
}
///|
let platform_integer_arguments : Array[Int32] = [7, 6, 2, 1, 8, 9]
///|
fn platform_call_layout(types : Array[@native.ValueType]) -> CallLayout {
@vcode.platform_call_layout(types, platform_integer_arguments[:])
}
///|
fn platform_result_registers(
types : Array[@native.ValueType],
) -> Array[@vcode.PhysicalReg] {
@vcode.platform_result_registers(types, integer_registers=[0, 2])
}
///|
fn platform_call_clobbers() -> Array[@vcode.PhysicalReg] {
let clobbers : Array[@vcode.PhysicalReg] = []
for index in [0, 1, 2, 6, 7, 8, 9, 10, 11] {
clobbers.push(@vcode.PhysicalReg::new(index, Int))
}
for index in 0..<16 {
clobbers.push(@vcode.PhysicalReg::new(index, FpVector))
}
clobbers
}
///|
fn internal_call_clobbers(plan : InternalCallPlan) -> Array[@vcode.PhysicalReg] {
let clobbers = platform_call_clobbers()
if plan.result_area_register() is Some(reg) && !clobbers.contains(reg) {
clobbers.push(reg)
}
clobbers
}
///|
fn integer_type(width : GprWidth) -> @native.ValueType {
match width {
W32 => I32
W64 => I64
}
}
///|
/// Physical registers an x64 instruction always overwrites beyond its declared
/// outputs.
///
/// These are properties of the instruction encoding itself, not of any
/// particular lowering path:
///
/// - Variable shifts and rotates take their count in `cl`, so the emitter
/// always materializes the count into `rcx`.
/// - `imul`/`idiv`/`div` and the `rax`-based atomic read-modify-write loops
/// write both `rax` and `rdx`.
///
/// Lowering must not be trusted to remember them. The `append_body` helper in
/// `lower.mbt` unions this set into every appended instruction, so a clobber
/// cannot be lost by a special-case lowering path that bypasses the generic
/// instruction-selection code.
fn X64Inst::mandatory_clobbers(self : X64Inst) -> Array[@vcode.PhysicalReg] {
let rax = @vcode.PhysicalReg::new(0, Int)
let rcx = @vcode.PhysicalReg::new(1, Int)
let rdx = @vcode.PhysicalReg::new(2, Int)
match self {
IntBinary(_, Lsl | Asr | Lsr | Ror) => [rcx]
IntBinary(_, Sdiv | Udiv) => [rdx]
IntRemainder(_, _) => [rax]
IntHighMultiply(_, _) => [rax]
IntWithOverflow(_, Mul(_)) => [rdx]
AtomicRmw(_, _, And | Or | Xor) => [rdx]
_ => []
}
}
///|
/// Reject VCode that omits an instruction's mandatory clobbers.
///
/// Register allocation trusts the declared clobber set: a missing entry lets it
/// keep a live value in a register the emitter is going to overwrite. Checking
/// it here makes the invariant hold for any VCode reaching the verifier, not
/// only VCode built through this module's `append_body` helper.
fn require_mandatory_clobbers(
function : @vcode.Function[X64Inst],
instruction : @vcode.Instruction,
) -> Unit raise TargetVCodeVerifyError {
let inst = function.instruction(instruction).unwrap()
let required = inst.mandatory_clobbers()
if required.is_empty() {
return
}
let declared = function.instruction_clobbers(instruction)
for reg in required {
if !declared.contains(reg) {
raise InvalidInstruction(
instruction~,
message="instruction must declare its mandatory clobber of physical register \{reg.id}",
)
}
}
}
///|
fn conversion_shape(
conversion : X64Conversion,
) -> (@native.ValueType, @native.ValueType)? {
match conversion {
WrapI64ToI32 => Some((I64, I32))
ExtendI32ToI64(_) => Some((I32, I64))
SignExtend(I32, W8 | W16) => Some((I32, I32))
SignExtend(I64, W8 | W16 | W32) => Some((I64, I64))
DemoteF64ToF32 => Some((F64, F32))
PromoteF32ToF64 => Some((F32, F64))
Bitcast(from, to) =>
if @native.valid_scalar_bitcast(from, to) {
Some((from, to))
} else {
None
}
IntToFloat(I32, F32, _) => Some((I32, F32))
IntToFloat(I32, F64, _) => Some((I32, F64))
IntToFloat(I64, F32, _) => Some((I64, F32))
IntToFloat(I64, F64, _) => Some((I64, F64))
FloatToInt(F32, I32, _) | FloatToIntSaturating(F32, I32, _) =>
Some((F32, I32))
FloatToInt(F32, I64, _) | FloatToIntSaturating(F32, I64, _) =>
Some((F32, I64))
FloatToInt(F64, I32, _) | FloatToIntSaturating(F64, I32, _) =>
Some((F64, I32))
FloatToInt(F64, I64, _) | FloatToIntSaturating(F64, I64, _) =>
Some((F64, I64))
_ => None
}
}
///|
pub fn verify_vcode(
function : @vcode.Function[X64Inst],
) -> Unit raise TargetVCodeVerifyError {
@vcode.verify_selected(function) catch {
error => raise CommonFailure(cause=error)
}
verify_target_vcode(function)
}
///|
fn verify_target_vcode(
function : @vcode.Function[X64Inst],
) -> Unit raise TargetVCodeVerifyError {
for index in 0.. check.incoming_reg(ty, reg)
IncomingStack(ty, offset) => check.incoming_stack(ty, offset)
IncomingResultArea(reg) => check.incoming_result_area(reg)
IncomingCallResult(ty, reg) => check.incoming_call_result(ty, reg)
IncomingCallAreaResult(ty, offset) =>
check.incoming_call_area_result(ty, offset)
LoadConstant(width, bits) =>
check.load_constant(integer_type(width), bits)
LoadVectorConstant(_, _) => check.shape([], [V128])
LoadNull(ty) => check.load_null(ty)
LoadAddress(_) => check.shape([], [Ptr64])
StackAddress(object) => check.stack_address(object.is_valid())
LoadFloatConstant(ty, bits) => check.load_float_constant(ty, bits)
OutgoingReg(ty, reg) => check.outgoing_reg(ty, reg)
OutgoingAreaResult(ty, offset) => check.outgoing_area_result(ty, offset)
KeepAlive(ty) => check.shape([ty], [])
Move(ty) => check.shape([ty], [ty])
CarrierMove(from, to) => check.carrier_move(from, to)
Select(ty) => check.scalar_select(ty)
SpectreGuard => check.shape([I32, Ptr64, Ptr64], [Ptr64])
VectorSelect => check.shape([I32, V128, V128], [V128])
VectorSplat(lane) => check.shape([lane.scalar_type()], [V128])
VectorExtractLane(lane, index, extension) =>
check.vector_extract_lane(lane, index, extension is Some(_))
VectorReplaceLane(lane, index) => check.vector_replace_lane(lane, index)
VectorShuffle(mask) => check.vector_shuffle(mask)
VectorSwizzle | VectorRelaxedDot8To16 | VectorPairwiseAddI16x8 =>
check.shape([V128, V128], [V128])
VectorIntUnary(lane, operation) => check.vector_int_unary(lane, operation)
VectorIntBinary(lane, operation) =>
check.vector_int_binary(lane, operation)
VectorIntShift(lane, _) => check.vector_int_shift(lane)
VectorIntCompare(lane, _) => check.vector_int_compare(lane)
VectorConvert(conversion) => check.vector_conversion(conversion)
VectorPredicate(predicate) => check.vector_predicate(predicate)
VectorFloatUnary(lane, _) => check.vector_float_unary(lane)
VectorFloatBinary(lane, _) | VectorFloatCompare(lane, _) =>
check.vector_float_binary(lane)
VectorFloatTernary(lane, _) => check.vector_float_ternary(lane)
VectorBitwise(operation) => check.vector_bitwise(operation)
IntUnary(width, _) | PopulationCount(width) =>
check.shape([integer_type(width)], [integer_type(width)])
IntBinary(width, _)
| IntHighMultiply(width, _)
| IntRemainder(width, _) =>
check.shape([integer_type(width), integer_type(width)], [
integer_type(width),
])
IntWithOverflow(width, _) =>
check.shape([integer_type(width), integer_type(width)], [
integer_type(width),
I32,
])
TrapIfZero(width) => check.trap_if_zero(integer_type(width))
TrapIfSignedDivOverflow(width) =>
check.trap_if_signed_div_overflow(integer_type(width))
CompareSet(width, _) =>
check.shape([integer_type(width), integer_type(width)], [I32])
ReferenceCompareSet(ty, _) => check.reference_compare(ty)
FloatUnary(ty, _) => check.float_unary(ty)
FloatBinary(ty, _) => check.float_binary(ty)
FloatTernary(ty, _) => check.float_ternary(ty)
FloatCompareSet(ty, _) => check.float_compare(ty)
TrapIfFloat(ty, condition) =>
check.trap_if_float(ty, condition == Unordered)
Convert(conversion) =>
check.conversion(
conversion_shape(conversion),
conversion is (Bitcast(I64, Ptr64) | Bitcast(Ptr64, I64)),
target="x64",
)
AddAddress => check.shape([Ptr64, I64], [Ptr64])
ScalarLoad(width, extension, result_type, _) =>
check.scalar_load(width, extension, result_type, target="x64")
StoreRepeated64(offset, count, _) => check.store_repeated64(offset, count)
ScalarStore(width, value_type, _) =>
check.scalar_store(width, value_type, target="x64")
VectorLoad128(_) | VectorLoadSplat(_, _) => check.shape([Ptr64], [V128])
VectorStore128(_) => check.shape([Ptr64, V128], [])
VectorLoadZero(width, _) => check.vector_load_zero(width)
VectorLoadExtend(lane, _, _) => check.vector_load_extend(lane)
VectorLoadLane(lane, index, _) => check.vector_load_lane(lane, index)
VectorStoreLane(lane, index, _) => check.vector_store_lane(lane, index)
AtomicLoad(width, ty) => check.atomic_load(width, ty, target="x64")
AtomicStore(width, ty) => check.atomic_store(width, ty, target="x64")
AtomicRmw(width, ty, operation) => {
check.atomic_rmw(width, ty, target="x64")
if operation is (And | Or | Xor) {
check.atomic_early_def(
"compare-exchange atomic RMW result must be an early definition",
)
}
}
AtomicCompareExchange(width, ty) => {
check.atomic_compare_exchange(width, ty, target="x64")
for index, register in [11, 0, 10, 0] {
let operand = function
.instruction_operand_at(instruction, index)
.unwrap()
if operand.constraint != FixedReg(int_physical(register)) {
raise InvalidInstruction(
instruction~,
message="atomic compare-exchange must reserve its address, accumulator, and replacement registers",
)
}
}
}
AtomicFence => check.shape([], [])
SafepointMarker => check.safepoint_marker()
PlatformCall(_, signature) | ReturnsTwicePlatformCall(_, signature) =>
check.platform_call(
signature, platform_call_layout, is_allocatable, platform_call_clobbers,
)
InternalCall(_, signature, plan) =>
check.internal_call(
signature,
plan,
indirect=false,
is_allocatable,
internal_call_clobbers,
)
InternalCallIndirect(signature, plan) =>
check.internal_call(
signature,
plan,
indirect=true,
is_allocatable,
internal_call_clobbers,
)
TailCallDirect(_, signature, plan) =>
require_tail_call_plan(
function,
instruction,
signature,
plan,
false,
is_allocatable,
internal_call_clobbers(plan),
)
TailCallIndirect(signature, plan) =>
require_tail_call_plan(
function,
instruction,
signature,
plan,
true,
is_allocatable,
internal_call_clobbers(plan),
)
Jump => check.shape([], [], successors=1)
BranchNonZero32 => check.shape([I32], [], successors=2)
BranchCompare(width, _) =>
check.shape(
[integer_type(width), integer_type(width)],
[],
successors=2,
)
Switch(width, cases) => check.switch_shape(integer_type(width), cases)
Trap(reason) => check.trap(reason)
Return => check.return_shape()
}
}
@vcode.verify_abi_materialization(
function, abi_materialization, platform_result_registers,
)
}
///|
fn abi_materialization(inst : X64Inst) -> @vcode.AbiMaterialization {
match inst {
PlatformCall(_, signature) | ReturnsTwicePlatformCall(_, signature) =>
PlatformResults(signature)
InternalCall(_, signature, plan) | InternalCallIndirect(signature, plan) =>
InternalResults(signature, plan)
TailCallDirect(_, signature, plan) if plan.result_area_size > 0 =>
ForwardResultArea(signature.params.length(), indirect=false)
TailCallIndirect(signature, plan) if plan.result_area_size > 0 =>
ForwardResultArea(signature.params.length() + 1, indirect=true)
IncomingResultArea(_) => ReceiveResultArea
IncomingCallResult(ty, reg) => ReceiveRegisterResult(ty, reg)
IncomingCallAreaResult(ty, offset) => ReceiveAreaResult(ty, offset)
OutgoingReg(ty, _) => ReturnRegisterResult(ty)
OutgoingAreaResult(ty, _) => ReturnAreaResult(ty)
Return => Return
_ => Ordinary
}
}