rust/src/asm.rs

786 lines
37 KiB
Rust
Raw Normal View History

use gccjit::{LValue, RValue, ToRValue, Type};
2020-05-10 09:54:30 -05:00
use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece};
use rustc_codegen_ssa::mir::operand::OperandValue;
use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::traits::{AsmBuilderMethods, AsmMethods, BaseTypeMethods, BuilderMethods, GlobalAsmOperandRef, InlineAsmOperandRef};
2020-05-10 09:54:30 -05:00
use rustc_hir::LlvmInlineAsmInner;
use rustc_middle::{bug, ty::Instance};
use rustc_span::{Span, Symbol};
2020-05-10 09:54:30 -05:00
use rustc_target::asm::*;
use std::borrow::Cow;
2020-05-10 09:54:30 -05:00
use crate::builder::Builder;
use crate::context::CodegenCx;
use crate::type_of::LayoutGccExt;
// Rust asm! and GCC Extended Asm semantics differ substantially.
//
// 1. Rust asm operands go along as one list of operands. Operands themselves indicate
// if they're "in" or "out". "In" and "out" operands can interleave. One operand can be
// both "in" and "out" (`inout(reg)`).
//
// GCC asm has two different lists for "in" and "out" operands. In terms of gccjit,
// this means that all "out" operands must go before "in" operands. "In" and "out" operands
// cannot interleave.
//
// 2. Operand lists in both Rust and GCC are indexed. Index starts from 0. Indexes are important
// because the asm template refers to operands by index.
//
// Mapping from Rust to GCC index would be 1-1 if it wasn't for...
//
// 3. Clobbers. GCC has a separate list of clobbers, and clobbers don't have indexes.
// Contrary, Rust expresses clobbers through "out" operands that aren't tied to
// a variable (`_`), and such "clobbers" do have index.
//
// 4. Furthermore, GCC Extended Asm does not support explicit register constraints
// (like `out("eax")`) directly, offering so-called "local register variables"
// as a workaround. These variables need to be declared and initialized *before*
// the Extended Asm block but *after* normal local variables
// (see comment in `codegen_inline_asm` for explanation).
//
// With that in mind, let's see how we translate Rust syntax to GCC
// (from now on, `CC` stands for "constraint code"):
//
// * `out(reg_class) var` -> translated to output operand: `"=CC"(var)`
// * `inout(reg_class) var` -> translated to output operand: `"+CC"(var)`
// * `in(reg_class) var` -> translated to input operand: `"CC"(var)`
//
// * `out(reg_class) _` -> translated to one `=r(tmp)`, where "tmp" is a temporary unused variable
//
// * `out("explicit register") _` -> not translated to any operands, register is simply added to clobbers list
//
// * `inout(reg_class) in_var => out_var` -> translated to two operands:
// output: `"=CC"(in_var)`
// input: `"num"(out_var)` where num is the GCC index
// of the corresponding output operand
//
// * `inout(reg_class) in_var => _` -> same as `inout(reg_class) in_var => tmp`,
// where "tmp" is a temporary unused variable
//
// * `out/in/inout("explicit register") var` -> translated to one or two operands as described above
// with `"r"(var)` constraint,
// and one register variable assigned to the desired register.
//
const ATT_SYNTAX_INS: &str = ".att_syntax noprefix\n\t";
const INTEL_SYNTAX_INS: &str = "\n\t.intel_syntax noprefix";
struct AsmOutOperand<'a, 'tcx, 'gcc> {
rust_idx: usize,
constraint: &'a str,
late: bool,
readwrite: bool,
tmp_var: LValue<'gcc>,
out_place: Option<PlaceRef<'tcx, RValue<'gcc>>>
}
struct AsmInOperand<'a, 'tcx> {
rust_idx: usize,
constraint: Cow<'a, str>,
val: RValue<'tcx>
}
impl AsmOutOperand<'_, '_, '_> {
fn to_constraint(&self) -> String {
let mut res = String::with_capacity(self.constraint.len() + self.late as usize + 1);
let sign = if self.readwrite { '+' } else { '=' };
res.push(sign);
if !self.late {
res.push('&');
}
res.push_str(&self.constraint);
res
}
}
enum ConstraintOrRegister {
Constraint(&'static str),
Register(&'static str)
}
2020-05-10 09:54:30 -05:00
impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
fn codegen_llvm_inline_asm(&mut self, _ia: &LlvmInlineAsmInner, _outputs: Vec<PlaceRef<'tcx, RValue<'gcc>>>, _inputs: Vec<RValue<'gcc>>, span: Span) -> bool {
self.sess().struct_span_err(span, "GCC backend does not support `llvm_asm!`")
.help("consider using the `asm!` macro instead")
.emit();
// We return `true` even if we've failed to generate the asm
// because we want to suppress the "malformed inline assembly" error
// generated by the frontend.
true
2020-05-10 09:54:30 -05:00
}
Sync from rust (#107) * Rebase fallout. * Move rustc_middle::middle::cstore to rustc_session. * Create more accurate debuginfo for vtables. Before this commit all vtables would have the same name "vtable" in debuginfo. Now they get a name that identifies the implementing type and the trait that is being implemented. * Remove alloc::prelude As per the libs team decision in #58935. Closes #58935 * Make hash_result an Option. * Properly check `target_features` not to trigger an assertion * Add LLVM CFI support to the Rust compiler This commit adds LLVM Control Flow Integrity (CFI) support to the Rust compiler. It initially provides forward-edge control flow protection for Rust-compiled code only by aggregating function pointers in groups identified by their number of arguments. Forward-edge control flow protection for C or C++ and Rust -compiled code "mixed binaries" (i.e., for when C or C++ and Rust -compiled code share the same virtual address space) will be provided in later work as part of this project by defining and using compatible type identifiers (see Type metadata in the design document in the tracking issue #89653). LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto). * Update to nightly-2021-10-30 * Add deduplication of constant values as rustc relies on LLVM doing that Co-authored-by: Camille GILLOT <gillot.camille@gmail.com> Co-authored-by: Michael Woerister <michaelwoerister@posteo> Co-authored-by: Amanieu d'Antras <amanieu@gmail.com> Co-authored-by: Yuki Okushi <yuki.okushi@huawei.com> Co-authored-by: Ramon de C Valle <rcvalle@users.noreply.github.com>
2021-10-30 17:21:33 -05:00
fn codegen_inline_asm(&mut self, template: &[InlineAsmTemplatePiece], rust_operands: &[InlineAsmOperandRef<'tcx, Self>], options: InlineAsmOptions, _span: &[Span], _instance: Instance<'_>) {
2020-05-10 09:54:30 -05:00
let asm_arch = self.tcx.sess.asm_arch.unwrap();
let is_x86 = matches!(asm_arch, InlineAsmArch::X86 | InlineAsmArch::X86_64);
let att_dialect = is_x86 && options.contains(InlineAsmOptions::ATT_SYNTAX);
let intel_dialect = is_x86 && !options.contains(InlineAsmOptions::ATT_SYNTAX);
2020-05-10 09:54:30 -05:00
// GCC index of an output operand equals its position in the array
let mut outputs = vec![];
// GCC index of an input operand equals its position in the array
// added to `outputs.len()`
let mut inputs = vec![];
2020-05-10 09:54:30 -05:00
// Clobbers collected from `out("explicit register") _` and `inout("expl_reg") var => _`
let mut clobbers = vec![];
// We're trying to preallocate space for the template
let mut constants_len = 0;
// There are rules we must adhere to if we want GCC to do the right thing:
//
// * Every local variable that the asm block uses as an output must be declared *before*
// the asm block.
// * There must be no instructions whatsoever between the register variables and the asm.
//
// Therefore, the backend must generate the instructions strictly in this order:
//
// 1. Output variables.
// 2. Register variables.
// 3. The asm block.
//
// We also must make sure that no input operands are emitted before output operands.
//
// This is why we work in passes, first emitting local vars, then local register vars.
// Also, we don't emit any asm operands immediately; we save them to
// the one of the buffers to be emitted later.
// 1. Normal variables (and saving operands to buffers).
for (rust_idx, op) in rust_operands.iter().enumerate() {
2020-05-10 09:54:30 -05:00
match *op {
InlineAsmOperandRef::Out { reg, late, place } => {
use ConstraintOrRegister::*;
let (constraint, ty) = match (reg_to_gcc(reg), place) {
(Constraint(constraint), Some(place)) => (constraint, place.layout.gcc_type(self.cx, false)),
// When `reg` is a class and not an explicit register but the out place is not specified,
// we need to create an unused output variable to assign the output to. This var
// needs to be of a type that's "compatible" with the register class, but specific type
// doesn't matter.
(Constraint(constraint), None) => (constraint, dummy_output_type(self.cx, reg.reg_class())),
(Register(_), Some(_)) => {
// left for the next pass
continue
},
(Register(reg_name), None) => {
// `clobber_abi` can add lots of clobbers that are not supported by the target,
// such as AVX-512 registers, so we just ignore unsupported registers
let is_target_supported = reg.reg_class().supported_types(asm_arch).iter()
.any(|&(_, feature)| {
if let Some(feature) = feature {
self.tcx.sess.target_features.contains(&Symbol::intern(feature))
} else {
true // Register class is unconditionally supported
}
});
if is_target_supported && !clobbers.contains(&reg_name) {
clobbers.push(reg_name);
}
continue
}
};
let tmp_var = self.current_func().new_local(None, ty, "output_register");
outputs.push(AsmOutOperand {
constraint,
rust_idx,
late,
readwrite: false,
tmp_var,
out_place: place
});
2020-05-10 09:54:30 -05:00
}
InlineAsmOperandRef::In { reg, value } => {
if let ConstraintOrRegister::Constraint(constraint) = reg_to_gcc(reg) {
inputs.push(AsmInOperand {
constraint: Cow::Borrowed(constraint),
rust_idx,
val: value.immediate()
});
}
else {
// left for the next pass
continue
}
}
InlineAsmOperandRef::InOut { reg, late, in_value, out_place } => {
let constraint = if let ConstraintOrRegister::Constraint(constraint) = reg_to_gcc(reg) {
constraint
}
else {
// left for the next pass
continue
};
// Rustc frontend guarantees that input and output types are "compatible",
// so we can just use input var's type for the output variable.
//
// This decision is also backed by the fact that LLVM needs in and out
// values to be of *exactly the same type*, not just "compatible".
// I'm not sure if GCC is so picky too, but better safe than sorry.
let ty = in_value.layout.gcc_type(self.cx, false);
let tmp_var = self.current_func().new_local(None, ty, "output_register");
// If the out_place is None (i.e `inout(reg) _` syntax was used), we translate
// it to one "readwrite (+) output variable", otherwise we translate it to two
// "out and tied in" vars as described above.
let readwrite = out_place.is_none();
outputs.push(AsmOutOperand {
constraint,
rust_idx,
late,
readwrite,
tmp_var,
out_place,
});
if !readwrite {
let out_gcc_idx = outputs.len() - 1;
let constraint = Cow::Owned(out_gcc_idx.to_string());
inputs.push(AsmInOperand {
constraint,
rust_idx,
val: in_value.immediate()
});
}
}
InlineAsmOperandRef::Const { ref string } => {
constants_len += string.len() + att_dialect as usize;
}
InlineAsmOperandRef::SymFn { instance } => {
constants_len += self.tcx.symbol_name(instance).name.len();
}
InlineAsmOperandRef::SymStatic { def_id } => {
constants_len += self.tcx.symbol_name(Instance::mono(self.tcx, def_id)).name.len();
2020-05-10 09:54:30 -05:00
}
}
}
// 2. Register variables.
for (rust_idx, op) in rust_operands.iter().enumerate() {
2020-05-10 09:54:30 -05:00
match *op {
// `out("explicit register") var`
InlineAsmOperandRef::Out { reg, late, place } => {
if let ConstraintOrRegister::Register(reg_name) = reg_to_gcc(reg) {
let out_place = if let Some(place) = place {
place
}
else {
// processed in the previous pass
continue
};
let ty = out_place.layout.gcc_type(self.cx, false);
let tmp_var = self.current_func().new_local(None, ty, "output_register");
tmp_var.set_register_name(reg_name);
outputs.push(AsmOutOperand {
constraint: "r".into(),
rust_idx,
late,
readwrite: false,
tmp_var,
out_place: Some(out_place)
});
}
// processed in the previous pass
}
// `in("explicit register") var`
InlineAsmOperandRef::In { reg, value } => {
if let ConstraintOrRegister::Register(reg_name) = reg_to_gcc(reg) {
let ty = value.layout.gcc_type(self.cx, false);
let reg_var = self.current_func().new_local(None, ty, "input_register");
reg_var.set_register_name(reg_name);
self.llbb().add_assignment(None, reg_var, value.immediate());
inputs.push(AsmInOperand {
constraint: "r".into(),
rust_idx,
val: reg_var.to_rvalue()
});
}
// processed in the previous pass
}
// `inout("explicit register") in_var => out_var`
InlineAsmOperandRef::InOut { reg, late, in_value, out_place } => {
if let ConstraintOrRegister::Register(reg_name) = reg_to_gcc(reg) {
let out_place = if let Some(place) = out_place {
place
}
else {
// processed in the previous pass
continue
};
// See explanation in the first pass.
let ty = in_value.layout.gcc_type(self.cx, false);
let tmp_var = self.current_func().new_local(None, ty, "output_register");
tmp_var.set_register_name(reg_name);
outputs.push(AsmOutOperand {
constraint: "r".into(),
rust_idx,
late,
readwrite: false,
tmp_var,
out_place: Some(out_place)
});
let constraint = Cow::Owned((outputs.len() - 1).to_string());
inputs.push(AsmInOperand {
constraint,
rust_idx,
val: in_value.immediate()
});
}
// processed in the previous pass
}
InlineAsmOperandRef::Const { .. }
| InlineAsmOperandRef::SymFn { .. }
| InlineAsmOperandRef::SymStatic { .. } => {
// processed in the previous pass
}
2020-05-10 09:54:30 -05:00
}
}
// 3. Build the template string
let mut template_str = String::with_capacity(estimate_template_length(template, constants_len, att_dialect));
if !intel_dialect {
template_str.push_str(ATT_SYNTAX_INS);
}
2020-05-10 09:54:30 -05:00
for piece in template {
match *piece {
InlineAsmTemplatePiece::String(ref string) => {
// TODO(@Commeownist): switch to `Iterator::intersperse` once it's stable
let mut iter = string.split('%');
if let Some(s) = iter.next() {
template_str.push_str(s);
2020-05-10 09:54:30 -05:00
}
for s in iter {
template_str.push_str("%%");
template_str.push_str(s);
2020-05-10 09:54:30 -05:00
}
}
InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span: _ } => {
let mut push_to_template = |modifier, gcc_idx| {
use std::fmt::Write;
template_str.push('%');
if let Some(modifier) = modifier {
template_str.push(modifier);
}
write!(template_str, "{}", gcc_idx).expect("pushing to string failed");
};
match rust_operands[operand_idx] {
InlineAsmOperandRef::Out { reg, .. } => {
2020-05-10 09:54:30 -05:00
let modifier = modifier_to_gcc(asm_arch, reg.reg_class(), modifier);
let gcc_index = outputs.iter()
.position(|op| operand_idx == op.rust_idx)
.expect("wrong rust index");
push_to_template(modifier, gcc_index);
}
InlineAsmOperandRef::In { reg, .. } => {
2020-05-10 09:54:30 -05:00
let modifier = modifier_to_gcc(asm_arch, reg.reg_class(), modifier);
let in_gcc_index = inputs.iter()
.position(|op| operand_idx == op.rust_idx)
.expect("wrong rust index");
let gcc_index = in_gcc_index + outputs.len();
push_to_template(modifier, gcc_index);
}
InlineAsmOperandRef::InOut { reg, .. } => {
let modifier = modifier_to_gcc(asm_arch, reg.reg_class(), modifier);
// The input register is tied to the output, so we can just use the index of the output register
let gcc_index = outputs.iter()
.position(|op| operand_idx == op.rust_idx)
.expect("wrong rust index");
push_to_template(modifier, gcc_index);
2020-05-10 09:54:30 -05:00
}
InlineAsmOperandRef::SymFn { instance } => {
let name = self.tcx.symbol_name(instance).name;
template_str.push_str(name);
}
InlineAsmOperandRef::SymStatic { def_id } => {
// TODO(@Commeownist): This may not be sufficient for all kinds of statics.
// Some statics may need the `@plt` suffix, like thread-local vars.
let instance = Instance::mono(self.tcx, def_id);
let name = self.tcx.symbol_name(instance).name;
template_str.push_str(name);
}
2020-05-10 09:54:30 -05:00
InlineAsmOperandRef::Const { ref string } => {
// Const operands get injected directly into the template
if att_dialect {
template_str.push('$');
}
2020-05-10 09:54:30 -05:00
template_str.push_str(string);
}
}
}
}
}
if !intel_dialect {
template_str.push_str(INTEL_SYNTAX_INS);
}
// 4. Generate Extended Asm block
2020-05-10 09:54:30 -05:00
let block = self.llbb();
let extended_asm = block.add_extended_asm(None, &template_str);
for op in &outputs {
extended_asm.add_output_operand(None, &op.to_constraint(), op.tmp_var);
}
2020-05-10 09:54:30 -05:00
for op in &inputs {
extended_asm.add_input_operand(None, &op.constraint, op.val);
}
2020-05-10 09:54:30 -05:00
for clobber in clobbers.iter() {
extended_asm.add_clobber(clobber);
}
if !options.contains(InlineAsmOptions::PRESERVES_FLAGS) {
// TODO(@Commeownist): I'm not 100% sure this one clobber is sufficient
// on all architectures. For instance, what about FP stack?
extended_asm.add_clobber("cc");
}
if !options.contains(InlineAsmOptions::NOMEM) {
extended_asm.add_clobber("memory");
}
if !options.contains(InlineAsmOptions::PURE) {
extended_asm.set_volatile_flag(true);
}
if !options.contains(InlineAsmOptions::NOSTACK) {
// TODO(@Commeownist): figure out how to align stack
}
if options.contains(InlineAsmOptions::NORETURN) {
let builtin_unreachable = self.context.get_builtin_function("__builtin_unreachable");
let builtin_unreachable: RValue<'gcc> = unsafe { std::mem::transmute(builtin_unreachable) };
self.call(self.type_void(), builtin_unreachable, &[], None);
}
// Write results to outputs.
//
// We need to do this because:
// 1. Turning `PlaceRef` into `RValue` is error-prone and has nasty edge cases
// (especially with current `rustc_backend_ssa` API).
// 2. Not every output operand has an `out_place`, and it's required by `add_output_operand`.
//
// Instead, we generate a temporary output variable for each output operand, and then this loop,
// generates `out_place = tmp_var;` assignments if out_place exists.
for op in &outputs {
if let Some(place) = op.out_place {
OperandValue::Immediate(op.tmp_var.to_rvalue()).store(self, place);
2020-05-10 09:54:30 -05:00
}
}
}
}
fn estimate_template_length(template: &[InlineAsmTemplatePiece], constants_len: usize, att_dialect: bool) -> usize {
let len: usize = template.iter().map(|piece| {
match *piece {
InlineAsmTemplatePiece::String(ref string) => {
string.len()
}
InlineAsmTemplatePiece::Placeholder { .. } => {
// '%' + 1 char modifier + 1 char index
3
2020-05-10 09:54:30 -05:00
}
}
})
.sum();
// increase it by 5% to account for possible '%' signs that'll be duplicated
// I pulled the number out of blue, but should be fair enough
// as the upper bound
let mut res = (len as f32 * 1.05) as usize + constants_len;
if att_dialect {
res += INTEL_SYNTAX_INS.len() + ATT_SYNTAX_INS.len();
2020-05-10 09:54:30 -05:00
}
res
2020-05-10 09:54:30 -05:00
}
/// Converts a register class to a GCC constraint code.
fn reg_to_gcc(reg: InlineAsmRegOrRegClass) -> ConstraintOrRegister {
let constraint = match reg {
2020-05-10 09:54:30 -05:00
// For vector registers LLVM wants the register name to match the type size.
InlineAsmRegOrRegClass::Reg(reg) => {
match reg {
InlineAsmReg::X86(_) => {
// TODO(antoyo): add support for vector register.
//
// // For explicit registers, we have to create a register variable: https://stackoverflow.com/a/31774784/389119
return ConstraintOrRegister::Register(match reg.name() {
// Some of registers' names does not map 1-1 from rust to gcc
"st(0)" => "st",
name => name,
});
}
_ => unimplemented!(),
}
2020-05-10 09:54:30 -05:00
},
InlineAsmRegOrRegClass::RegClass(reg) => match reg {
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::preg) => unimplemented!(),
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) => unimplemented!(),
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) => unimplemented!(),
InlineAsmRegClass::Bpf(_) => unimplemented!(),
InlineAsmRegClass::Hexagon(HexagonInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::Mips(MipsInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::Mips(MipsInlineAsmRegClass::freg) => unimplemented!(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg16) => unimplemented!(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg32) => unimplemented!(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg64) => unimplemented!(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg_nonzero) => unimplemented!(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::freg) => unimplemented!(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::cr)
| InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::xer) => {
unreachable!("clobber-only")
},
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => unimplemented!(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::vreg) => unimplemented!(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r",
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => "Q",
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => "q",
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg)
| InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) => "x",
InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => "v",
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => unimplemented!(),
InlineAsmRegClass::Wasm(WasmInlineAsmRegClass::local) => unimplemented!(),
InlineAsmRegClass::X86(
X86InlineAsmRegClass::x87_reg | X86InlineAsmRegClass::mmx_reg,
) => unreachable!("clobber-only"),
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("GCC backend does not support SPIR-V")
}
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::freg) => unimplemented!(),
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::Err => unreachable!(),
}
};
ConstraintOrRegister::Constraint(constraint)
2020-05-10 09:54:30 -05:00
}
/// Type to use for outputs that are discarded. It doesn't really matter what
/// the type is, as long as it is valid for the constraint code.
fn dummy_output_type<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, reg: InlineAsmRegClass) -> Type<'gcc> {
match reg {
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::preg) => unimplemented!(),
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg)
| InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => {
unimplemented!()
}
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => cx.type_i32(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) => cx.type_f32(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) => cx.type_f64(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => {
unimplemented!()
}
InlineAsmRegClass::Bpf(_) => unimplemented!(),
InlineAsmRegClass::Hexagon(HexagonInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::Mips(MipsInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::Mips(MipsInlineAsmRegClass::freg) => cx.type_f32(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg16) => cx.type_i16(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg32) => cx.type_i32(),
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg64) => cx.type_i64(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg_nonzero) => cx.type_i32(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::freg) => cx.type_f64(),
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::cr)
| InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::xer) => {
unreachable!("clobber-only")
},
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => cx.type_f32(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::vreg) => cx.type_f32(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg)
| InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => cx.type_i32(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => cx.type_i8(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::mmx_reg) => unimplemented!(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg)
| InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg)
| InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => cx.type_f32(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::x87_reg) => unimplemented!(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => cx.type_i16(),
InlineAsmRegClass::Wasm(WasmInlineAsmRegClass::local) => cx.type_i32(),
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("LLVM backend does not support SPIR-V")
},
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::freg) => cx.type_f64(),
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::Err => unreachable!(),
}
}
impl<'gcc, 'tcx> AsmMethods for CodegenCx<'gcc, 'tcx> {
fn codegen_global_asm(&self, template: &[InlineAsmTemplatePiece], operands: &[GlobalAsmOperandRef], options: InlineAsmOptions, _line_spans: &[Span]) {
let asm_arch = self.tcx.sess.asm_arch.unwrap();
// Default to Intel syntax on x86
let intel_syntax = matches!(asm_arch, InlineAsmArch::X86 | InlineAsmArch::X86_64)
&& !options.contains(InlineAsmOptions::ATT_SYNTAX);
// Build the template string
let mut template_str = String::new();
for piece in template {
match *piece {
InlineAsmTemplatePiece::String(ref string) => {
for line in string.lines() {
// NOTE: gcc does not allow inline comment, so remove them.
let line =
if let Some(index) = line.rfind("//") {
&line[..index]
}
else {
line
};
template_str.push_str(line);
template_str.push('\n');
}
},
InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span: _ } => {
match operands[operand_idx] {
GlobalAsmOperandRef::Const { ref string } => {
// Const operands get injected directly into the
// template. Note that we don't need to escape %
2020-05-10 09:54:30 -05:00
// here unlike normal inline assembly.
template_str.push_str(string);
}
}
}
}
}
let template_str =
if intel_syntax {
format!("{}\n\t.intel_syntax noprefix", template_str)
}
else {
format!(".att_syntax\n\t{}\n\t.intel_syntax noprefix", template_str)
};
// NOTE: seems like gcc will put the asm in the wrong section, so set it to .text manually.
let template_str = format!(".pushsection .text\n{}\n.popsection", template_str);
self.context.add_top_level_asm(None, &template_str);
}
}
fn modifier_to_gcc(arch: InlineAsmArch, reg: InlineAsmRegClass, modifier: Option<char>) -> Option<char> {
match reg {
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => modifier,
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::preg) => modifier,
InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg)
| InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => {
unimplemented!()
}
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) => unimplemented!(),
InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8)
| InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => {
unimplemented!()
}
InlineAsmRegClass::Bpf(_) => unimplemented!(),
InlineAsmRegClass::Hexagon(_) => unimplemented!(),
InlineAsmRegClass::Mips(_) => unimplemented!(),
InlineAsmRegClass::Nvptx(_) => unimplemented!(),
InlineAsmRegClass::PowerPC(_) => unimplemented!(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg)
| InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => unimplemented!(),
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::vreg) => unimplemented!(),
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg)
| InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => match modifier {
None => if arch == InlineAsmArch::X86_64 { Some('q') } else { Some('k') },
2020-05-10 09:54:30 -05:00
Some('l') => Some('b'),
Some('h') => Some('h'),
Some('x') => Some('w'),
Some('e') => Some('k'),
Some('r') => Some('q'),
_ => unreachable!(),
},
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => None,
InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::xmm_reg)
| InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::ymm_reg)
| InlineAsmRegClass::X86(reg @ X86InlineAsmRegClass::zmm_reg) => match (reg, modifier) {
(X86InlineAsmRegClass::xmm_reg, None) => Some('x'),
(X86InlineAsmRegClass::ymm_reg, None) => Some('t'),
(X86InlineAsmRegClass::zmm_reg, None) => Some('g'),
(_, Some('x')) => Some('x'),
(_, Some('y')) => Some('t'),
(_, Some('z')) => Some('g'),
_ => unreachable!(),
},
InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => None,
InlineAsmRegClass::X86(X86InlineAsmRegClass::x87_reg | X86InlineAsmRegClass::mmx_reg) => {
unreachable!("clobber-only")
}
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::Wasm(WasmInlineAsmRegClass::local) => unimplemented!(),
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("LLVM backend does not support SPIR-V")
},
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::reg) => unimplemented!(),
InlineAsmRegClass::S390x(S390xInlineAsmRegClass::freg) => unimplemented!(),
2020-05-10 09:54:30 -05:00
InlineAsmRegClass::Err => unreachable!(),
}
}